问题背景
使用 opencode 时,退出后会提示一句:
1 |
|
但如果终端窗口关得太快,或忘记复制,这个会话 ID 就丢了,导致无法恢复之前的会话。
1、异常数据的发现及补充、日志分类优化
2、日志数据的文件保存、滚动存储
3、日志文件上传
4、日志回捞
| 序号 | 目标(Target) | ||
|---|---|---|---|
| 1 | app | ||
| 2 | sdk | 三方库 | |
| 3 | h5 | 网页 |
| 序号 | 类型 | 描述 | |
|---|---|---|---|
| 1 | api_app | app中的网络请求 | |
| 2 | api_app_cache | app中的网络请求的网络缓存请求 | |
| 3 | api_buriedPoint | 埋点的网络请求 | |
| 4 | sdk_other | sdk的各种事件(初始化等) | |
| 5 | sdk_api | sdk中的网络请求 | |
| 6 | dart | 语法 | |
| 7 | widget | 视图(布局像素越界等) | |
| 8 | click_other | 点击、 | |
| 9 | click_share | 分享 | |
| 10 | native_route | 路由/跳转 | |
| 11 | h5_route | 与网页跳转有关 | |
| 12 | h5_js | js交互 | |
| 13 | monitor_network | 监控:网络类型变化 | |
| 14 | monitor_lifecycle | 监控:生命周期变化 | |
| 15 | buriedPoint_other | 埋点数据生成等 | |
| 16 | im | IM | |
| 17 | heartbeat | 心跳 | |
| 18 | other | 其他 |
| 序号 | 目标(Level) | 描述 | |
|---|---|---|---|
| 1 | Normal | 正常信息(目前用于请求开始) | |
| 2 | Success | 成功信息(目前用于请求结束:成功) | |
| 3 | Warning | 警告信息(目前用于请求结束:报错) | |
| 4 | Error | 错误日志(目前用于请求结束:失败) | |
| 5 | Dangerous | 危险(处理白屏等) | 一般会进行额外的埋点 |
| 序号 | 列表 | 标志 | 包含 |
|---|---|---|---|
| 1 | 全部 | all | 所有 |
| 2 | 警告 | warning | 所有的警告 |
| 3 | 错误 | error | 所有的错误 |
| 4 | 接口 | api | api_app、api_cache(不包括sdk_api、api_buriedPoint) |
| 5 | 点击 | click | click_share、click_other、h5_js |
| 6 | 路由 | route | navite_route、h5_route |
| 7 | 网页 | H5 | h5_route、h5_js |
| 8 | sdk | sdk | sdk_api、sdk_other |
| 9 | code | code | dart、widget |
| 10 | 埋点 | buriedPoint | api_buriedPoint、buriedPoint_other |
| 11 | 监控 | monitor | monitor_lifecycle、monitor_network |
| 12 | 其他 | other | other |
| 13 | api结果 | api_result | type=api_app & level != Normal |
| 14 | im | im | 本地缓存消息、历史消息、收到的消息等 |
| 15 | 心跳 | heartbeat |
要收集的数据已在上述《1.2、日志类型》中说明。
详见上述 《3、日志整合归类》中的”路由”
| 序号 | 关键字 | 场景 |
|---|---|---|
| 1 | break | for 、 swtich |
| 2 | return |
| 序号 | 行为 | 用途的场景说明 |
|---|---|---|
| 1 | 用户的资源选择 | 判断选择的本地资源是否上传到错误的存储桶 |
写入xlog文件中。
1、本地日志文件结构
1 | -- Document |
2、日志保留清理的配置信息(摘自/同 下文的日志接口中的《日志文件配置信息》)
序号 说明 字段 示例 接口回值1 最多保留几个天的日志目录 maxDayDirCount 7 接口回值2 最多每天保留几个文件 maxDayFileCount 10 接口回值3 每个日志文件超过多少后创建新文件 perFileMaxMB 5
3、清理方案
清理时机:在切换前后台的时候,
要增加的日志记录,如果添加后会超过日志文件的大小,则使用新文件写入。
细节点:
1、日志文件的大小判断,不要每次都去读取文件,而是记录住大小变化
2、日志不要有一条写一条,而是使用写入缓冲区+定时器,5s尝试写入一次
1、加密:
写入日志字符串时候,对编码后的字节数据,额外增加一个三位随机值。
1 | /// 使用 GZip 压缩算法将字符串进行压缩,并返回压缩后的字节数据(List<int> 类型)。 |
2、解密:
使用python脚本对log文件进行解密
1 | import sys |
以用户1000001在2020-01时候上传声音文件 beautiful_scenery.aac为例,其路径完整示例如下:
1 | https://media.xxx.com/app1_test1/audio/1/1000001/2020-01/beautiful_scenery.aac |
要上传的存储桶:
| 序号 | 说明 | 参数 | 约定值示例 |
|---|---|---|---|
| 1 | 存储桶region regionGetFunction |
网络环境 | ap-shanghai |
| 2 | 存储桶bucket bucketGetFunction |
网络环境 | xxx-pro-image-1302324914 |
| 3 | 上传成功后,桶的映射路径值 cosFileUrlPrefixGetFunction |
1、图片桶:https://images.xxx.com/ 2、媒体桶:https://media.xxx.com/ 3、日志桶:https://static.xxx.com/ |
|
| 4 | 要上传到桶的哪个相对路径下 cosFileRelativePathGetFunction 上传成功后完整路径=以上桶值+此相对路径 |
见下文 | 见下文 eg:app1_test1/audio/1/1000001/2020-01/beautiful_scenery.aac |
《要上传到桶的哪个相对路径下 cosFileRelativePathGetFunction 》的文件路径分批说明:
| 序号 | 层次说明 | 层次值算法 | 层次值描述 |
|---|---|---|---|
| 1.1 | 存储桶里相对路径的前缀 | app标志_环境标志 eg:app1_pro、app1_test1、app1_dev1 |
文件区分 |
| 1.2 | 是否在存储桶里进行进一步路径区分(可选) | 根据场景决定是否对桶进行进一步分类。 if ( mediaType == xxx ) { if(mediaScene == yyy) { } } |
如 1、都是图片,但自拍图片要求独立出来 2、都是多媒体,但音视频要区分开 |
| 2.1 | 用户1级 | int.parse(uid) % 1000; // 取余数 | 用户名取余,有效减少同层上文件夹个数 |
| 2.2 | 用户2级 | uid | 用户名 |
| 3 | 年月 | DateTime.now().toString().substring(0, 7); | 年-月 |
| 4 | 文件名 | ${fileOriginName}_ ${DateTime.now().microsecondsSinceEpoch} .$fileExtensionType |
文件名 |
附1:MediaType 媒体类型
| 序号 | UploadMediaType 媒体类型 | 描述 | |
|---|---|---|---|
| 1 | unkonw | 未知(默认值) | |
| 2 | image | 图片 | |
| 3 | audio | 音频 | |
| 4 | video | 视频 | |
| 5 | xlog | 日志文件 |
附2:MediaScene场景 说明
| 序号 | UploadMediaScene场景 | 描述 |
|---|---|---|
| 1 | unkonw | 未知(默认值) |
| 2 | selfie | 自拍(安全等级较多) |
| 3 | im | 会话聊天 |
| 4 | live | 直播 |
1、是否记录日志的开关及其配置信息
| 序号 | 说明 | 字段 | 示例 |
|---|---|---|---|
| 网页配置1 | 是否开启日志记录 | isLogOn | 1:开启、其他:关 |
| 网页配置2 | 要开启日志的用户(灰度上线) (当且仅当开启上述日志记录时有效) 📢:未配置但开关为开时为全记录 |
logUserids | [“101”, “102”] |
| 网页配置3 | 要开启日志的设备(灰度上线) (当且仅当开启上述日志记录时有效) 📢:未配置但开关为开时为全记录 |
logDeviceIds | [“udid001”, “udid002”] |
| 接口回值1 | 是否进行日志记录(灰度上线) | needLog | 1:记录、其他:不记录 |
2、不需要记录什么日志信息
| 序号 | 说明 | 字段 | 示例 |
|---|---|---|---|
| 接口回值1 | 不需要记录的日志类型 | ignoreLogLevel | [“Normal”, “Success”] |
| 接口回值2 | 不需要记录的日志等级 | ignoreLogType | [“api_buriedPoint”, “buriedPoint_other”] |
3、日志文件配置信息
| 序号 | 说明 | 字段 | 示例 |
|---|---|---|---|
| 接口回值1 | 最多保留几个天的日志目录 | maxDayDirCount | 7 |
| 接口回值2 | 最多每天保留几个文件 | maxDayFileCount | 10 |
| 接口回值3 | 每个日志文件超过多少后创建新文件 | perFileMaxMB | 5 |
4、日志文件问题回滚
| 序号 | 说明 | 字段 | 示例 |
|---|---|---|---|
| 网页配置1 | 强制删除什么时间前的日志 | removeLogBeforeDate | 2020-12-31 |
| 网页配置2 | 强制删除哪个用户的日志 | removeUserids | [“101”, “102”] |
| 网页配置2 | 强制删除哪个设备的日志 | logDeviceIds | [“udid001”, “udid002”] |
| 接口回值1 | 强制删除什么时间前的日志(不删除时空) | removeLogBeforeDate | 2020-12-31 或 空值 |
接口名定义:
1 | function getLoggerConfig(userid, deviceId) |
接口结果的json示例如下:
1 | { |
1、日志回捞
| 序号 | 说明 | 字段 | 示例 |
|---|---|---|---|
| 网页配置1 | 要回捞日志的用户 | salvageUserids | [“101”, “102”] |
| 网页配置2 | 要回捞日志的设备 | salvageDeviceIds | [“udid001”, “udid002”] |
| 网页配置3 | 日志回捞的开始时间 | salvageStartDate | 2020-01-01 |
| 网页配置4 | 日志回捞的结束时间 | salvageEndDate | 2020-01-07 |
| 接口回值1 | 此用户此设备日志回捞的开始时间 | salvageStartDate | 2020-01-01 |
| 接口回值1 | 此用户此设备日志回捞的结束时间 | salvageEndDate | 2020-01-07 |
2、日志文件问题回滚
| 序号 | 说明 | 字段 | 示例 |
|---|---|---|---|
| 网页配置1 | 强制删除什么时间前的日志 | removeLogBeforeDate | 2020-12-31 |
| 网页配置2 | 强制删除哪个用户的日志 | removeUserids | [“101”, “102”] |
| 网页配置2 | 强制删除哪个设备的日志 | logDeviceIds | [“udid001”, “udid002”] |
| 接口回值1 | 强制删除什么时间前的日志(不删除时空) | removeLogBeforeDate | 2020-12-31 或 空值 |
接口名定义:
1 | function getLoggerSalvageAndRevert(salvageUserids, salvageDeviceIds) |
接口结果的json示例如下:
1 | { |
用途:日志文件上传到cos后同步给后台,后台数据库记录每个用户,每个设备都捞到了什么数据。
接口名定义:
1 | function addLoggerFile(userid, deviceId) |
接口结果的json示例如下:
1 | { |
1、后台通过接口返回要回捞的信息。
2、前端用户在下次使用,收到后台信息时候,进行本地日志的上传。在腾云存储桶能收到对应日志,即代表成功。
灰度方案:请参照 灰度系统 。(附:日志记录开关里目前已有灰度策略。)
目的:避免功能异常,出现集体性问题。
就是**”基础库”或者“基础组件”,**意思是把代码重复的部分提炼出一个个组件供给功能使用。
使用:Dialog,各种自定义的UI控件、能在项目或者不同项目重复应用的代码等等。
目的:复用,解耦。
依赖:组件之间低依赖,比较独立。
架构定位:纵向分层(位于架构底层,被其他层所依赖)。
就是**”业务框架”或者“业务模块”**,也可以理解为“框架”,意思是把功能进行划分,将同一类型的代码整合在一起,所以模块的功能相对复杂,但都同属于一个业务。
使用:按照项目功能需求划分成不同类型的业务框架(例如:注册、登录、外卖、直播…..)
目的:隔离/封装 (高内聚)。
依赖:模块之间有依赖的关系,可通过路由器进行模块之间的耦合问题。
架构定位:横向分块(位于架构业务框架层)。
其实组件相当于库,把一些能在项目里或者不同类型项目中可复用的代码进行工具性的封装。
而模块相应于业务逻辑模块,把同一类型项目里的功能逻辑进行进行需求性的封装。
1、产生背景:
所有模块代码都编写在一个项目中,在项目越来越大后,测试/使用某个模块或功能,需要编译运行整个项目,麻烦。
2、组件化思路:
将每个模块作为一个组件,加一个中间层来协调各个模块间的调用,所有的模块间的调用都会经过中间层中转。(只让其他模块对中间层产生耦合关系,中间层不对其他模块发生耦合。)
3、组件化好处:
业务划分更佳清晰,新人接手更佳容易,可以按组件分配开发任务。
项目可维护性更强,提高开发效率。
更好排查问题,某个组件出现问题,直接对组件进行处理。
开发测试过程中,可以只编译自己那部分代码,不需要编译整个项目代码。
方便集成,项目需要哪个模块直接通过CocoaPods集成即可。
完整的项目:https://github.com/dvlproad/033-Data-Notification-iOS
启动时候注册关系表,执行时候遍历关系表
1 | // 核心思想1:注册,以统一管理,后续调用时候,从注册表中查询并执行对应的操作 |
缺点:入参,取参不明显。
哈希的本质 哈希冲突的解决:链地址法和开放寻址法。
问:使用多代理模式实现一个类似通知的功能,请问应该用什么来什么结构来存储代理和类的结构?
答:在多代理(Multiple Delegates)模式下,要存储多个代理对象,并且这些代理对象通常是弱引用,以防止循环引用。实现一个类似通知的功能,可以考虑使用 NSPointerArray 或 NSMapTable 来存储代理对象。如果你只是想管理多个代理对象,NSPointerArray 是更简单的选择。如果你需要将某个对象(如 UIViewController)与多个代理进行关联,NSMapTable 更合适,所以我们选择 NSMapTable 。
将要执行的方法统一放到类中,通过protocal或者类。
1 | // 根据 protocal 查找 class |
模块协议拆分为多个子协议,用户
网络处理协议、数据管理协议(单独)、用户界面更新协议
改进思想
1 | // 1、发起请求(LoginViewController) |
上述代码还可以演变成实现通知的一种形式(当注入的类中,有多个类都遵循了(实际上不应该出现)模块协议时候)
1 | // 1、发起请求(LoginViewController) |
下列图片来源于 《架构分层.graffle》 中的【三、模块间设计】
跳转的使用示例:
通知的实现示例:
跳转 + 数据变化后内部通知的实现示例:
项目示例:CJStandardProject 中 CTMediator+CJDemoModuleMain.h
核心思想:直接执行,让系统通过反射来找到要响应事件的类和方法
1 | void exec(target, action, params) { |
实现示例:


通过前一个页面的已获得数据,对所进入的新页面中的数据进行预填充。
eg1:商品列表 —> 商品详情:使用数据携带
eg2:愿望星count个数:通过count的0与非0,知晓所进入的页面初始状态更有可能是哪种状态
1、如果所进入的页面没有缓存数据,则携带的数据在进入的时候直接使用,后台接口返回实时数据后,再更新
2、如果所进入的页面有缓存数据,则携带的数据只能给缓存数据,而不能是后台接口返回的实时数据
通过缓存框架,将数据缓存起来(key需携带uid),下次界面展示时候,优先从缓存中获取。
附:缓存框架详见:flutter_cache_kit使用文档
建立Service层,管理用户所有数据的变动。
好处:与普通数据的缓存相比,能够在将来增加数据变动时候,通过本地通知系统,告知相关页面更新相应数据,而不用等到重新下载后才能显示已知道会更新的数据。
使用要点:
修改的时候,同步数据;下次界面需要数据,优先从用户管理服务中获取初始数据
eg1:用户愿望单收藏、商品收藏、品牌收藏、足迹数据
eg2:用户会员中心数据
如果没有从前一页携带数据过来,则使用与产品约定的默认数据来加载。
1 | if (携带数据) return 携带的.orderCount; |
对携带的数据与默认数据中相同的对象,使用携带的数据替换掉,一起整合成初始默认数据(携带的数据优先级高)。
页面处理优化及处理过程中的内容(图片)加载优化如下图所示:
图片来源可查看 《app启动与页面加载.graffle》中的【二、页面加载】版面
从上图中,可以看出弱网情况下,我们可以针对不同的网络:做不同超时,重试设置,以及取不同质量的图片数据。
图片优化,请查看我的另一篇文章:图片框架.md
图片高磁盘占用,请查看我的另一篇文章:《高磁盘占用的排查与优化.md》
api数据缓存,请查看我的另一篇文章:《网络框架.md》
弱网优化,请查看我的另一篇文章:《弱网优化空间探索.md》
架构模式:架构模式的出现是为了管理复杂的应用程序,这样可以在一个时间内专门关注一个方面。例如,您可以在不依赖业务逻辑的情况下专注于视图设计。同时也让应用程序的测试更加容易。同时也简化了分组开发。不同的开发人员可同时开发视图、控制器逻辑和业务逻辑。我们经常说的MVC架构、MVVM架构属于此类。
框架:这个最好理解了,通常是代码重用。框架与设计模式的概念容易弄混,两者有相似之处,但却有着根本的不同。设计模式是对在某种环境中反复出现的问题以及解决该问题的方案的描述规范,它比框架更抽象;框架为已经解决问题的具体实现方法,能直接执行或复用;设计模式是比框架更小的元素,一个框架中往往含有一种或多种设计模式。Xcode自带的Foundation、UIKit,以及我们经常使用的AFNetworking,MJExtension,SVProgressHUD就属于这一类。
设计模式:设计模式可以通俗的理解为实现/解决某些问题,而形成的解决方案规范。增加代码的可重用性,让代码能更容易理解和可靠。我们通常说所的代理模式、迭代器模式、策略模式就属于这一类。对各种设计模式的了解可以帮助我们更快的解决编程过程中遇到的问题。
三者关系:架构(动词)>框架>设计模式。
软件通过架构,可以设计出很多不同的框架。在一个框架中,也可以使用很多的设计模式。设计模式不是哪儿哪儿都可以用的,只有当出现了某一特定的问题时,才利用设计模式去解决。设计模式不是用的越多越好,在维护的时候,过多的设计模式会极大的增添维护成本。
1、架构的设计其实是为了更好的维护和迭代,而不是只考虑眼前当下的开发。
2、最基本的架构是MVC,试想下如果我一个应用的所有功能都是像”关于“功能的,那我还需要其他架构干嘛?显然MVC就已经很足够,而且还显得不冗余(这里指的是设计或者粒度不冗余)。
3、其他的架构都是从MVC演变而来。再讲其他架构的演变之前,我们先明确最原始的MVC代码是如何的。
①、首先M主要负责数据、V主要负责视图、C主要负责数据的视图显示。而这里的M是原始的瘦Model。
所以,我们拿常见的”我的”页面来举例,部分原始的MVC代码一般为如下样子:
1 | UserModel |
4、在MVC+瘦Model的演变下,还有MVC+胖Model
胖Model就是瘦Model+部分弱业务逻辑(这些弱业务重复一般都是经常出现,或者说是要求可复用性的,如根据枚举获取性别字符串)。它使得Controller可以从胖Model这里拿到数据之后,不用额外做操作或者只要做非常少的操作,就能够将数据直接应用在View上.
1 | UserModel |
5、有时候在MVC+瘦Model的演变下,不是转成MVC+胖Model,而是MVC+瘦Model+Helper
其实这个思路已经很接近MVP了、但是还差提点。MVP还需要为View提供数据
1 | UserModel |
6、关于MVC+瘦Model+Helper,还可以改为MVC+瘦Model+ViewModel
刚才的胖Model只从Controller移植走了一些简单的弱业务。
而ViewModel则干脆把数据的处理全部从Controller移植了出去。
1 | UserModel |
拿有数据操作的”购物车“页面来说,
假设其除了有网络数据请求外,还有本地数据库处理。(附:购物车是否需要本地数据库,个人认为是基于服务端的压力来考虑,不然一般是放在服务端合适点。压力设想如下,千万级用户,购物车加减商品频繁,假设放在后台,则操作和计算也就频繁,压力也就大了。如果放在前端,则后台只需要负责是否可加减商品的检验,而不用再建购物车数据表和计算价格等,压力就小了。)
每个页面一般都会有网络请求,这些网络请求最原始的时候,是放在ViewController,也就是MVC的C中。
为了避免MVC中的ViewController后期变得十分臃肿。我们肯定要在MVC的基础上额外增加一层来处理这个。
综合考虑后,我们选用MVCS来处理。
其中S代表着Store,且我们这里的S代表着是整个模块的S,而不是每个页面都有一个S。其下层又包含着Storage和Service,即 数据库DB部分 和 网络Network部分 。
Store类名,个人习惯是使用Manager来命令。如UserManager。

所以,最终我们选用的是 MVCS+MVVM 或者说 MVVM+S。
S层(service 层)提供一种和外界(比如远程服务 API 或文件系统)交互的独立机制。
MVVM+S 纵向的数据流就不用多说,横向的数据流,我们采用自定义的类系统通知。区别在于它有着系统Notification的一对多方便,又有着delegate的接口对接方便。

抛开数据共享问题(如每个请求都要带userToken),每个页面
有一个iOS的app的购物车功能,添加的商品会放到购物车中,可以在购物车中对该商品进行数量变更,变更后会重新计算价格。购物车可添加多种商品计算,请问这个计算应该是请求接口让后台计算,还是后台提供每个商品的单价(和折扣),然后前端计算?
购物车的价格计算方式选择 前端计算 还是 后端计算,取决于业务需求和系统设计,以下是两种方案的分析:
✅ 方案 1:前端计算(客户端计算总价)
方式:
• 后端提供每个商品的单价、折扣、活动信息等数据。
• 客户端在 数量变更 时,直接 本地计算 商品总价(单价 * 数量),无需请求后端。
适用场景:
✅ 前端交互体验要求高(如希望用户调整数量后,立即看到价格变更,无需等待网络请求)。
✅ 价格计算逻辑简单(单价 * 数量,没有复杂促销规则)。
✅ 减少服务器压力(减少频繁的请求)。
缺点:
❌ 价格可能不准确,如果 单价变更、促销活动 发生变化,用户的本地价格可能与后端不同。
❌ 容易被篡改,黑客可以修改前端逻辑,伪造价格(需要后端校验最终订单金额)。
✅ 方案 2:后端计算(每次变更数量都请求接口)
方式:
• 客户端每次数量变更时,向后端发送请求,后端返回最新的价格(包括折扣、活动计算)。
适用场景:
✅ 涉及复杂促销计算(如满减、阶梯折扣、优惠券等)。
✅ 商品价格变化频繁(避免用户本地计算价格不一致)。
✅ 安全要求高(防止价格被篡改,确保支付金额正确)。
缺点:
❌ 用户体验下降(每次调整数量都要请求后端,可能会有延迟)。
❌ 服务器压力增加(每次改数量都要请求后端)。
🚀 最优解:前端 + 后端结合
建议方案:前端预计算 + 后端校验
• 前端 先本地计算实时显示价格,让用户调整数量时立即看到变化(提升体验)。
• 提交订单时,后端最终计算总价,避免前端被篡改价格,确保安全。
• 定期刷新(比如每次进入购物车页面时请求一次后端最新价格),防止前端数据过期。
示例流程:
1. 用户点击 + / - 修改商品数量时,前端本地计算价格,界面立即更新。
2. 后台定期提供 最新单价、促销信息,前端数据保持同步。
3. 下单时,后端再次计算总价,如果价格被修改过(例如后台有最新优惠),前端更新价格,并提醒用户。
🎯 总结
| 方案 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| 前端计算 | 快速、减少网络请求 | 价格可能不准确、安全性较低 | 简单购物车、价格变动少 |
| 后端计算 | 确保价格准确、安全性高 | 影响体验、增加服务器压力 | 复杂促销、多变商品价格 |
| 前端 + 后端结合(推荐) | 体验流畅,保证安全 | 需做好数据同步策略 | 需要兼顾体验和准确性 |
👉 最佳方案是 “前端计算 + 后端最终校验”,这样既保证了用户体验,又确保了价格的正确性!🚀
架构分层是一种将系统分解为多个逻辑层次的方法,每一层都有特定的职责和功能。
图片来源于 《架构分层.graffle》中的【一、架构分层】
其他图片见我的项目 CJStandardProject 中的 Screenshots
其他参考文章
iOS:
常见的项目目录结构:
项目目录结构规范说明 请点击本链接跳转下文附录进行查看
完整的 项目目录结构树 请点击链接,跳转到本问附文中查看。
页面执行自己的动作,调用Service服务类。无需关心数据请求、数据请求结束后是否需要缓存等等。
Service服务中心,内部包含请求和可能的数据存储等处理,不对外暴露。
页面调用服务类举例
![]()
图片来源于 《架构分层.graffle》中的【二、页面与服务类设计】
问:我不要频繁安装+卸载来安装我想要的包。我能不能同时装一个测试环境和正式环境?
答:不行。安装两个包,严格上来讲,不用想了,不行。具体原因涉及应用id,及app根据该id配置了各种三方key环境(除非你连三方的也都额外提供一套),太详细的道理我就不讲了。”不行“就对了。但是,我能让你安装一个包,却使用不同环境的功能。或者额外安装一个除与应用id相关的功能外其他皆一样的安装包。
一、切换环境常见的场景为:
1、测试时候:换环境不用一直下载;
2、演示时候:某个环境使用不了,不用重新下载;
3、抓包时候:想切换代理,不用重新打包安装;
见下文:app环境与切换
二、额外安装一个除与应用id相关的功能外其他皆一样的安装包的使用场景:
1、app中不能接受切换环境,即严格限制一个包只能有一个环境
不允许切换环境的考虑原因:
测试包不允许切到生产:怕脏数据
生产包不允许切到测试:测试环境不是外界用户真正需要的
见我的另一篇文章:《iOS的重签名.md》


流程图:《开发工具(含环境切换等).graffle》中的【一、切换环境】版面

未截取到的其他埋点参数见我的另一篇文章:《埋点规范_结构.md》

| 支付 | 支付宝 | ||
| 认证 | 实名认证、真人认证 | ||
| 选择器 | 生日选择、活动时间选择、项目多选 | ||
| 分享 | |||
| 关键页面入口 | 个人主页、发布页、引导页、完善性别/生日 | ||
| 位置相关 | 地图位置选择 |
快捷入口


《h5js.md》
见我在”安全”专题里的另一篇文章:《开发工具安全性处理》
目前的脚本操作如下:

其中自定义的脚本操作有如下:

文档:《Charles.md》
dio添加代理
1 | (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = |
项目目录结构规范、架构分层、页面与服务类设计、依赖关系可视化第一章:服务器相关