package versionSer import ( "bufio" "bytes" "context" "fmt" "io" "os" "regexp" "stat/api/proto" "stat/common/log" "stat/common/utils" downloadinfo "stat/models/downloadinfo" "stat/models/versionMod" "stat/service/cacheSer" "stat/statg" "time" "github.com/google/uuid" "github.com/xxjwxc/public/myglobal/snowflake" ) func GenerateConfigFile(origin, target, configFile, DC, PC string) ([]byte, string) { if len(DC) > 0 && len(PC) > 0 { node, err := snowflake.NewNode(1) if err == nil { id := node.Generate() target = fmt.Sprintf("%s?dc=%s&pc=%s&UID=%d", target, DC, PC, id) } } else if len(DC) > 0 { node, err := snowflake.NewNode(1) if err == nil { id := node.Generate() target = fmt.Sprintf("%s?dc=%s&UID=%d", target, DC, id) } } else if len(PC) > 0 { node, err := snowflake.NewNode(1) if err == nil { id := node.Generate() target = fmt.Sprintf("%s?pc=%s&UID=%d", target, PC, id) } } else { node, err := snowflake.NewNode(1) if err == nil { id := node.Generate() target = fmt.Sprintf("%s?UID=%d", target, id) } } target = fmt.Sprintf("%s&isLight=true", target) output, _, err := readFile("conf/"+configFile, origin, target) if err != nil { output, _ = os.ReadFile("conf/" + configFile) } return uniqueProfileIdentity(output, configFile), configFile } // profileIDNamespace 标识符派生的固定命名空间,保证同一模板每次生成的值一致 var profileIDNamespace = uuid.NewSHA1(uuid.NameSpaceURL, []byte("stat_center/mobileconfig")) var ( rePayloadIdent = regexp.MustCompile(`(PayloadIdentifier[ \t]*\r?\n[ \t]*)([^<]*)()`) rePayloadUUID = regexp.MustCompile(`(PayloadUUID[ \t]*\r?\n[ \t]*)([^<]*)()`) reUUIDValue = regexp.MustCompile(`[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}`) ) // uniqueProfileIdentity 把 mobileconfig 里的 PayloadIdentifier/PayloadUUID 改写成按 configFile 派生的值。 // conf 下各 app 的模板是互相复制出来的,标识符完全相同,而 iOS 按顶层 PayloadIdentifier 判重: // 相同即替换已安装的描述文件,用户装了第二个 app 第一个就没了。 // 按 configFile 派生可保证各 app 互不覆盖;同一 app 多次下载得到相同标识符, // 重复安装只覆盖自己,不会在桌面堆出一排图标。 func uniqueProfileIdentity(data []byte, configFile string) []byte { if len(data) == 0 { return data } derive := func(i int) string { return uuid.NewSHA1(profileIDNamespace, []byte(fmt.Sprintf("%s#%d", configFile, i))).String() } n := 0 data = rePayloadUUID.ReplaceAllFunc(data, func(m []byte) []byte { g := rePayloadUUID.FindSubmatch(m) v := derive(n) n++ return []byte(string(g[1]) + v + string(g[3])) }) // 第 i 个 PayloadIdentifier 与第 i 个 PayloadUUID 共用派生值,保留原前缀(luli. / com.apple.webClip.managed.) i := 0 data = rePayloadIdent.ReplaceAllFunc(data, func(m []byte) []byte { g := rePayloadIdent.FindSubmatch(m) v := derive(i) i++ val := g[2] if reUUIDValue.Match(val) { val = reUUIDValue.ReplaceAll(val, []byte(v)) } else { val = []byte(string(val) + "." + v) } return []byte(string(g[1]) + string(val) + string(g[3])) }) return data } func GetAppConfig(appId int32) (origin, target, config string) { endpoint := statg.GlobalAppIdEndpoints[appId] if endpoint != nil { origin = endpoint.H5Origin target = endpoint.H5Target config = endpoint.H5Config } return } // PageButtonClick 落地页点击下载返回最新的下载链接 func GetH5Config(ctx context.Context, req *proto.H5ConfigReq, userAgent string, ip string) ([]byte, string) { req.DC = utils.RepairDC(req.DC) downloadInfo := &downloadinfo.DownloadInfoModel{ AppId: req.AppId, IP: ip, PC: req.PC, DC: req.DC, SysType: "light", PackageType: versionMod.Light, PeerIP: ip, Domain: req.Domain, UserAgent: userAgent, CreatedAt: time.Now(), } cacheSer.Writers.LandPageClickWriter.Add(downloadInfo) origin, target, config := GetAppConfig(req.AppId) log.Info("GetH5Config", log.Any("origin", origin), log.Any("target", target)) if origin == "" || target == "" || config == "" { return nil, "madou.mobileconfig" } return GenerateConfigFile(origin, target, config, req.DC, req.PC) } func readFile(filePath string, origin, target string) ([]byte, bool, error) { var err error var data []byte cacheKey := fmt.Sprintf("h5config#%s", filePath) if cache, exists := cacheSer.Cache.Get(cacheKey); exists { if item, ok := cache.([]byte); ok { data = item } } if data == nil { data, err = os.ReadFile(filePath) if err != nil { return nil, false, err } cacheSer.Cache.Set(cacheKey, data, 0) } reader := bufio.NewReader(bytes.NewReader(data)) needHandle := false output := make([]byte, 0) for { line, _, err := reader.ReadLine() if err != nil { if err == io.EOF { return output, needHandle, nil } return nil, needHandle, err } if ok, _ := regexp.Match(origin, line); ok { reg := regexp.MustCompile(origin) newByte := reg.ReplaceAll(line, []byte(target)) output = append(output, newByte...) output = append(output, []byte("\n")...) if !needHandle { needHandle = true } } else { output = append(output, line...) output = append(output, []byte("\n")...) } } return output, needHandle, nil } func GetH5ConfigV2(ctx context.Context, req *proto.H5ConfigReq, ip string) proto.H5ConfigV2Resp { if req.AppId == 694 { return proto.H5ConfigV2Resp{ URL: "http://awll.yangjiaolian.top/91seman.mobileconfig", } } return proto.H5ConfigV2Resp{} }