URL这部分内容其实并不算难,最近刚好项目用到,做一个统一的处理
image.png
App开发中,鉴于上面的设计图,一般都是金刚区的内容,自己的项目整体经历了差不多3个过程
- 移动端本地配置
这种一般被用于项目初期,各种架构都不完善,为了赶项目进度,最为快捷方便的方式 - 根据后端接口返回对应数据
这种是根据后端接口返回对应数据,一般都是一个数组(图标、名称、其他隐藏数据) - 后端数据返回统一字段对应URL类型数据,移动端统一解析
这种其实是对于上述返回数据格式的一种包装,通过URL的一种包装
下面是未包装之前的数据,其实完全够用,有很多公司其实一直也是这么用的,用同事的话说,能用但不够优雅
[
{
"sourceType" : "FTSC",
"gmtCreated" : "2024-09-20 09:57:21",
"paramData" : "0",
"name" : "飞兔商城",
"status" : 0,
"jumpUrl" : "\/pages\/tabbar\/mall",
"sort" : 1,
"gmtModified" : "2025-03-21 13:49:40",
"icon" : "2024\/12\/19\/cee2e20a098448c09600ac23d4cd4b11.png",
"id" : 100012
},
{
"sourceType" : "FTJD",
"gmtCreated" : "2024-12-19 10:41:29",
"paramData" : "",
"name" : "飞兔酒店",
"status" : 0,
"jumpUrl" : "\/pages\/hotel\/index",
"sort" : 2,
"gmtModified" : "2025-03-20 16:06:41",
"icon" : "2024\/12\/19\/f241260bce304de5a1c93431280ee1ae.png",
"id" : 100324
},
{
"sourceType" : "FTWM",
"gmtCreated" : "2024-09-20 09:57:11",
"paramData" : "",
"name" : "飞兔外卖",
"status" : 0,
"jumpUrl" : "",
"sort" : 3,
"gmtModified" : "2025-03-20 16:06:54",
"icon" : "2024\/12\/19\/eb3e350c17f946d6aaa78d3416115a5f.png",
"id" : 100000
},
{
"sourceType" : "FTCSH",
"gmtCreated" : "2024-10-08 10:34:03",
"paramData" : "",
"name" : "飞兔车生活",
"status" : 0,
"jumpUrl" : "",
"sort" : 4,
"gmtModified" : "2025-03-21 14:36:04",
"icon" : "2025\/03\/20\/e5ee3189cd8445c981ca9c4476ea61e3.png",
"id" : 100138
},
{
"sourceType" : "FTKD",
"gmtCreated" : "2024-12-09 10:32:11",
"paramData" : "",
"name" : "飞兔快递",
"status" : 0,
"jumpUrl" : "",
"sort" : 5,
"gmtModified" : "2025-03-20 16:38:20",
"icon" : "2025\/03\/20\/e06c082c11a44f4ea3adc57b5ca73989.png",
"id" : 100321
},
{
"sourceType" : "SHJF",
"gmtCreated" : "2024-09-20 09:57:16",
"paramData" : "",
"name" : "飞兔缴费",
"status" : 0,
"jumpUrl" : "\/pages\/recharge\/phone",
"sort" : 6,
"gmtModified" : "2025-03-20 16:07:32",
"icon" : "2024\/12\/19\/647bff6437eb4c54821f9fdc4ddbd70c.png",
"id" : 100003
},
{
"sourceType" : "FTBX",
"gmtCreated" : "2024-10-08 10:39:07",
"paramData" : "",
"name" : "飞兔保险",
"status" : 0,
"jumpUrl" : "",
"sort" : 7,
"gmtModified" : "2025-03-20 16:38:35",
"icon" : "2025\/03\/20\/7d2e7c65d3a946e8bfb7bae4fa0f338d.png",
"id" : 100156
},
{
"sourceType" : "FTJP",
"gmtCreated" : "2025-01-13 23:27:41",
"paramData" : "0",
"name" : "飞兔机票",
"status" : 0,
"jumpUrl" : "\/pages\/tabbar\/ticket",
"sort" : 8,
"gmtModified" : "2025-03-21 14:35:33",
"icon" : "2025\/01\/13\/9c60e750a30640d0820e6c938ad2945d.png",
"id" : 100500
},
{
"sourceType" : "JTKAPPLET",
"gmtCreated" : "2024-12-05 10:55:55",
"paramData" : "{\"actId\":105}",
"name" : "景点门票",
"status" : 0,
"jumpUrl" : "",
"sort" : 9,
"gmtModified" : "2025-03-20 16:10:03",
"icon" : "2024\/12\/19\/f7a471d29312401da5b934422f3c5dc2.png",
"id" : 100305
},
{
"sourceType" : "JTKAPPLET",
"gmtCreated" : "2024-12-05 10:56:28",
"paramData" : "{\"actId\":76}",
"name" : "电影票",
"status" : 0,
"jumpUrl" : "",
"sort" : 10,
"gmtModified" : "2025-03-20 16:10:17",
"icon" : "2024\/12\/19\/44740ee00e35436cb52b7580803f97d9.png",
"id" : 100313
},
{
"sourceType" : "FTHY",
"gmtCreated" : "2025-03-25 16:46:59",
"paramData" : "",
"name" : "飞兔货运",
"status" : 0,
"jumpUrl" : "",
"sort" : 11,
"gmtModified" : "2025-03-25 16:48:53",
"icon" : "2025\/03\/25\/1b21dd83e4d04e1bb00d5243e40fef3f.png",
"id" : 100607
},
{
"sourceType" : "FTTC",
"gmtCreated" : "2025-03-25 16:46:41",
"paramData" : "",
"name" : "飞兔同城",
"status" : 0,
"jumpUrl" : "",
"sort" : 12,
"gmtModified" : "2025-03-25 16:48:57",
"icon" : "2025\/03\/25\/0d80dd99d0a2407298e614c2e56fc4b0.png",
"id" : 100603
},
{
"sourceType" : "FTCDB",
"gmtCreated" : "2024-12-05 10:54:38",
"paramData" : "",
"name" : "飞兔充电宝",
"status" : 0,
"jumpUrl" : "",
"sort" : 20,
"gmtModified" : "2024-12-19 10:42:20",
"icon" : "2024\/12\/19\/245f41dd6f8445aeba75a0a268e1dc79.png",
"id" : 100300
},
{
"sourceType" : "JTKLONGH5",
"gmtCreated" : "2024-10-08 10:30:23",
"paramData" : "{\"actId\":152}",
"name" : "生活团购",
"status" : 0,
"jumpUrl" : "",
"sort" : 30,
"gmtModified" : "2024-12-19 11:06:13",
"icon" : "2024\/12\/19\/10f1df92bdcd4cc08a34840b88168588.png",
"id" : 100114
}
]
- 对上面的隐藏数据类型简单说明下
因为项目中这部分的交互复杂可变,有点击跳转小程序的,也有本地新页面,也有直接跳转本地H5页面,也有根据对应id获取对应数据,解析其中url跳转本地H5页面或者浏览器H5页面的,所有的这些的大前提都需要依据 sourceType 这个枚举类型,后期如果增加了新的类型,移动端就必须重新发包,苹果的话还需要再次经过审核,而且后端返回的字段相对比较冗余了,所以后面就想着最好可以一劳永逸的处理,后期新增或者删除只需要后端控制就好,这时候URL就提上日程
URl的初步认识
URL(Uniform Resource Locator)即统一资源定位符,它是互联网上用于定位和访问资源的地址
- https://www.baidu.com
- https://www.jianshu.com/sign_in
- https://www.baidu.com/link?url=BlcN2tinTLZXRA6VEvakpw3d9mzATC8IyWF7JlZrEc7EGcITI_k22yxAigYeMYev7s3nxRDIyFHz8r7rW9xHUoYgJd6-WNKiEkmNPBm1wcK&wd=&eqid=98f9d66e00101bf90000000267ea0c51
- https://upload-images.jianshu.io/upload_images/8379097-54391ee23b7e7978.png?imageMogr2/auto-orient/strip%7CimageView2/2
- https://www.jianshu.com/writer#/notebooks/17479299/notes/126840298/preview
上面简单列举集中常见的url,一个完整的url通常由多个部分构成,基本格式如下
scheme://username:password@host:port/path?query#fragment
Scheme
- 访问资源时具体使用的协议,一般有https/http/ftp 等,最常见的可能就是https/http 了
- http 是超文本传输协议,https 是在 http 基础上加入了安全加密机制的协议,ftp 则用于文件传输。具体这里不多说,有需要可以自行了解
Username:Password
- 可选部分,用于访问资源需要身份验证时的用户名与密码
- 示例 ftp://user:pass@ftp.example.com,这里的 user 是用户名,pass 是密码
- 作用 对用户进行身份验证,确保只有授权用户才能访问特定数据
Host
- 资源所在服务器的域名或者IP
- 在 https://www.baidu.com 中,www.baidu.com 就是host
Port
- 可选部分,用于指定服务器上提供服务的端口号
- http 默认端口 80 ,https 默认端口 443, FTP命令端口:21、FTP数据端口(非加密):20(通常客户端随机选择,但服务器也可能配置为使用20)、FTP over SSL/TLS(隐式):990、FTP over SSL/TLS(显式):21(通过TLS加密,但仍使用21端口)、SFTP:22
- 端口好用于区分不同的服务,客户端可以指定端口号链接服务器上特定的服务
Path
- 指定服务器上资源的具体位置
- 在 https://www.example.com/path/to/resource.html 中,/path/to/resource.html 就是路径
- 有点类似文件系统中的目录结构
Query
- 参数可选,一个或者多个key=value使用&连接
- 在 https://www.example.com/search?keyword=apple&page=2 中,keyword=apple&page=2 就是查询参数
Fragment
- 可选,以#开头,指定资源内部的一个位置
- 在 https://www.example.com/page.html#section2 中,#section2 就是片段。
URL 示例
https://user:pass@www.example.com:8080/path/to/resource.html?key1=value1&key2=value2#section3
- scheme: https
- username: user
- password: pass
- host: www.example.com
- port: 8080
- path: /path/to/resource.html
- query: key1=value1&key2=value2
- fragment: #section3
最终经过进一步的数据结构调整,返回数据格式如下所示
- 跳转flutter页面
{
"id": 102,
"icon": "2024/12/19/cee2e20a098448c09600ac23d4cd4b11.png",
"name": "飞兔商城",
"route": "feitu://native.feitu.com/mall",
"sort": 1,
"pid": 0,
"isAuth": 1
}
- 跳转微信小程序
{
"id": 126,
"icon": "2024/12/19/44740ee00e35436cb52b7580803f97d9.png",
"name": "电影票",
"route": "weixin://feitu.com/?username=gh_fc1db4789939&path=pages%2Findex%2Findex%2Findex&appid=wx4dea6f132ae99e01",
"sort": 10,
"pid": 0,
"isAuth": 1
}
- 跳转H5
{
"id": 145,
"icon": "2024/12/19/10f1df92bdcd4cc08a34840b88168588.png",
"name": "生活团购",
"route": "redirect://jtk/152?target=WEB",
"sort": 14,
"pid": 0,
"isAuth": 1
}
总的来说,后端通过接口返回的这两种方式思路都是差不多的,将第一种返回多个字段合并为router一个字段,移动端根据URL解析做出不同的页面交互,为了后期扩展,预留字段pid(如果后期有多级,可以根据该字段获取下一级的数据),isAuth (该模块交互是否需要登录等),思路都是一样的,就是数据结构可能更优雅一点
这里使用swift简单封装一个解析的类
class FTURLParser {
let scheme: String?
let userName: String?
let passWord: String?
let host: String?
let port: Int?
let path: String?
let query: String?
let fragment: String?
let queryItems: [URLQueryItem]?
init?(urlString: String) {
guard let url = URL(string: urlString),
let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
return nil
}
self.scheme = components.scheme
self.userName = components.user
self.passWord = components.password
self.host = components.host
self.port = components.port
self.path = components.path
self.query = components.query
self.fragment = components.fragment
self.queryItems = components.queryItems
}
//MARK: --- 根据query的key获取对应的value
func getQueryValue(for key: String) -> String? {
return queryItems?.first { $0.name == key }?.value
}
}
具体使用如下
let url = "https://user:pass@www.example.com:8080/path/to/resource.html?key1=value1&key2=value2#section3"
let parser = FTURLParser(urlString: url)
print("scheme = \(parser?.scheme ?? "")")
print("userName = \(parser?.userName ?? "")")
print("passWord = \(parser?.passWord ?? "")")
print("host = \(parser?.host ?? "")")
print("port = \(parser?.port ?? 0)")
print("path = \(parser?.path ?? "")")
print("query = \(parser?.query ?? "")")
print("fragment = \(parser?.fragment ?? "")")
print("value = \(parser?.getQueryValue(for: "key1") ?? "")")
结果如下
image.png