AbooJan Blog

专注移动开发


  • 首页

  • 归档

  • 分类

  • 关于

Privacy Agreement

发表于 2018-12-12 | 分类于 iOS开发

Privacy Usage Agreement

By using this app means you agreed with the terms below.

What do we collect?

We do not collect any content you created.

We only anonymous collect the usage info of app listed below:

  • App launch time.
  • How long user will spend on each app page.
  • What action has the user takes.
  • App’s crash info.

All the data is anonymous. We don’t know who takes the action exactly, and all of the data will be shared with Tencent’s MTA service only.

What 3rd party services do we use

We use Tencent’s MTA to analytics the anonymous usage data.

Golang ORM + PostgreSQL

发表于 2017-06-25 | 分类于 Golang

Golang 的 ORM 框架有很多,我这里使用的国产的 xorm [http://xorm.io/], 它包含了一个命令行工具用以从数据库生成databean。

PostgreSQL 配置

  1. 在 $PGDATA 指向的文件夹里有一个文件 postgresql.conf, 参数listen_addresses 可以配置数据库的监听IP地址,默认值为 localhost , 如果把它的值设为 * 符号,表示监听全局。

    我这里把它的值设为 * ,方便Golang对数据库进行连接。
    更多参数说明可以查看手册:http://www.postgres.cn/docs/9.3/runtime-config-file-locations.html

  1. 在 postgresql.conf 文件所在目录下,有一个文件 pg_hba.conf,它用以配置连接数据库的用户认证。它主要分为3个类别:local Unix domain socket、IPV4 connect、IPV6 connect。

    在 TYPE 一列,可以配置为 local、host、hostssl、hostnossl。

    在 ADDRESS 一列,如果想要指向一个唯一主机,IPV4地址为:IP地址/32,IPV6地址为:IP地址/128。

    在 METHOD 一列,可以配置 trust, reject, md5, password等, password 和 md5 的区别是一个明文传输,一个加密传输。

    更多配置说明可以参考手册:http://www.postgres.cn/docs/9.3/auth-pg-hba-conf.html

    以下是配置示例:

    1
    2
    3
    4
    5
    6
    local  all      aboo                       md5

    host all all 127.0.0.1/32 md5
    host db_test aboo 192.168.0.102/32 md5

    host all all ::1/128 md5

xorm

  • 添加 xorm 库:

    1
    go get github.com/go-xorm/xorm
  • 添加Golang的PostgreSQL驱动 lib\pg 库:

    1
    go get github.com/lib/pq
  • 下载 xorm\cmd 工具:

    1
    go get github.com/go-xorm/cmd/xorm

    下载完之后进入到该目录,执行 go build 生成脚本,方便以后生成数据Bean用。在构建这个脚本前,本地有 golang.org\x\crypto 和 golang.org\x\net 2个库,如果没有可以从GitHub下载然后添加到 GOPATH 中,否则无法成功。

    更多 xorm 相关信息可以到它官网查看:http://xorm.io 。

    在这里推荐一个Go依赖包下载工具 gopm : https://gopm.io 。

CRUD

  1. 现在数据库 db_test 中添加一个名为 tb_user 的表。
  2. 使用 xorm 的命令行工具生成表 tb_user 的数据Bean,然后添加到项目中。
  3. 为了全局能够操作同一数据库,需要使用一个单例来维护 xorm 的实例,在单例里面连接并初始化数据库。

    connectInfo := "user=aboo password=666666 dbname=db_test sslmode=disable host=192.168.0.102 port=5432"
    eng,err := xorm.NewEngine("postgres", connectInfo)
    
    if err != nil {
        logs.Info("connect db fail: ", err)
        return
    }
    logs.Info("connect db success")
    
    //连接池的空闲数大小
    eng.SetMaxIdleConns(30)
    //设置最大打开连接数
    eng.SetMaxOpenConns(30)
    //数据库操作log打印
    eng.ShowSQL(true)
    eng.Logger().SetLevel(core.LOG_INFO)
    

    记得要导入驱动: _ "github.com/lib/pq"

  4. 查询方法: Get方法、Find方法、Rows方法。
  5. 插入方法:Insert方法 或 InsertOne方法。
  6. 更新方法:Update方法。
  7. 删除方法: Delete方法。

更多使用方法可以查看 xorm 官网开发文档。

PostgreSQL 上手体验

发表于 2017-06-20 | 分类于 PostgreSQL

安装

在 Mac 上安装PostgreSQL的方式有很多,我选择了使用Homebrew,配上国内源,速度飞起,很快就可以安装好了。Homebrew 国内源,我选择了清华大学的TUNA社区开源镜像。

  • 替换现有Homebrew上游:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    cd "$(brew --repo)"

    git remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git

    cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"

    git remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git

    brew update
  • 替换Homebrew二进制预编译包的镜像:

    1
    2
    3
    echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles' >> ~/.bash_profile

    source ~/.bash_profile
  • 安装PostgreSQL:

    1
    brew install postgresql

等待安装完成。

启动数据库

PostgreSQL数据库安装好之后,创建一个文件夹用于以后存放数据。我在用户根路径创建了一个 PostgreSQL 文件夹及其子文件夹 data。然后配置环境变量 PGDATA, 把它加到 bash_profile 文件中。

1
export PGDATA= "/Users/abo/PostgreSQL/data"

然后命令行进入 data 子文件夹,执行命令 initdb 来初始化数据库,初始化完成之后会提示数据库启动命令。

执行命令启动数据库

1
pg_ctl -D /Users/abo/PostgresSQL/data -l logfile start

数据库相关操作

  • 查看当前所有数据库, 默认会有3个数据库 postgres、template0、template1

    1
    psql -l
  • 进入目标数据库

    1
    psql postgres
  • 创建用户,注意数据库命令后面有个分号,大小写都可以

    1
    create user abo login superuser with password '666';
  • 创建数据库

    1
    CREATE DATABASE db_test OWNER abo;
  • 删除数据库

    1
    drop database db_test;
  • 创建表

    1
    create table user_tbl(id bigint, name varchar(10), age int, height real);
  • 插入数据

    1
    insert into user_tbl (id, name, age, height) values(123456, 'abo', 25, 170.0);

更多数据库命令可以使用命令 \h 查看或到下载手册。
中文手册地址:http://www.postgres.cn/docs/9.3/index.html

数据类型

名字 别名 描述
bigint int8 有符号8字节整数
bigserial serial8 自增8字节整数
bit [ (n) ] 定长位串
bit varying [ (n) ] varbit 可变长位串
boolean bool 逻辑布尔值(真/假)
box 平面上的矩形
bytea 二进制数据(“字节数组”)
character varying [ (n) ] varchar [ (n) ] 可变长字符串
character [ (n) ] char [ (n) ] 定长字符串
cidr IPv4 或 IPv6 网络地址
circle 平面上的圆
date 日历日期(年, 月, 日)
double precision float8 双精度浮点数(8字节)
inet IPv4 或 IPv6 主机地址
integer int, int4 有符号 4 字节整数
interval [ fields ] [ (p) ] 时间间隔
line 平面上的无限长直线
lseg 平面上的线段
macaddr MAC (Media Access Control)地址
money 货币金额
numeric [ (p, s) ] decimal [ (p, s) ] 可选精度的准确数值数据类型
path 平面上的几何路径
point 平面上的点
polygon 平面上的封闭几何路径
real float4 单精度浮点数(4 字节)
smallint int2 有符号 2 字节整数
smallserial serial2 自增 2 字节整数
serial serial4 自增 4 字节整数
text 可变长字符串
time [ (p) ] [ without time zone ] 一天中的时刻(无时区)
time [ (p) ] with time zone timetz 一天中的时刻,含时区
timestamp [ (p) ] [ without time zone ] 日期与时刻(无时区)
timestamp [ (p) ] with time zone timestamptz 日期与时刻,含时区
tsquery 文本检索查询
tsvector 文本检索文档
txid_snapshot 用户级别的事务ID快照
uuid 通用唯一标识符
xml XML 数据
json JSON 数据

Golang上手体验

发表于 2017-06-17 | 分类于 Golang
1
学习一门新语言的时候,一般喜欢先花几天时间过一遍语法,然后选一个热门的框架入手,再一步步深入。

Golang 环境搭建

  • Golang 的安装倒是挺简单,直接到 官网 就可以下载安装包安装,然后配置环境变量:

    1
    2
    3
    4
    5
    GOPATH="/Users/tmp/Golang/GOPATH"
    export GOPATH
    GOBIN="/usr/local/go/bin:/Users/tmp/go/bin"
    export GOBIN
    export PATH=$PATH:$GOBIN
  • 麻烦的是工具链的下载,一开选择了VS Code 工具来开发,后来改用了 JetBrains 出的Golang IDE,虽然还是 EAP 版,不过对日常使用没什么影响,目前还是免费的。如果使用 go get 命令去下载工具链,等个大半天都没下载好,期间还经常出错,在国内做开发就是麻烦。我的做法是直接去 gitHub 上下载源码,然后本地编译,编译完之后再放到环境变量 GOBIN 目录下。以下是涉及到的工具:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    go-find-references	
    goimports
    goreturns
    go-outline
    golint
    gotests
    go-symbols
    gomodifytags
    guru
    gocode
    gopkgs
    godef
    gorename
    dlv
  • 以上工具链配置完之后再下载第三方框架 Beego 的工具 Bee,这环境算是配置好了。


Beego 框架

直接使用 Bee 工具的 bee new demo 命令生成一个demo项目, 进入目录,go build 之后就可以直接命令行执行生成的运行文件。

Beego 框架遵循 MVC 模式,以下是它自动生成的目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
demo
|-- conf
| `-- app.conf
|-- controllers
| `-- default.go
|-- main.go
|-- models
|-- routers
| `-- router.go
|-- static
| |-- css
| |-- img
| `-- js
|-- tests
| `-- default_test.go
`-- views
`-- index.tpl

Coding

Golang 跟C类似,它的运行入口在 main 方法。Beego框架对原生的方法做了很多封装,对网络请求的处理会更便捷。

  • M: Model, 即数据模型,新的模型我们把它放到models文件夹下,例如新建一个BaseResponse模型:

    1
    2
    3
    4
    5
    type BaseResponse struct {
    Code string `json:"code"`
    Msg string `json:"msg"`
    Data interface{} `json:"data"`
    }

    由于它的每个成员都是public的,所以它的首字母必须大写,成员后面的 json 标签是为了定义它在JSON序列化时对应的字段名称。

  • V: View, 即视图,Beego使用的是视图模板,它放在views文件夹下。如果只是Api开发,不会使用到。

  • C: Controller,即控制器,所有的业务逻辑会放到这里来处理,它被放到controllers 文件夹下。例如新建一个TestController控制器:

    1
    2
    3
    type TestController struct {
    beego.Controller
    }

    添加匿名成员beego.Controller,相当于 TestController 继承 beego.Controller ,拥有了它的成员变量和方法。

  • 路由,网络请求的Api定义,它在routers文件夹下,Beego框架对路由的定义有好几种方法,以下是其中一种:

    1
    2
    3
    4
       beego.Router("/test/version", &controllers.TestController{}, "*:Version")
    beego.Router("/test/user", &controllers.TestController{}, "get:Get")
    beego.Router("/test/friends", &controllers.TestController{}, "get:Friends")
    beego.Router("/test/login", &controllers.TestController{}, "post:Post")

    第一个变量是定义网络请求的Api名称,第二个变量是网络请求的接收控制器,第三个是Api跟控制器的匹配定义。: 符号之前是限定当前Api的请求方式, :符号之后是Api请求对应的控制器方法名称。

代码

源码: https://github.com/AbooJan/BeegoDemo

二叉树广度优先遍历

发表于 2017-03-14 | 分类于 算法

所谓的二叉树广度遍历,就是从上到下依次一层一层遍历。
队列的特点是先进先出,正好可以利用这一特点做二叉树的广度优先遍历,所以遍历的时候需要借助一个队列。

以下是要遍历的二叉树:

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
85

class TreeNode{
var value:String?;
var leftChild:TreeNode?;
var rightChild:TreeNode?;

init(value:String, left:TreeNode?, right:TreeNode?) {
self.value = value;
self.leftChild = left;
self.rightChild = right;
}
}


class Queue{

private var itemArray:Array<TreeNode>?;

init() {
self.itemArray = [];
}

func push(value:TreeNode) -> Void {
self.itemArray?.append(value);
}

func pop() -> TreeNode? {

if self.itemArray!.count > 0 {

let headItem:TreeNode = self.itemArray!.first!;

self.itemArray?.removeFirst();

return headItem;
}

return nil;
}

func isEmpty() -> Bool {
return self.itemArray!.count == 0;
}
}


let dNode:TreeNode = TreeNode(value: "D", left: nil, right: nil);
let eNode:TreeNode = TreeNode(value: "E", left: nil, right: nil);
let fNode:TreeNode = TreeNode(value: "F", left: nil, right: nil);
let bNode:TreeNode = TreeNode(value: "B", left: dNode, right: eNode);
let cNode:TreeNode = TreeNode(value: "C", left: fNode, right: nil);
let aNode:TreeNode = TreeNode(value: "A", left: bNode, right: cNode);

var readResult:Array<String> = [];
var treeQueue:Queue = Queue();


// 开始遍历,先把根节点放入队中
treeQueue.push(value: aNode);

// 一直到队列的所有元素出列
while (!(treeQueue.isEmpty())){

// 队列里的第一个元素出列
let root:TreeNode? = treeQueue.pop();

if root != nil {

readResult.append(root!.value!);

// 如果存在左子树,就把它入队
if (root!.leftChild != nil) {
treeQueue.push(value: root!.leftChild!);
}

// 如果存在右子树,就把它入队
if (root!.rightChild != nil) {
treeQueue.push(value: root!.rightChild!);
}
}
}

print("广度优先遍历结果:");
print(readResult);

以下是输出结果:

1
2
3

广度优先遍历结果:
["A", "B", "C", "D", "E", "F"]

二叉树深度优先遍历

发表于 2017-03-13 | 分类于 算法

二叉树深度优先遍历分为:前序优先遍历、中序优先遍历、后序优先遍历。
所谓的前中后,是相对于读取根节点的顺序来说的。
以下是 swift 3.0 的实现

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

class TreeNode {

var value:String?;
var leftNode:TreeNode?;
var rightNode:TreeNode?;

init(value:String?, leftNode:TreeNode?, rightNode:TreeNode?) {
self.value = value;
self.leftNode = leftNode;
self.rightNode = rightNode;
}
}

var firstReadResult:Array<String> = [];
func firtRead(root:TreeNode?)
{
guard root != nil else {
return;
}

firstReadResult.append((root?.value)!);
firtRead(root: root?.leftNode);
firtRead(root: root?.rightNode);
}

var midReadResult:Array<String> = [];
func midRead(root:TreeNode?)
{
guard root != nil else {
return;
}

midRead(root: root?.leftNode);
midReadResult.append((root?.value)!);
midRead(root: root?.rightNode);
}

var lastReadResult:Array<String> = [];
func lastRead(root:TreeNode?)
{
guard root != nil else {
return;
}

lastRead(root: root?.leftNode);
lastRead(root: root?.rightNode);
lastReadResult.append((root?.value)!);
}

let dNode:TreeNode = TreeNode(value: "D", leftNode: nil, rightNode: nil);
let eNode:TreeNode = TreeNode(value: "E", leftNode: nil, rightNode: nil);
let fNode:TreeNode = TreeNode(value: "F", leftNode: nil, rightNode: nil);
let bNode:TreeNode = TreeNode(value: "B", leftNode: dNode, rightNode: eNode);
let cNode:TreeNode = TreeNode(value: "C", leftNode: fNode, rightNode: nil);
let aNode:TreeNode = TreeNode(value: "A", leftNode: bNode, rightNode: cNode);

// 前序遍历
firtRead(root: aNode);
print("前序遍历:");
print(firstReadResult);
print("\n");

// 中序遍历
midRead(root: aNode);
print("中序遍历:");
print(midReadResult);
print("\n");

// 后序遍历
lastRead(root: aNode);
print("后序遍历:");
print(lastReadResult);
print("\n");

以下是输出结果:

1
2
3
4
5
6
7
8
前序遍历:
["A", "B", "D", "E", "C", "F"]

中序遍历:
["D", "B", "E", "A", "F", "C"]

后序遍历:
["D", "E", "B", "F", "C", "A"]

二分查找

发表于 2017-03-12 | 分类于 算法

二分查找的前提是,数组是有序数组,目标元素一定在数组中

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
static const int COUNT = 10;

int search(int arr[], int target)
{
int left = 0;
int right = COUNT - 1;
int mid = (left+right)/2;

while (1) {

// 说明元素在右边
if (target > arr[mid]) {
left = mid + 1;
mid = (left + right)/2;

// 说明元素在左边
}else if (target < arr[mid]) {
right = mid - 1;
mid = (left + right)/2;

}else{
// 一直到找到为止
return mid;
}
}
}

快速排序

发表于 2017-03-12 | 分类于 算法
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
int partition(int arr[], int low, int high)
{
// 分割元素
int key = arr[low];

// 从右边开始找比key小的元素
while (low < high) {

// 从右边找到一个比key小的元素
if (arr[high] <= key) {
// 把左边存放key的位置给找到的元素
arr[low] = arr[high];
// 左边指针右移一位
low++;
break;
}

// 如果没找到,一直往左找
high --;
}


//从左边开始找比key大的元素
while (low < high) {

// 从左边找到一个比key大的元素
if (arr[low] >= key) {
// 把上一步找到的元素的位置给当前较小元素
arr[high] = arr[low];
// 右边指针左移一位
high --;
break;
}

// 如果没找到,一直往右找
low++;
}


/*
// 另一种循环
for (; left < right; right--) {

if (arr[right] <= key) {
arr[left] = arr[right];
left++;
break;
}
}

for (; left < right; left++) {
if (arr[left] >= key) {
arr[right] = arr[left];
right--;
break;
}
}
*/


// 把较小元素的那个位置放回key元素
arr[low] = key;

return low;
}

void quick_sort(int arr[], int left, int right)
{
if (left < right){

int pos = partition(arr, left, right);

// 分割元素的左边去排序
quick_sort(arr,left,pos-1);

// 分割元素的右边去排序
quick_sort(arr,pos+1,right);
}

}

归并排序

发表于 2017-03-12 | 分类于 算法

核心思想是分治法,将数组1分为2,通过递归,把数组拆分成很多个小数组(只有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
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
void mergeArray(int targetArr[], int start, int mid, int last, int tmpArr[])
{
int k = 0;
int backupStart = start;

// 把数组1分为2
int leftArrLast = mid;
int rightArrStart = mid+1;

// 合并2个数组
while (start <= leftArrLast && rightArrStart <= last) {

// 如果左边元素比右边元素大
if (targetArr[start] <= targetArr[rightArrStart]) {
// 把左边元素放入临时数组中
tmpArr[k] = targetArr[start];
start++;

}else{
// 如果右边元素较小,把右边元素放入临时数组中
tmpArr[k] = targetArr[rightArrStart];
rightArrStart++;
}

k++;
}

// 左边2边,肯定会有1边多出有元素没比较

// 把左边剩余的部分放入临时数组中
while (start <= leftArrLast) {
tmpArr[k] = targetArr[start];
k++;
start++;
}

// 把右边剩余部分放入临时数组中
while (rightArrStart <= last) {
tmpArr[k] = targetArr[rightArrStart];
k++;
rightArrStart++;
}

// 把排序好的合并数组放入原来数组中,注意原来数组的开始索引,从backStart+i开始
for (int i = 0; i < k; i++) {
targetArr[backupStart+i] = tmpArr[i];
}

}

void mergeSort(int targetArr[], int start, int last, int tmpArr[])
{

// 传入1个临时的 tmpArr 是为了节省内存开销

if (start < last) {

// 1分为2,分而治之
int mid = (start+last)/2;

// 对左边元素进行排序
mergeSort(targetArr, start, mid, tmpArr);

// 对右边元素进行排序, 索引从 mid+1 开始
mergeSort(targetArr, mid+1, last, tmpArr);

// 合并左右2边的排序数组
mergeArray(targetArr, start, mid, last, tmpArr);
}
}

App网络层的思考

发表于 2017-03-12 | 分类于 iOS开发

App开发基本离不开网络请求,网络层应该怎么去搭建才能更方便使用、更专注于业务逻辑?

自个儿琢磨了一段时间,也参考了一些博客和开源框架,于是有了以下的网络层架构图。主要包含2大块,缓存层和网络请求层。

  • Github:https://github.com/AbooJan/AJNetworking

缓存层

大部分App都会针对某些接口设计缓存。
一方面是为了在无网络或网络请求失败的时候有数据展示,而不是空白一片,这样用户体验会更好;
另一方面是为了节省资源,如果本身接口的数据更新不频繁的,没必要每次都去发起网络请求去服务器取数据,只需读缓存就可以了,例如省市区数据。

针对以上问题,我想不如每个接口都设计2个缓存配置项,一个用于配置是否需要缓存数据,另一个配置缓存过期时间。
在缓存有效时间内发起的网络请求,底层不会真正发起网络请求,只是去读取缓存数据然后返回结果。
如果缓存过期时间配置是永久有效的,则先读取缓存数据,然后发起网络请求,会回调2次。这种配置主要是为了在无网络或者是网络请求失败的时候有数据展示。

在框架中,我使用了开源库 SPTPersistentCache, 它是LRU缓存设计,可以设置缓存数据有效时间和缓存自动回收。

网络请求层

这是核心层,里面包含网络监测、JSON数据转化和缓存处理。
每一次请求都会先检查网络是否连接,如果无网络,直接返回错误。
如果网络请求失败,例如网络超时或者是服务器错误,则会返回错误。
当网络请求成功并有数据返回时,会判断当前请求是否有设置缓存,如果有则会先把数据缓存起来,然后回调成功结果。

网络请求是封装开源框架 AFNetwoking,请求参数是以NSDictionary 格式传入,返回的则是JSON数据。

我觉得请求参数以字典的形式很不方便,写起来麻烦,维护起来也麻烦,如果某个接口参数变了,需要找到那个网络发起的位置,然后修改字典的key。
针对以上问题,我利用开源框架 MJExtension,把每个接口的参数以数据Bean的形式管理,一个接口对应一个Bean。发起网络请求的时候只需传入这个Bean的实例,网络层会在里面转成字典形式,然后发起请求。

另外一个是网络返回的JSON数据,如果我们每次都需要在业务层对返回的JSON数据进行转化,这样重复代码很多,写起来也不方便。
针对此问题,我在网络层里面把返回的JSON数据利用框架 MJExtension 转成数据Bean,然后返回给业务层,这样业务层拿到就可以用了,方便。

总结

总的来说,就是把数据的处理合并在一起,在业务层直接拿就可以了。

以上是我对网络层的一些思考,不足之处,还请多多指教。

12
AbooJan

AbooJan

岭深常得蛟龙在,梧高自有凤凰栖

19 日志
6 分类
10 标签
GithHub
© 2018 AbooJan
由 Hexo 强力驱动
主题 - NexT.Pisces