- 新增 advitise v2: info/location/bind/click-stat/day-click-stat 模型与 对应的 service、api、router、proto 定义 - models/index.go 注册 5 个新模型的索引初始化 - 鉴权白名单: /rsdownload/ 改为 /download/,放行 advitise v2 的 get/click 接口 - statg/config: proxyUrl 增加 fileRes1 - common/genId: lid 生成逻辑补充 - 新增 91fans / huangguo 的 mobileconfig 描述文件 - 新增 deploy/nginx/cpcdownload.mfcg.me.conf 网关配置 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
90 lines
3.0 KiB
Go
90 lines
3.0 KiB
Go
package versionApi
|
|
|
|
import (
|
|
"net/http"
|
|
"stat/common/stdLog"
|
|
"stat/service/cpcDownloadCfgSer"
|
|
"stat/service/versionSer"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// CpcDownload 双端下载
|
|
// @Tags 双端下载
|
|
// @Summary 双端下载
|
|
// @Description 记录下载历史。android:302跳转到下载地址(优先按appId+channel取配置,否则取最新);ios:返回mobileconfig配置文件
|
|
// @Produce octet-stream
|
|
// @Param appId path int true "产品AppId"
|
|
// @Param channel path string true "渠道(json字符串)"
|
|
// @Param tid query string false "埋点traceID"
|
|
// @Success 200 {file} file "ios返回mobileconfig文件"
|
|
// @Success 302 {string} string "android跳转下载地址"
|
|
// @Failure 400 {string} string "失败"
|
|
// @Router /download/{appId}/{channel} [get]
|
|
func CpcDownload(ctx *gin.Context) {
|
|
// 第一个参数 appId
|
|
appId, err := strconv.Atoi(ctx.Param("appId"))
|
|
if err != nil {
|
|
stdLog.ServerJSON(ctx, stdLog.ParamError, err.Error())
|
|
return
|
|
}
|
|
// 第二个参数 channel(渠道,json字符串)
|
|
channel := ctx.Param("channel")
|
|
|
|
userAgent := ctx.Request.Header.Get("User-Agent")
|
|
isIos := strings.Contains(userAgent, "iPhone") || strings.Contains(userAgent, "iPad")
|
|
|
|
// 记录下载历史(双端都记)
|
|
versionSer.RecordCpcDownload(ctx, isIos, int32(appId), channel, ctx.ClientIP(), userAgent)
|
|
|
|
// ios: 优先按appId和渠道查询双端配置的落地页,配置了落地页则301跳转,否则返回mobileconfig配置文件
|
|
if isIos {
|
|
landingPage := cpcDownloadCfgSer.GetLandingPage(ctx, int32(appId), channel)
|
|
if len(landingPage) > 0 {
|
|
// 落地页未带dc参数时,拼上当前渠道
|
|
if !strings.Contains(landingPage, "dc=") {
|
|
if strings.Contains(landingPage, "?") {
|
|
landingPage = landingPage + "&dc=" + channel
|
|
} else {
|
|
landingPage = landingPage + "?dc=" + channel
|
|
}
|
|
}
|
|
ctx.Redirect(http.StatusMovedPermanently, landingPage)
|
|
return
|
|
}
|
|
cfg := versionSer.IosMobileconfigByAppId(int32(appId))
|
|
if cfg == nil {
|
|
stdLog.ServerJSON(ctx, stdLog.Failure, "ios config不存在")
|
|
return
|
|
}
|
|
pc, dc, _ := versionSer.ParseChannel(channel)
|
|
writeMobileconfig(ctx, cfg, dc, pc)
|
|
return
|
|
}
|
|
|
|
// android: 优先按appId和channel查询配置的下载链接,没有则使用默认最新下载地址
|
|
downloadUrl := cpcDownloadCfgSer.GetDownloadUrl(ctx, int32(appId), channel)
|
|
if downloadUrl == "" {
|
|
downloadUrl = versionSer.LatestAndroidDownloadUrl(int32(appId))
|
|
}
|
|
if downloadUrl == "" {
|
|
stdLog.ServerJSON(ctx, stdLog.Failure, "android下载地址不存在")
|
|
return
|
|
}
|
|
ctx.Redirect(http.StatusFound, downloadUrl)
|
|
}
|
|
|
|
// writeMobileconfig 将 mobileconfig 文件以附件形式写入响应
|
|
func writeMobileconfig(ctx *gin.Context, cfg *versionSer.IosMobileconfigCfg, dc, pc string) {
|
|
data, fileName := versionSer.BuildMobileconfig(cfg, dc, pc)
|
|
if fileName == "" {
|
|
fileName = "madou.mobileconfig"
|
|
}
|
|
ctx.Header("Content-Type", "application/octet-stream")
|
|
ctx.Header("Content-Disposition", "attachment; filename="+fileName)
|
|
ctx.Header("Content-Transfer-Encoding", "binary")
|
|
_, _ = ctx.Writer.Write(data)
|
|
}
|