第1节:腾讯云cos上传

[toc]

腾讯云cos上传

官方文档:腾讯云-COSCMD 工具

一、主要步骤:

使用 pip install coscmd

需要:①使用python3安装pip、②使用pip install coscmd、③使用coscmd上传文件

其他:brew

1
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

1、python3

找不到python3的bin地址可使用如下命令:

1
which python3

2、pip

在 Mac 上为 Python 3 使用 PIP 安装脚本

1
2
3
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py

python3 get-pip.py

以上脚本来源于:如何在 Mac 上安装 PIP

image-20220726013611412

安装失败,原因未设置到环境变量中,通过如下命令即可设置新增环境变量

1
2
3
4
5
6
7
8
9
10
11
which pip

# 非M1芯片的MacOS使用如下:
echo 'export PATH=~/Library/Python/3.8/bin:$PATH' >>~/.zshrc
source ~/.zshrc

# 非M1芯片的MacOS使用如下:
echo 'export PATH=~/Library/Python/3.8/bin:$PATH' >>~/.bash_profile
source ~/.bash_profile

which pip

错误修改参考于文章:Pip安装警告:Consider adding this directory to PATH..

3、COSCMD 工具安装

官方文档:腾讯云-COSCMD 工具

我们使用通过 pip 安装方式安装 COSCMD。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pip install coscmd

coscmd -v
which coscmd

# 非M1芯片的MacOS使用如下:
echo 'export PATH=~/Library/Python/3.8/bin:$PATH' >>~/.zshrc
source ~/.zshrc

# 非M1芯片的MacOS使用如下:
echo 'export PATH=~/Library/Python/3.8/bin:$PATH' >>~/.bash_profile
source ~/.bash_profile

which pip

image-20220726150612750

4、COSCMD 工具使用

COSCMD 工具在运行前会首先从配置文件中读取运行时所需的必要信息,COSCMD 会默认从 ~/.cos.conf 中读取配置项。

所以,在~目录下,新建.cos.conf文件,并配置上类似如下的信息:

1
2
3
4
5
6
7
8
9
10
11
12
[common]
secret_id = AKIDA6wUmImTMzvXZNbGLCgtusZ2E8mG****
secret_key = TghWBCyf5LIyTcXCoBdw1oRpytWk****
bucket = configure-bucket-1250000000
region = ap-chengdu
max_thread = 5
part_size = 1
retry = 5
timeout = 60
schema = https
verify = md5
anonymous = False
1
2
3
4
5
6
7
8
# 非M1芯片的MacOS使用如下:
echo 'export PATH=/Applications/Xcode.app/Contents/SharedFrameworks/ContentDeliveryServices.framework/itms/bin:$PATH' >>~/.zshrc
source ~/.zshrc


# 非M1芯片的MacOS使用如下:
echo 'export PATH=/Applications/Xcode.app/Contents/SharedFrameworks/ContentDeliveryServices.framework/itms/bin:$PATH' >>~/.bash_profile
source ~/.bash_profile

腾讯云-对象存储-Python SDK

之后即可正常安装腾讯云-对象存储-Python SDK

1
pip install -U cos-python-sdk-v5

image-20220726014253864

End

至此,利用腾讯云Cos上传文件的操作介绍结束。

苹果设备注册管理

[toc]

苹果设备注册管理

一、无法安装告知

各位需要安装app的苹果手机用户们,晚上好。如您在通过如下地址:
https://www.pgyer.com/xxxx
下载app的时候,发现无法安装,则可能是您的设备未进行证书注册(如果您可以安装则证明已注册,请忽略本消息)。
如您对安装有需求,则可通过如下地址
https://www.pgyer.com/tools/udid
将您的苹果手机udid告知我们,我们为您添加后。当我们打新包的时候,就可以安装上app啦。
特别注意:因证书注册设备只能添加100台,故即使添加上去之后,再删除也算是一台。所以如果您非必要安装,请忽略本消息。
最后,提前祝大家节日快乐!

二、设备管理列表

1、规则

1.1、备注规则

序号 类型 简写
1 产品
2 测试
3 后台
4 iOS开发 i
5 Android开发 A

1.2、用途示例

序号 类型 场景
1 开发 开发必须
2 测试 测试测试与回归需求
3 验收 产品验收版本功能
4 体验 其他用户提前体验

2、列表示例:

序号 备注 用途 UUID 是否必须 申请时间
1 产-张三-iPhone8 功能验收

End

设计模式-①概览

[Toc]

设计模式三大分类以及六大原则

  1. 设计模式三大分类

设计模式分为三种类型,共23类。

(1)创建型模式:单例模式抽象工厂模式、建造者模式、工厂模式、原型模式。

(2)结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式

(3)行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式职责链模式、访问者模式。

一、设计模式的六大原则/SOLID原则

使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。

六大原则在代码中的设计体现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 里氏替换原则:应用程序中任何父类对象出现的地方,我们都可以用其子类的对象来替换,并且可以保证原有程序的逻辑行为和正确性。因为这里父类是抽象类,所以肯定遵守里氏替换原则。
abstract class ICacheStore {
ICacheStore();

Future<CacheObj?> getCacheObj(String key, {String? subKey});
Future<bool> setCacheObj(CacheObj obj); // 接口隔离原则:告诉我们在设计接口的时候要精简单一; get set分为两个接口,不要一个接口用type
Future<bool> delete(String key, {String? subKey});
Future<bool> clearExpired();
Future<bool> clearAll();
}


class CacheManager {
CacheConfig _config;
ICacheStore? _diskCacheStore; // 依赖倒置原则:CacheManager高层模块依赖于cache抽象(接口或抽象类),而不是具体实现
ICacheStore? _memoryCacheStore; // 单一职责原则

// 迪米特法则(又叫做最少知识原则)中的第2点减少对朋友的了解,即尽量减少一个类对外暴露的方法。
Future<bool> pushToCache(CacheObj obj) {
return _getCacheFutureResult(_memoryCacheStore, _diskCacheStore,
_memoryCacheStore?.setCacheObj(obj), _diskCacheStore?.setCacheObj(obj));
}

Future<bool> _getCacheFutureResult(
ICacheStore? memoryCacheStore, // 开闭原则:对扩展开放,对修改封闭. 不同的保存用不同的store,不影响原本的store。实施开放封闭原则的基本思路:让类xxxStore依赖于固定的抽象ICacheStore,所以对修改是封闭的;而通过面向对象的继承和多态机制,可以实现对抽象体的继承,通过覆写其方法来改变固有行为,实现新的扩展方法,所以对于扩展就是开放的。
ICacheStore? diskCacheStore,
Future<bool>? memoryCacheFuture,
Future<bool>? diskCacheFuture) async {
var result1 = (null == memoryCacheStore) ? true : (await memoryCacheFuture!);
var result2 = (null == diskCacheStore) ? true : (await diskCacheFuture!);
return result1 && result2;
}
}

class CacheNetworkClient extends NetworkClient {
// 迪米特法则(又叫做最少知识原则)中的第1点只和直接的朋友交流
DioCacheManager? _dioCacheManager;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
```





[设计模式六大原则(六)----开闭原则](https://cloud.tencent.com/developer/article/1836753)

实施开放封闭原则的基本思路:让类xxxStore依赖于固定的抽象ICacheStore,所以对修改是封闭的;而通过面向对象的继承和多态机制,可以实现对抽象体的继承,通过覆写其方法来改变固有行为,实现新的扩展方法,所以对于扩展就是开放的。



[设计模式六大原则(二)----里式替换原则](https://cloud.tencent.com/developer/article/1831005)

里氏替换原则主要**阐述了有关继承的一些原则,即什么时候应该使用继承,什么时候不应该使用继承**,以及其中蕴含的原理。里氏替换原是继承复用的基础,它反映了基类与子类之间的关系,**是对开闭原则的补充,是对实现抽象化的具体步骤的规范**。

实现里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。

里氏替换原则是针对继承而言的。其至少有两种含义:
1、如果继承是为了实现代码重用,也就是为了共享方法,那么共享的父类方法就应该保持不变,不能被子类重新定义。子类只能通过新添加方法来扩展功能,父类和子类都可以实例化,而子类继承的方法和父类是一样的,父类调用方法的地方,子类也可以调用同一个继承得来的,逻辑和父类一致的方法,这时用子类对象将父类对象替换掉时,当然逻辑一致,相安无事。
2、如果继承的目的是为了多态,。。。。



[设计模式六大原则(五)----迪米特法则](https://cloud.tencent.com/developer/article/1836752)

实施迪米特法则:做到两点就足够了: 1.只和直接的朋友交流; 2.减少对朋友的了解。

什么是直接的朋友呢?出现在**成员变量**、**方法的输入输出参数**中的类就是直接的朋友。

减少对朋友的了解:**换作在一个类中,就是尽量减少一个类对外暴露的方法。**

CacheManager是NetworkClient的好友,CacheManager只需提供pushToCache公共方法,不用提供pushToDiskCache、pushToMemoryCache这两个私有方法。





### 1、单一职责原则(S:Single Responsibility Principle)

>①单一职责原则告诉我们实现类要职责单一;
>eg:UserService专做用户相关,OrderService专做订单相关
>
>
>
>②依赖倒置原则告诉我们要面向接口编程;通过抽象(接口或抽象类)使各个类或模块实现彼此独立,互不影响,实现模块间的松耦合。
>
>定义: 高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。//“抽象”指“接口或抽象类”,“细节”指“实现类”
>
>eg:Driver开车,依赖的是Driver.drive(BaseCar)。而不能是没有基类的BenchiCar、BMWCar。
>
>[依赖倒置原则(设计模式原则)](https://blog.csdn.net/u013862108/article/details/79054620)
>
>
>
>③里氏替换原则告诉我们不要破坏继承体系;
>eg:子类可以扩展父类的方法(扩展出一个showSpeed方法),但不应该复写父类的方法。不要破坏继承体系;
>
>
>
>④开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。
>eg:在你有新的需求的时候,最好不要轻易修改老功能,你应当增加新的对象来实现,而不是修改原来的对象
>
>
>
>⑤接口隔离原则告诉我们在设计接口的时候要精简单一;
>
>通俗讲:不要把一大堆方法塞进一个接口里,导致这个接口变得臃肿无比。应该要根据实际需要,让接口中只有用得上的方法,**也就是说要细化我们的接口**。
>
>定义为:
>
>> 1. Clients should not be forced to depend upon interfaces that they don’t use. (客户端不应该依赖它不需要的接口。)
>> 2. The dependency of one class to another one should depend on the smallest possible interface. (类间的依赖关系应该建立在最小的接口上。)它要求“最小的接口”,也就是该接口中没有多余的方法,所以**这里的隔离是指和多余的方法隔离。**
>
>
>
>⑥迪米特法则(最少知道原则),告诉我们要尽量降低类与类之间的耦合;
>eg:UserService与OrderService之间要降低耦合

### 2、开闭原则(O:Open Close Principle)

开闭原则:对扩展开发,对修改关闭。

在代码层面而言就是**在你有新的需求的时候,你应当增加新的对象来实现,而不是修改原来的对象。**

设计模式六大原则(6):开闭原则
定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。

问题由来:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
举例:
当有新需求时,如果增加功能涉及老功能点某些地方,最好不要轻易修改老功能,最好重新新增相应的功能,不然一些忽略的细节可能就会造成隐患。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

### 3、里氏替换原则(L:Liskov Substitution Principle)

**里氏替换原则:子类可以扩展父类的方法,但不应该复写父类的方法。不要破坏继承体系;**

假设有父类如下:

```swift
import Foundation

class Car {
func run() {
print("汽车跑起来了")
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 错误
class BaoMaCar: Car {
override func run() {
super.run()

print("当前行驶速度是80Km/h")
}
}

// 正确:子类可以扩展父类的方法(扩展出一个showSpeed方法),但不应该复写父类的方法。不要破坏继承体系;
class BaoMaCar: Car {
func showSpeed() {
print("当前行驶速度是80Km/h")
}
}

4、接口隔离原则(I:Interface Segregation Principle)

5、依赖倒置原则(D:Dependence Inversion Principle)

依赖倒置原则告诉我们要面向接口编程;通过抽象(接口或抽象类)使各个类或模块实现彼此独立,互不影响,实现模块间的松耦合。

二、常见的设计模式

< 返回目录

MVC是模型、视图、控制器开发模式,对于iOS SDK,所有的View都是视图层的,它应该独立于模型层,由视图器来控制。所有的用户数据都是模型层,它应该独立于视图。所有的ViewController都是视图器,由它负责控制视图,访问模型数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class CacheManager {	// 单例模式
CacheConfig _config;
ICacheStore? _diskCacheStore;
ICacheStore? _memoryCacheStore;

late StreamSubscription? _xxxSubscription;
CacheManager(this._config) {
......
_xxxSubscription = eventBus.on<MemoryReleaseEvent>().listen((event) { // 观察者模式

});
}

Future<bool> pushToCache(CacheObj obj) {
return _getCacheFutureResult(_memoryCacheStore, _diskCacheStore,
_memoryCacheStore?.setCacheObj(obj), _diskCacheStore?.setCacheObj(obj));
}

Future<bool> _getCacheFutureResult(
ICacheStore? memoryCacheStore, // 策略模式
ICacheStore? diskCacheStore,
Future<bool>? memoryCacheFuture,
Future<bool>? diskCacheFuture) async {
var result1 = (null == memoryCacheStore) ? true : (await memoryCacheFuture!);
var result2 = (null == diskCacheStore) ? true : (await diskCacheFuture!);
return result1 && result2;
}
}

1、iOS开发中的几种设计模式介绍(应用场景、实例)

iOS开发中的几种设计模式介绍

(一)代理模式

应用场景:当一个类的某些功能需要由别的类来实现,但是又不确定具体会是哪个类实现。

实例:tableview的datasource和delegate

(二)观察者模式

应用场景:一般为model层对,controller和view进行的通知方式,不关心谁去接收,只负责发布信息。

实例:
①Notification通知中心,注册通知中心,任何位置可以发送消息,注册观察者的对象可以接收。
②KVO,键值对改变通知的观察者。

(三)MVC模式

应用场景:MVC模式是一种非常古老的设计模式,它通过数据模型,控制器逻辑,视图展示将应用程序进行逻辑划分。

实例:model-即数据模型,view-视图展示,controller进行UI展现和数据交互的逻辑控制。

(四)单例模式

应用场景:确保程序运行期间,某个类只有一份实例,常用于进行资源共享控制

实例:[UIApplication sharedApplication]。

注意事项:确保使用者只能通过 getInstance方法才能获得,单例类的唯一实例。
java,C++中使其没有公有构造函数,私有化并覆盖其构造函数。
object c中,重写allocWithZone方法,保证即使用户用 alloc方法直接创建单例类的实例,
返回的也只是此单例类的唯一静态变量。

(五)策略模式

应用场景:定义算法族,封装起来,使他们之间可以相互替换。
优势:使算法的变化独立于使用算法的用户
敏捷原则:接口隔离原则;多用组合,少用继承;针对接口编程,而非实现。
实例:排序算法,NSArray的sortedArrayUsingSelector;经典的鸭子会叫,会飞案例。
注意事项:1,剥离类中易于变化的行为,通过组合的方式嵌入抽象基类
2,变化的行为抽象基类为,所有可变变化的父类
3,用户类的最终实例,通过注入行为实例的方式,设定易变行为
防止了继承行为方式,导致无关行为污染子类。完成了策略封装和可替换性。

附:Objective C中数组排序几种情况的总结

OC中常用的数组排序有以下几种方法:

①、sortedArrayUsingSelector:;

②、sortedArrayUsingComparator:;

③、sortedArrayUsingDescriptors:

1
2
3
4
5
6
//简单排序
- (void)sortArray() {
NSArray *array = [NSArray arrayWithObjects:@"abc",@"456",@"123",@"789",@"ef", nil];
NSArray *sortedArray = [array sortedArrayUsingSelector:@selector(compare:)];
NSLog(@"排序后:%@",sortedArray);
}

策略模式的其他例子:

(六)工厂模式

应用场景:工厂方式创建类的实例,多与proxy模式配合,创建可替换代理类。
优势:易于替换,面向抽象编程,application只与抽象工厂和易变类的共性抽象类发生调用关系。
敏捷原则:DIP依赖倒置原则
实例:项目部署环境中依赖多个不同类型的数据库时,需要使用工厂配合proxy完成易用性替换
注意事项:项目初期,软件结构和需求都没有稳定下来时,不建议使用此模式,因为其劣势也很明显,
增 加了代码的复杂度,增加了调用层次,增加了内存负担。所以要注意防止模式的滥用。

iOS 设计模式之工厂模式
工厂模式我的理解是:他就是为了创建对象的

创建对象的时候,我们一般是alloc一个对象,如果需要创建100个这样的对象,如果是在一个for循环中还好说,直接一句alloc就行了,但是事实并不那么如意,我们可能会在不同的地方去创建这个对象,那么我们可能需要写100句alloc 了,但是如果我们在创建对象的时候,需要在这些对象创建完之后,为它的一个属性添加一个固定的值,比方说都是某某学校的学生,那么可能有需要多些100行重复的代码了,那么,如果写一个-(void)createObj方法,把创建对象和为对象设置初始的属性值(比如学校属性)写在这个方法里边,那么就是会省事很多,也就是说我们可以alloc 创建对象封装到一个方法里边,直接调用这个方法就可以了,这就是简单工厂方法

如果一个工厂里有两个create创建方法,比如PeopleFactory类工厂同时有createTeacher和createStudent两个方法,现在假设原本创建100个老师的写法是如下所示:

1
2
3
4
5
PeopleFactory *factory = [PeopleFactory alloc] init];
People *people1 = [factory createTeacher];
People *people2 = [factory createTeacher];
People *people3 = [factory createTeacher];
People *people4 = [factory createTeacher];

现在突然想让创建出来的对象是student,而不是老师teacher,那么这时候我们就不得不一个个的把createTeacher替换成createStudent方法了。

1
2
3
4
5
PeopleFactory *factory = [PeopleFactory alloc] init];
People *people1 = [factory createStudent];
People *people2 = [factory createStudent];
People *people3 = [factory createStudent];
People *people4 = [factory createStudent];

但是如果利用工厂模式,我们为每一个要创建的对象所在的类都相应地创建一个工厂,则我们的写法将会变成

1
2
3
4
5
6
7
8
9
10
11
12
13
//创建老师
TeacherFactory *factory = [TeacherFactory alloc] init];
People *people1 = [factory createPeople];
People *people2 = [factory createPeople];
People *people3 = [factory createPeople];
People *people4 = [factory createPeople];

//创建学生
StudentFactory *factory = [StudentFactory alloc] init];
People *people1 = [factory createPeople];
People *people2 = [factory createPeople];
People *people3 = [factory createPeople];
People *people4 = [factory createPeople];

显然上面这种方法,在我们需要不同对象时,修改起来更方便点。

工厂方法模式是为每一个要创建的对象所在的类都相应地创建一个工厂

END

版本检查

[toc]

版本检查

一、App的版本检查

1、流程图

版本检查

图片来源于 版本检查.graffle

二、接口设计

公共参数详见:《基础规范-请求规范》 中的request公共入参保持一致

序号 标识 示例 描述 必传 用途
1 isManualCheck true/false 是否是手动检查更新 避免关闭后无法弹出
2 callOwner app、h5 调用者 不同调用者版本提示语可能不一致

三、JS方法提供

背景:一些新活动需要触发弹出新版本

序号 js接口 描述 场景
1 h5CallBridgeAction_checkVersion 检查更新
2 h5CallBridgeAction_updateVersion 马上更新 更新弹窗h5游戏自定义不同的风格

二、版本修改

1、iOS版本设置

2、Android版本修改

进入bulid.gradle文件,修改如下:

1
2
3
flutterVersionName  ="1."+new Date().format("MM.dd", TimeZone.getTimeZone("GMT+8:00"))

flutterVersionCode = new Date().format("MMddHHmm", TimeZone.getTimeZone("GMT+8:00")) // 需要versionCode每次都要比上次大,与版本无关

3、Jenkins中的版本设置

进入Jenkins指定iOS项目的配置,修改

1
2
3
4
VERSION=$(date "+%y.%m.%d") # 19.02.21 小y是19,大Y是2019
BUILD=$(date "+%y%m%d%H%M") # 1902211506
echo "------VERSION:${VERSION}"
echo "------BUILD:${BUILD}"

二、网页的版本检查/版本更新

https://www.google.com 为例,即访问google的时候,想要访问到最新的版本内容。

其他参考文章:

一、直接版本地址方案

有版本更新时候,接口直接返回最终的访问地址。形如:https://www.google.com/v10201000

缺点:

  • 返回给app的地址得一直变化。

  • app中历史记录得做映射,不能直接保存网页地址。否则会出现点击了一个历史记录,发现之前的地址无法访问的问题。(为此,app就得通过类似id的方式,每次去向接口取最新的值)

    退一步将每次获取改为切换前后台的时候,才去获取,也会出现有地址id,却出现通过id取接口失败的一些异常情况出现。

二、重定向方案

版本举例如下:

时间 发布的版本号 浏览器/客户端访问的【输入地址】 浏览器/客户端【访问的实际地址】
10.20 10:00 v10201000 https://www.google.com https://www.google.com/v10201000
10.24 10:00 v10241000 同上 https://www.google.com/v10241000
11.01 10:00 V11011000 同上 https://www.google.com/v10311000
11.01 12:00 V11011200 同上 https://www.google.com/v10311200

方案过程如下:

1、浏览器/客户端访问 https://www.google.com

2、https://www.google.com 所对应的网页 https://www.google.com/index.html 进行 check_version 接口请求

1
2
3
4
5
网页:https://www.google.com/index.html
请求方法:check_version
请求结果:{
"url": "https://www.google.com/v10241000/index.html"
}

3、https://www.google.com/index.html 根据所得地址重定向

4、最终即看到的是用户在浏览器/客户端访问 https://www.google.com ,但实际上会访问最新的版本 https://www.google.com/v10241000/index.html

优点:

  • 浏览器/客户端访问的【输入地址】版本变化时,也不用变化。

三、常见问题(FAQ)

问题1:访问google没看到变化的内容

1、无重定向时,即访问的地址是 https://www.google.com/v10201000

可能原因为:有缓存,需要在url后加参数 来刷缓存,形如: https://www.google.com/v10201000?refresh=123

2、有重定向时,即访问的地址一直是 https://www.google.com

可能原因为:https://www.google.com/index.html 没做重定向到最新的版本地址

End

网络库接口文档

[toc]

网络库接口文档

前言

本文为规范网络请求的编码文件位置和规范调用的方法。

一、网络请求接口使用简版

这里只介绍接口使用。详细的接口设计,详见下文。

1、基础请求接口(post/get)

1.1、不需要缓存 AppNetworkRequestUtil

1
2
3
4
5
// post 请求
AppNetworkRequestUtil.post

// get 请求
AppNetworkRequestUtil.get

1.2、需要缓存 AppNetworkCacheUtil(默认一级缓存)

1
2
3
4
5
// post 请求
AppNetworkCacheUtil.post

// get 请求
AppNetworkCacheUtil.get

2、列表请求接口

1.2、需要缓存 AppNetworkListCacheUtil(post)

1
AppNetworkListCacheUtil.post

底层会调用 postListWithCallback

①、不需要特殊的pageSize时候,请求参数里无需设置 pageSize=20 ,底层会自动为你自动补上。

②、自动提取参数中的 pageNum,当发现pageNum为第一页的时候,底层会自动为你做第一页的缓存,且其他页不会做缓存。

二、接口使用规范+示例

1、接口使用规范

接口请求,统一写在每个模块的Request下。

附:已在前文《 项目目录结构规范 见《架构模式-①概览》 》 里的【7、服务类Service】中规范的Service结构如下:

7、服务类service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> │  ├─ service
> │ │ ├─ global_config
> │ │ │ ├─ manager
> │ │ │ │ └─ global_config_service.dart
> │ │ │ ├─ request
> │ │ │ │ └─ global_config_request.dart
> │ │ │ └─ cache
> │ │ │ └─ global_config_cache.dart
> │ │ └─ user
> │ │ ├─ manager
> │ │ │ └─ user_service.dart
> │ │ ├─ request
> │ │ │ └─ user_info_request.dart
> │ │ └─ cache
> │ │ └─ user_info_cache.dart
>

services/:包含应用程序的服务类或者管理中心,例如用户信息中心、配置信息中心等。

——-manager: 管理中心

——-request: 管理中心会需要的请求功能

——-cache: 管理中心可能需要的存储功能

2、示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class UserRequest {
// 登录-普通
static login_normal({
String mobile,
String smsCode,
void Function(UserModel userModel) completeBlock,
}) {
return AppNetworkRequestUtil.post(
UrlPath.doLogin,
params: {
"mobile": mobile,
"smsCode": smsCode,
"grantType": "sms_code",
},
).then((ResponseModel responseModel) {
if (responseModel.isSuccess != true) {
completeBlock(null);
return;
}

User_manager_bean baseUserModel;
if (responseModel.result != null) {
baseUserModel = UserModel.fromJson(responseModel.result);
} else {
baseUserModel = null;
}
completeBlock(baseUserModel);
});
}
}

三、网络请求机制设计

本点只介绍机制,请求的属性介绍,详见下文。

1、重试机制设计

接口:requestWithRetry –> requestUrl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
Future<ResponseModel> requestWithRetry(
String api, {
RequestMethod requestMethod = RequestMethod.post,
Map<String, dynamic>? customParams,
int retryCount = 0, // 轮询次数,最后一次不管成功与否都要返回
Duration retryDuration = const Duration(milliseconds: 1000), // 轮询间隔
bool Function(ResponseModel responseModel)?
retryStopConditionConfigBlock, // 是否请求停止的判断条件(为空时候,默认请求成功即停止)
Options? options,
bool withLoading = false,
bool?
toastIfMayNeed, // 应该弹出toast的地方是否要弹出toast(如网络code为500的时候),必须可为空是,不为空的时候无法实现修改

ResponseModel? beforeResponseModel, // 上一次请求时候得到的值(重试\缓存)
}) async {
return requestUrl(
api,
requestMethod: requestMethod,
customParams: customParams,
options: options,
withLoading: withLoading,
toastIfMayNeed: toastIfMayNeed,
).then((ResponseModel responseModel) {
if (beforeResponseModel != null) {
if (responseModel.isEqualToResponse(beforeResponseModel)) {
responseModel.isSameToBefore = true;
}
}

bool allowRetryIfFailure = retryCount > 1;
if (allowRetryIfFailure != true) {
// 重试次数用完的话,最后一次不管成功与否都要返回
return responseModel;
}

// 如果实际的请求成功,则直接返回
bool noneedRetry = responseModel.isSuccess;
if (retryStopConditionConfigBlock != null) {
noneedRetry = retryStopConditionConfigBlock(responseModel);
}
if (noneedRetry) {
return responseModel;
} else {
// 如果实际的请求失败,则尝试再进行请求
retryCount--;

return Future.delayed(retryDuration).then((value) {
return requestWithRetry(
api,
requestMethod: requestMethod,
customParams: customParams,
retryCount: retryCount,
retryDuration: retryDuration,
retryStopConditionConfigBlock: retryStopConditionConfigBlock,
options: options,
withLoading: withLoading,
toastIfMayNeed: toastIfMayNeed,
beforeResponseModel: responseModel,
);
});
}
});
}

2、缓存机制设计

2.1、在重试基础上+缓存

接口:cache_requestWithCallback –> requestWithRetry –> requestUrl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/// 可进行缓存的请求(需要多次返回结果)
// ignore: non_constant_identifier_names
void cache_requestWithCallback(
String api, {
RequestMethod requestMethod = RequestMethod.post,
Map<String, dynamic>? customParams,
bool? ifNoAuthorizationForceGiveUpRequest, // 没有 Authorization 的时候是否强制放弃请求
int retryCount = 0,
NetworkCacheLevel cacheLevel = NetworkCacheLevel.none,
required void Function(ResponseModel responseModel) completeCallBack,
ResponseModel? cacheResponseModel,
}) async {
/*
bool shouldGiveUp = await super.shouldGiveUpRequest(api);
// 此时可能网络初始化还没完成(PS:初始化操作在base_requestUrl中 await _initCompleter.future;)
if (shouldGiveUp) {
LogApiUtil.logCancelApi(api);
return;
}
*/

if (ifNoAuthorizationForceGiveUpRequest == true) {
if (await existAuthorization() == false) {
return;
}
}

ResponseModel responseModel = await requestWithRetry(
api,
requestMethod: requestMethod,
customParams: customParams,
retryCount: retryCount,
options: CacheHelper.buildOptions(cacheLevel),
beforeResponseModel: cacheResponseModel,
);

// 不是真正的网络请求返回的Response\Error结果(eg:比如是取缓存的结果时候)
// bool noRealRequest = cacheLevel == NetworkCacheLevel.one;
if (responseModel.isCache != true) {
// 1、当请求结果是后台实际请求返回的时候:
if (responseModel.isSuccess) {
// ①.如果实际的请求成功,则直接返回
completeCallBack(responseModel);
} else {
// ②.如果实际的请求失败,由于 statusCode 在200到300之间的请求结果都会被保存起来,但我们此系统里他们可能是错误的。
// 如500,就不要保存起来了,防止请求的时候发现有缓存数据,使得页面使用到了保存的数据
if (cacheLevel != NetworkCacheLevel.none) {
Uri uri = getUri(api);
String requestMethodString = requestMethod.toString().split('.').last;
bool deleteSuccess = await deleteByPrimaryKeyAndSubKeyWithUri(uri,
requestMethod: requestMethodString, data: customParams);
if (deleteSuccess == false) {
_log("Warning:此份网络数据结果本不该存储,但存了,现在必须删却又没删除成功的接口");
}
}
completeCallBack(responseModel);
}
} else {
// 2、请求结果是之前缓存的数据返回的(即结果是真正的网络请求返回的Response\Error结果)的时候,
// ①先把缓存返回回去,
// ②再继续发起实际的请求,且新请求为后台实际请求并且其要会保存请求成功的数据
completeCallBack(responseModel);

late NetworkCacheLevel newCacheLevel;
if (cacheLevel == NetworkCacheLevel.one) {
newCacheLevel = NetworkCacheLevel.forceRefreshAndCacheOne;
} else {
_log(
'Error:$api判断出错啦,此结果不是缓存数据,却走到了isCache==true'); // TODO:无网络的情况下会发生此现象
newCacheLevel = NetworkCacheLevel
.none; //TODO:临时为了走下去,应该自始至终都不会走到这里,这里之后要 throw Exception
}

cache_requestWithCallback(
api,
requestMethod: requestMethod,
customParams: customParams,
cacheLevel: newCacheLevel,
completeCallBack: completeCallBack,
cacheResponseModel: responseModel, // 缓存数据
);
}
}

3、整合成app层的requestWithCallback

接口:requestWithCallback –> cache_requestWithCallback –> requestWithRetry –> requestUrl

①、加入接口模拟mock

②、加入网络判断

③、加入loading

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
void requestWithCallback(
String api, {
RequestMethod requestMethod = RequestMethod.post,
Map<String, dynamic>? customParams,
bool? ifNoAuthorizationForceGiveUpRequest, // 没有 Authorization 的时候是否强制放弃请求
int retryCount = 0,
AppNetworkCacheLevel cacheLevel = AppNetworkCacheLevel.none,
withLoading = false,
bool? showToastForNoNetwork,
required void Function(ResponseModel responseModel) completeCallBack,
}) async {
AppMockManager.tryDealApi(
api,
isGet: requestMethod == RequestMethod.get ? true : false,
);

// ignore: todo
/* ///TODO:判断不准确,临时注释起来
if (cacheLevel != NetworkCacheLevel.one) {
// 不是取缓存的请求的时候,才需要取网络
if (NetworkStatusManager().connectionStatus == NetworkType.none) {
ResponseModel newResponseModel = checkResponseModelFunction(
ResponseModel.nonetworkResponseModel(),
showToastForNoNetwork: showToastForNoNetwork,
);

if (completeCallBack != null) {
completeCallBack(newResponseModel);
}
return;
}
}
*/

if (withLoading == true) {
LoadingUtil.show();
}

cache_requestWithCallback(
api,
requestMethod: requestMethod,
customParams: customParams,
ifNoAuthorizationForceGiveUpRequest: ifNoAuthorizationForceGiveUpRequest,
retryCount: retryCount,
cacheLevel: cacheLevel == AppNetworkCacheLevel.one
? NetworkCacheLevel.one
: NetworkCacheLevel.none,
completeCallBack: (ResponseModel responseModel) {
if (withLoading == true && responseModel.isCache != true) {
LoadingUtil.dismiss();
}

ResponseModel newResponseModel = checkResponseModelFunction(
responseModel,
toastIfMayNeed: showToastForNoNetwork,
);

completeCallBack(newResponseModel);
},
);
}

4、callback转为Future的机制

接口:post/get –> requestWithCallback –> cache_requestWithCallback –> requestWithRetry –> requestUrl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class AppNetworkCacheUtil {
String api;
Map<String, dynamic>? params;
bool? ifNoAuthorizationForceGiveUpRequest; // 没有 Authorization 的时候是否强制放弃请求
int retryCount;
bool withLoading;
AppNetworkCacheLevel cacheLevel;
bool? showToastForNoNetwork;

RequestMethod requestMethod = RequestMethod.post;

AppNetworkCacheUtil.get(
this.api, {
this.params,
this.ifNoAuthorizationForceGiveUpRequest,
this.retryCount = 0,
this.withLoading = false,
this.cacheLevel = AppNetworkCacheLevel.one,
this.showToastForNoNetwork,
}) {
requestMethod = RequestMethod.get;
}

AppNetworkCacheUtil.post(
this.api, {
this.params,
this.ifNoAuthorizationForceGiveUpRequest,
this.retryCount = 0,
this.withLoading = false,
this.cacheLevel = AppNetworkCacheLevel.one,
this.showToastForNoNetwork,
}) {
requestMethod = RequestMethod.post;
}

void then(
void Function(ResponseModel responseModel) completeCallBack,
) async {
AppNetworkManager().requestWithCallback(
api,
requestMethod: requestMethod,
customParams: params,
ifNoAuthorizationForceGiveUpRequest: ifNoAuthorizationForceGiveUpRequest,
retryCount: retryCount,
cacheLevel: cacheLevel,
withLoading: withLoading,
showToastForNoNetwork: showToastForNoNetwork,
completeCallBack: completeCallBack,
);
}
}

四、列表接口的缓存设计

方法:postListWithCallback

①、不需要特殊的pageSize时候,请求参数里无需设置 pageSize=20 ,底层会自动为你自动补上。

②、自动提取参数中的 pageNum,当发现pageNum为第一页的时候,底层会自动为你做第一页的缓存,且其他页不会做缓存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

extension Cache on AppNetworkManager {
/// 列表的请求(未设置会自动补上 pageNum pageSize 参数)
void postListWithCallback(
String api, {
required Map<String, dynamic> customParams,
int retryCount = 0,
AppListCacheLevel listCacheLevel = AppListCacheLevel.one,
withLoading = false,
bool? showToastForNoNetwork,
required void Function(ResponseModel responseModel) completeCallBack,
}) async {
if (customParams['pageSize'] == null) {
customParams.addAll({"pageSize": 20});
}

AppNetworkCacheLevel cacheLevel = AppNetworkCacheLevel.none;
if (customParams['pageNum'] != null) {
int pageNum = customParams['pageNum'];
if (listCacheLevel == AppListCacheLevel.one && pageNum == 1) {
cacheLevel = AppNetworkCacheLevel.one;
}
}

requestWithCallback(
api,
requestMethod: RequestMethod.post,
customParams: customParams,
retryCount: retryCount,
cacheLevel: cacheLevel,
withLoading: withLoading,
showToastForNoNetwork: showToastForNoNetwork,
completeCallBack: completeCallBack,
);
}

}

五、接口属性介绍

1、错误toast

1
bool? toastIfMayNeed; //可空字段

1.1、默认null

按照默认的公共设置,公共设置里会指明每种code的toast显示方式。

举例:非0都要是toast就toast,不toast就不弹

详情点击:《网络设计.graffle 中的 【code的toast设计】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[
{
"code": [Unknow],
"message": "非常抱歉!服务器开小差了~",
},
{
"code": [ErrorTryCatch],
"message": "非常抱歉!系统请求发生错误了~",
},
{
"code": [NoNetwork], # -1
"message": "目前无可用网络",
},
{
"code": [ErrorTimeout],
"message": "请求超时",
},
{
"code": [ErrorDioCancel],
"message": "请求取消",
},
{
"code": [ErrorDioResponse],
"message": "非常抱歉!服务器开小差了~",
},
{
"code": [500, 503],
"message": "非常抱歉!服务器开小差了~",
},
{
"code": [401+暂未登录或token已经过期],
"message": "登录失效,请重新登录",
},
{
"code": [401+后台的其他message],
"message": "Token不能为空或Token过期,请重新登录",
},
{
"code": "ErrorTimeout",
"message": "请求超时",
},
]

1.2、!null

有任何错误都弹或者不弹

前端觉得应该弹出toast的地方是否要弹出toast(如网络code为500的时候),必须可为空,不为空的时候无法实现修改

1、null时候,即不设置true或false。

Android加固

[toc]

Android加固

一、360加固功能介绍

二、功能说明

1、免费版

1.1、DEX文件加密

1.2、防二次打包

2、专业防篡改版

2.1、DEX保护–字符串加密

Flutter中的字符串是否加密???

2.2、DEX保护–默认VMP保护

2.3、SO加固–SO保护

2.4、文件保护–文件完整性校验

防篡改

2.5、文件保护–资源文件保护

2.6、数据保护–防截屏

使用方法:加固时候,勾选是否需要防截屏

使用表现:如果使用,则当用户进行该操作的时候,会弹出toast提示。

2.7、Sandhook检测

功能介绍如下:

SandHook是一款开源的Android平台Hook框架,它可以用于检测应用程序是否被Hook。

Hook是指在应用程序执行期间,通过修改应用程序的代码或者内存中的数据,来改变应用程序原有的行为。

如果检测到应用程序被Hook,加固厂商会采取相应的措施,例如弹出警告框、终止应用程序等,以防止应用程序受到攻击。

问:如果Android本身有一些点击的埋点hook,那会被Sandhook检测出异常吗?

如果Android本身的点击埋点是通过Hook技术实现的,那么它们有可能被SandHook检测出异常。

2.8、环境检测–双开检测、脱壳检测

使用方法:加固时候,勾选是否需要该功能。

使用表现:如果使用,则当用户进行该操作的时候,会被检测到并进行退出。

3、高级防逆向

Dex2C

dex2c是一种工具,可以将Android应用程序的DEX文件转换成C/C++代码。DEX文件是Android应用程序的核心文件之一,包含了应用程序的字节码、类、方法等信息。通过将DEX文件转换成C/C++代码,可以使得应用程序的核心代码不再以DEX格式存储在设备上,而是以C/C++代码的形式存储在设备上,从而增强应用程序的安全性。

三、加固前后数据比较

1、基础加固服务的内容及数据对比

image-20230621104154335 image-20230621110245139

2、企业版与专业版加固效果对比

详见:Android合规安全

网络框架

[toc]

打包保证

背景

主要处理问题:

  • 问题1:避免外部人员使用非正式包
    • 1.1、安装生产调试包
      • 途径1:下载到蒲公英生产包
        • 解决1:设置下载密码(iOS+Android)
        • 解决2:iOS限制安装设备
        • 解决3:蒲公英包通过判断标志,禁止登录(白名单除外)
      • 途径2:
    • 1.2、安装测试包
      • 使用生产功能,通过测试包切换到生产环境
        • 生产数据错误
      • 使用测试功能
    • 1.3、使用开发工具
      • 添加密码(优先级:服务端密码 –> app版本密码,跟随build号)
    • 1.4、发生之后,如何避免,版本升级的控制。
      • 优先级:
  • 避免开发人员随意打包正式包
安装生产调试包 下载到蒲公英生产包 设置下载密码(iOS+Android)
iOS限制安装设备
蒲公英包通过判断标志,禁止登录(白名单除外)
问题 限制下载 限制安装 限制使用(登录) 强制升级
蒲公英的iOS生产包 ✅添加下载密码 限制安装设备 ✅根据发布平台/版本号
蒲公英的Android生产包 ✅添加下载密码
蒲公英的iOS调试包 ✅添加下载密码 限制安装设备 白名单
蒲公英的Android调试包 ✅添加下载密码

解决

1、避免外部人员使用非正式包

1、外部禁止安装生产调试包

1.1、iOS限制安装设备