# 客户端项目第二版注意事项 **Repository Path**: SuiXin1122/flutter_withdraw_question ## Basic Information - **Project Name**: 客户端项目第二版注意事项 - **Description**: xxxxxxxxxxxxx - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-01-10 - **Last Updated**: 2025-09-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### 注意事项 以下内容参考项目为`遇米倒计时-ios`。接口名均为去掉应用标识,实际使用需根据实际情况修改。 #### 接口域名 项目采用网关转发方式调用接口。即一个网关域名拼接对应服务地址再拼接对应接口的方式。 例如登录接口: ``` http://abcdgg.99tok.com/youxin/usermanger/apimyyxios/loginmyyxios/phonemyyxios ``` 其中网关域名为`http://abcdgg.99tok.com` 服务地址为`youxin/usermanger(用户服务)` 登录接口为`apimyyxios/loginmyyxios/phonemyyxios`,接口中的`myyxios`为项目独有的接口标识,单项目唯一。 以上三者项目第一版找服务端获取。 #### 头参数加密 将所有的头参数通过json组合成一个字符串,然后进行aes加密(加密key找负责人拿),加密后的字符串放到头参数的a变量名称下,同时添加appid和b=1的参数。 加密后的头参数列表为: ``` appid: appid a: 加密串 b: 1 (固定) ``` #### 添加垃圾代码 **_ios原生应用找一下没用的第三方库加进去,占比大概在包体的20%左右,主要是防止我们的应用代码太相似,多加点代码降低一点重复率,同一个苹果账号下用同一份第三方库即可。_** #### 友盟初始化 ``` let config = UMAPMConfig.default() config.crashAndBlockMonitorEnable = true; config.launchMonitorEnable = true; config.memMonitorEnable = true; config.oomMonitorEnable = true; config.networkEnable = true; config.javaScriptBridgeEnable = true config.pageMonitorEnable = true UMCrashConfigure.setAPMConfig(config) UMConfigure.initWithAppkey(友盟KEY, channel: 渠道) ``` 当前已登录时,打开app时调用,未登录时在登录成功时调用。友盟KEY找负责人。 #### appid、appcode、servername、加密key 项目创建时找负责人或者产品拿。 #### oaid(头参数中oaid) 传IDFA,未授权时传空。 #### idfv(头参数中idfv) 传IDFV。 #### kpaid(头参数中kpaid) 获取方法查看归因小节。 #### cid(头参数中randomImei) 获取ios设备的uuid,获取成功后使用拼上cid的字符(cid+uuid)。获取失败则生成一个`uuid`过滤`-`得到一串字母数字的内容,拼上cid的字符(cid+uuid)。存储本地,头参请求带上,头参数字段名`randomImei`。 #### appNameMark(头参数appNameMark) 用于服务端巨量买量使用,内容使用项目名称全拼缩写,如木鱼有心使用myyx或者muyuyouxin(推荐,防止重复)。 #### packagename(头参数packagename) 应用包名 #### 设备ID(头参数中imei) 用户授权获取设备信息时,获取当前设备`idfa`,如果当前获取到的`idfa`为`00000000-0000-0000-0000-000000000000`,则使用cid。 未授权使用cid。 #### 修改渠道 在请求的头参数中名称为`appStore`,默认`ios`(特殊应用(情绪戒烟等)使用`ios_渠道标识`)。 ##### 1.接口返回渠道 接口:`/app/oceanks/appstore` app打开并获取到网络权限时,首先调用该接口,服务端返回对应的渠道名(存在返回空),将渠道名存储至本地并修改请求头参数中的appstore值。登录成功时也需要调用一次。 ##### 2.复制粘贴修改渠道 接口`api/login/decappstorecode` app打开并获取到网络权限时,先通过字典`if_zhantie`内容判断(判断返回值是否为1)是否需要启用该功能。启动该功能时,获取剪贴板内容,判断是否包含`APPSTORE`,包含时调用接口将剪贴板内容传给后台,参数名为`key`,接口返回内容`data`中参数名为`modifyAppStore`的值为需要修改的渠道名。 ##### 3.测试修改渠道 在登录页通过重复点击等操作,将渠道修改为变现渠道。 ##### 4.优先级 测试修改渠道 > 复制粘贴 > 接口返回渠道 #### 设备上报 接口:`api/app/resister` 能正确获取到设备信息(IDFA)时,调用该接口用于服务端设备注册。 #### 上下架(马甲包开关)判断 接口:`api/app/init` 该接口的`data`内的`appMode`参数,值为`Debug`和`Beta`。当值为`Beta`时,显示变现版内容,包括金币,任务,奖励等。 **_调用该接口前需先调用接口返回渠道(`/app/oceanks/appstore`)确认渠道无误。_** 调用时机:启动(欢迎)页、登录成功之后、IDFA授权成功时。 **注:如当前是马甲包,通过登录成功或者IDFA授权成功重新请求接口时,如果接口返回变现版。未登录时需显示登录引导红包,已登录时需更新首页tabbar的赚钱菜单显示。** #### 添加闲玩淘金 ##### 淘金 将项目中的`TJSDK.framework`和`TJSDKBundle.bundle`导入到项目中。`info.plist`中添加白名单 `LSApplicationQueriesSchemes`添加`mqq`。 ``` //在`xxx-Bridging-Header.h`文件中添加 #import //初始化sdk,id和key通过申请获得 TJSDK.registerClient(withAppId: "id", appKey: "key") //设置用户id,已登录时初始化一起调用,未登录时登录成功调用 TJSDK.setAppUserId(id) //打开淘金内容页面 TJSDK.present(vc) ``` ##### 闲玩 `ios端`: 提供一个ViewController,内部为一个`webview`,用于加载闲玩h5地址。webview提供`Browser`方法供js调用。参考`YMXWVC.swift`文件。 ``` let idfa = UserDefaults.standard.string(forKey: "YMIDFAKey") ?? "" let ptype = "1" // 写死 let appid = "" // 后台申请 let appkey = "" // 后台申请 let userid = YMUserData.userData()?.memberId ?? "" // 用户id let xwver = 2 // 写死 let keycode = YMDeviceTool.md5(with: "\(appid)\(idfa)\(ptype)\(userid)\(appkey)") let url = "https://h5.17xianwan.com/try/iOS/try_list_ios?ptype=1&deviceid=\(idfa)&appid=\(appid)&appsign=\(userid)&xwversion=\(xwver)&keycode=\(keycode)" let vc = YMXWVC() vc.urlStr = url vc.modalPresentationStyle = .fullScreen UIApplication.topViewController()?.push(vc: vc) ``` #### 设备归因 ##### 穿山甲 `ios端`: `pod`文件添加穿山甲归因第三方库。 ``` pod 'BDASignalSDK' pod 'Protobuf' ``` 添加后执行`pod install`。在`xxx-Bridging-Header.h`文件中添加: ``` #import "BDASignalManager.h" #import "BDASignalDefinitions.h" ``` 在`AppDelegate.swift`文件中进行sdk初始化: ``` BDASignalManager.enableDelayUpload() BDASignalManager.register(withOptionalData: nil) BDASignalManager.didFinishLaunching(options: launchOptions, connect: nil) BDASignalManager.startSendingEvents() ``` 在`Xcode`中添加`URL SCHEME`规则: 在 `Xcode` 中,选择你的工程设置项,选中`TARGETS`一栏,在`info`标签栏的`URL type`添加`URL scheme`,其中`Identifier`填写固定值`oceanengine/ads`,`URL schemes`填写对应的应用包名。 App打开首页时调用接口`app/oceanks/oceanios/`上报信息,传参`pkgName`为当前应用的包名。 ``` YMNet.sendNet("app/oceanks/oceanios/",["pkgName":对应应用的包名],headUrl: .user,hiddenToast: true) { data in} fail: { _, _ in} ``` ##### 快手 获取`kpaid` 获取设备的`启动时间(秒)`、`系统更新时间`、`文件初始化时间`,使用md5加密后通过`-`拼接。 参考项目中`YMDeviceTool.m`和`YMDeviceTool.h`文件,在`xxx-Bridging-Header.h`中添加引用: ``` #import "YMDeviceTool.h" ``` 添加完成后添加对应调用代码: ``` class func getKpaid() -> String { let bootTime = YMDeviceTool.bootTimeInSec() let getSysU = YMDeviceTool.getSysU() let fileInitTime = YMDeviceTool.fileInitTime() let params = "\(YMDeviceTool.md5(with: fileInitTime))-\(YMDeviceTool.md5(with: getSysU))-\(YMDeviceTool.md5(with: bootTime))" return params } ``` 在网络请求的头参数中添加上报这两个参数: ``` headers["idfv"] = idfv; headers["kpaid"] = kPaid; ``` #### 新手引导 ##### 首页大红包 用户未登录且应用处于上架状态时,显示大红包,引导用户登录。点击登录时判断用户是否授权设备信息,无授权时跳转授权引导页面,点击按钮跳转系统设置开启设备追踪。返回授权引导页时如果用户正常授权,则关闭页面跳转登录页面,反之留在引导页继续引导用户开启权限。 ##### 福利红包 打开app或从二级页或桌面回到首页时判断当前是否有引导或者弹窗,如果没有的话,判断上一次显示时间,超过一小时时显示福利红包,点击跳转提现。 ##### 提现余额不足判断 余额不足时,先判断当前金币是否超过100,大于100时提醒用户兑换金币。不大于时判断当前历史金额是否超过2元,不超过提示跳转完成新手任务。 ##### 弹窗添加广告 自定义view方式添加弹窗,弹窗view需添加到UIViewController中。弹窗如果是在Tabbar中的Controller中弹窗,需将弹窗view添加到Tabbar中显示。 添加广告View: ``` 声明广告控件及观察者 var observer: Any? private lazy var adView: AdShowView = { let v = AdShowView(frame: CGRect(x: 0, y: 0, width: ScreenWidth - 40, height: 150), rootViewController: self.vc!, name: "\(type(of: vc.self))", area: "tc", isDialog: true) return v }() 添加控件及约束(bgView为弹窗主体内容) if vc != nil { self.addSubview(adView) adView.snp.remakeConstraints { make in make.left.right.equalTo(bgView) make.top.equalTo(bgView.snp.bottom).offset(20) make.height.equalTo(150) } } 添加观察者(广告展示移动内容弹窗) if vc != nil { observer = NotificationCenter.default.addObserver( forName: NSNotification.Name("ad_show"), object: nil, queue: .main ) { _ in print("dialog ad show NotificationCenter") self.bgView.snp.remakeConstraints { make in make.centerX.equalToSuperview() make.centerY.equalToSuperview().offset(-85) make.size.equalTo(CGSize(width: 306 * widthRation, height: 185 * widthRation)) } } } 关闭弹窗时移除观察者及控件 self.removeFromSuperview() if let observer = observer { NotificationCenter.default.removeObserver(observer) } ``` 测试多次弹窗加载广告是否多次调用观察者回调方法,正确情况是每次展示打印一次`dialog ad show NotificationCenter`。代码参考`YMTip.swift`。`Tabbar`内`Controller`展示弹窗可参考`YMTip`内`getVC`方法。 #### 广告展示规则 ##### 固定类 固定类广告为四个广告位(SY_KS,SY_CSJ,SY_GDT,SY_BAIDU)置于页面控件最底层同时展示,在广告上一层覆盖遮挡控件。正常情况下需添加的页面有:`首页底部菜单对应的页面`、`新手任务`,`提现页`,`新手攻略`。 **其他页面如遇到底部添加广告导致页面变形或者内容展示问题可根据实际情况添加该类广告。** ##### 权重类 在对话(弹窗)框中添加广告,位置于内容框下方(适量间距),在广告弹出时需将内容框往上顶(无广告,内容弹窗位于页面中间,有广告,内容弹窗和广告位于页面中间)。广告位`tc(弹窗)`。 ##### 轮播类 项目二级页面中添加该类广告,除登录相关、注销相关、隐私、用户协议页面外,均需要添加该类广告。一般位置置于页面底部,如页面内容需滑动,滑动内容区域为顶部到广告的上方,滑动时广告不动。广告位有`pt(拼图)`、`zp(转盘)`、`sy(首页)`等,默认可以用`sy`。 ##### 广告注意事项(重要) 1.广告正常展示到页面时,点击看能否正常进行广告跳转,没有跳转则广告接入错误。 2.观察是否轮播正常,通过日志查看广告展示的provide类中的reload方法日志。正常情况下能进行轮播的广告只有当前页面的广告,如首页有广告a,跳转二级页有一个广告b,停留在当前页面,只有b可以加载广告,a则会继续轮播等待页面显示再展示广告。 **_3.IOS原生项目:所有页面都需继承`BaseViewController`。通过`addHideAds`添加固定类广告。通过添加`adView`控件展示权重轮播类广告(一般展示在页面底部)_** **_注意:如果`TabbarController`页面里的`controller`还嵌套了`controller`并且继承于`BaseViewController`,这时内部的`controller`需排除`AdUtil.currentResumeVcName = "\(type(of: self))"`的赋值,参考`YMBaseVC`的`isPage`方法。_** 4.广告展示检查: 首先看接口是否返回对应的广告配置内容 接口地址:`/api/ad/allad/v2` 接口如果请求失败,success字段返回false等。存在两种情况,一当前返回的数据源因为错误码导致在冷却中,当前设备当前用户限制不请求该数据源广告、二后台没有配置项目相关信息,如广告id没配置等。 接口请求正常后,广告如果还没有出现,在该接口后面查看日志,看是否有调用到请求广告的方法,看回调是否有正常执行。(可根据代码调用链路查看具体的日志信息) 5.首页加载两次问题 `AppDelegate`初始化`JGZTabBarController.init()`,在`JGZAppManage`的`appInit`中又初始化了一次`JGZTabBarController.init()`并且没有销毁首次加载的首页,导致首页加载了两次,广告初始化两次,首次加载的首页会被第二次加载的首页覆盖导致广告展示请求成功但是展示失败。