# H5直连对接说明
# H5直连接入流程
对接流程如下:
- 先申请AppId和Key。正式环境需要提供客戶服务器出口IP,用 于开放 IP白名单(支持 IP 范围段)。
- 在生产环境使用提供的AppId和Key生成sign,后继调用锦江服务器都需要将fizz-appid、timestamp、sign填入http header中。sign的生成规则请详见调用地址。
- 员工信息同步接口的作用是将公司员工同步到锦江系统中。打开锦江酒店企业版⻚面的时候需要传送员工手机号,确定登录的员工。
- 客戶端调用锦江服务登录接口返回的域名即会进入首⻚。
# APP内嵌H5指引
- ios配置
// H5⽀付、定位、地图、打电话适配
// 定位:
// ⽤户开启定位权限就可以。
// ⽀付、打电话、地图:
// 需要在 WKWebview 的 decidePolicyForNavigationAction 的代理⽅法中增加⽀付、打电
// 话、地图相关适配逻辑。
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
if (([navigationAction.request.URL.scheme isEqualToString:@"weixin"]&&[navigationAction.request.URL.absoluteString containsString:@"weixin://wap/pay?"])||[url containsString:@"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?"]){
// 微信支付适配
// do something
}else if ([navigationAction.request.URL.absoluteString hasPrefix:@"alipays://"] || [navigationAction.request.URL.absoluteString hasPrefix:@"alipay://"]){
// 支付宝支付适配
decisionHandler(WKNavigationActionPolicyCancel);
// NOTE: 如果跳转失败,则跳转itune下载支付宝App
if (![[UIApplication sharedApplication]canOpenURL:navigationAction.request.URL]) {
// do something
}else{
// do something
if (@available(iOS 10,*)) {
[[UIApplication sharedApplication]openURL:navigationAction.request.URL options:@{UIApplicationOpenURLOptionUniversalLinksOnly: @NO} completionHandler:^(BOOL success) {}];
}else{
[[UIApplication sharedApplication] openURL:navigationAction.request.URL];
}
}
return;
}else if ([navigationAction.request.URL.scheme isEqualToString:@"tel"]) {
// 打电话适配
NSString *resourceSpecifier = [realUrl resourceSpecifier];
NSString *callPhone = [NSString stringWithFormat:@"telprompt://%@", resourceSpecifier];
dispatch_async(dispatch_get_main_queue(), ^{
if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:callPhone]]) {
if (@available(iOS 10,*)) {
[[UIApplication sharedApplication]openURL:[NSURL URLWithString:callPhone] options:@{UIApplicationOpenURLOptionUniversalLinksOnly: @NO} completionHandler:^(BOOL success) {}];
}else{
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:callPhone]];
}
}
});
decisionHandler(WKNavigationActionPolicyCancel);
return;
}else if ([navigationAction.request.URL.scheme isEqualToString:@"iosamap"] || [navigationAction.request.URL.scheme isEqualToString:@"baidumap"]|| [navigationAction.request.URL.scheme isEqualToString:@"qqmap"] || [navigationAction.request.URL.host isEqualToString:@"maps.apple.com"]) {
// 高德地图、百度地图、腾讯地图、苹果地图适配
decisionHandler(WKNavigationActionPolicyCancel);
if ([[UIApplication sharedApplication] canOpenURL:navigationAction.request.URL]) {
if (@available(iOS 10,*)) {
[[UIApplication sharedApplication] openURL:navigationAction.request.URL options:@{UIApplicationOpenURLOptionUniversalLinksOnly: @NO} completionHandler:^(BOOL success) {}];
}else{
[[UIApplication sharedApplication] openURL:navigationAction.request.URL];
}
}
return;
}else if ([navigationAction.request.URL.scheme isEqualToString:@"itms-appss"] ||[navigationAction.request.URL.scheme isEqualToString:@"itms-apps"] ) {
// 打开 AppStore 下载应用的适配
decisionHandler(WKNavigationActionPolicyCancel);
if ([[UIApplication sharedApplication] canOpenURL:navigationAction.request.URL]) {
if (@available(iOS 10,*)) {
[[UIApplication sharedApplication]openURL:navigationAction.request.URL options:@{UIApplicationOpenURLOptionUniversalLinksOnly: @NO} completionHandler:^(BOOL success) {}];
}else{
[[UIApplication sharedApplication] openURL:navigationAction.request.URL];
}
}
return;
}
else{
if (navigationAction.targetFrame == nil) {
[webView loadRequest:navigationAction.request];
}
}
decisionHandler(WKNavigationActionPolicyAllow);
}
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
- 安卓配置
# 关于原生拦截 url 提供电话,地图跳转,支付方法实现
- 定位: 需 要 定 位 权 限 : android.permission.ACCESS_COARSE_LOCATION , android.permission.ACCESS_FINE_LOCATION
- 支付: 拉起微信和支付宝,H5 页面需要原生适配拦截 scheme,微信 scheme:weixin://wap/pay?, 支付宝 scheme:alipay://
- 地图: 需要原生提供适配拉起对应的地图
- 打电话: Android 上有两种操作:操作 1:需要权限 android.permission.CALL_PHONE,直接拨打电 话,操作 2:跳转到拨号界面不需要权限申请
示例代码
package com.wehotel.app.ui.webview;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.appcompat.app.AlertDialog;
import androidx.core.app.DialogCompat;
import com.yanzhenjie.permission.Action;
import com.yanzhenjie.permission.AndPermission;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class UrlOverrideUtils {
boolean mIsX5Web=false;
AlertDialog dialog;
public UrlOverrideUtils(boolean isX5Web){
this.mIsX5Web = isX5Web;
}
public void intercepterUrl(Activity context,String s){
try{
Map<String,Object> outParam = getQueryMapObj(URLDecoder.decode(s.replace("https://pr.map.qq.com/pingd?appid=h5navi&apptype=lightapp&hash=",""),"UTF-8"));
if(outParam.containsKey("logid")&&("startnavi".equals(outParam.get("logid"))||"walknav".equals(outParam.get("logid")))){
try{
String url = URLDecoder.decode(s.replace("https://pr.map.qq.com/pingd?appid=h5navi&apptype=lightapp&hash=",""),"UTF-8");
Map<String,Object> mapParam = getQueryMapObj(url);
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
String start = "";
String end = "";
try{
String tempStart = URLDecoder.decode(mapParam.get("start").toString(),"UTF-8");
start = tempStart.split(",")[1]+","+tempStart.split(",")[0];
String tempEnd = URLDecoder.decode(mapParam.get("dest").toString(),"UTF-8");
end = tempEnd.split(",")[1]+","+tempEnd.split(",")[0];
}catch(Exception exp){
exp.printStackTrace();
}
if(mapParam!=null&&mapParam.size()>0){
Uri uri = Uri.parse("qqmap://map/routeplan?"
// 应用名
+ "referer=" + mapParam.get("key")
// 路线规划方式参数:公交:bus 驾车:drive 步行:walk(仅适用移动端)
+ "&type="+("startnavi".equals(outParam.get("logid"))?"drive":"walk")
// 起点名称
+ "&from="+mapParam.get("sword")
+ "&fromcoord="+start
// 终点名称
+ "&to=" + mapParam.get("eword")
// 终点坐标
+ "&tocoord=" + end
// 0:较快捷 1:无高速 2:距离 默认为0
+ "&policy=0");
System.out.println("tencentmap url:"+uri.toString());
if(isExistTencentMap(context)){
skipQQMap(context,uri.toString());
}else{
goMarket(context,"market://details?id=com.tencent.map");
}
}
}
});
}catch(Exception exp){
exp.printStackTrace();
}
}
}catch(Exception exp){
exp.printStackTrace();
}
}
public void showDialog(Activity activity,String content,Runnable allowCallback){
if(dialog==null) {
dialog = new AlertDialog.Builder(activity)
.create();
}
if(!dialog.isShowing()){
dialog.setButton(DialogInterface.BUTTON_POSITIVE, "允许", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if(allowCallback!=null){
allowCallback.run();
}
dialog.dismiss();
}
});
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, "不允许", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
dialog.setMessage(content);
dialog.show();
}
}
public boolean overrideUrl(Activity context, String urlDecode){
if (urlDecode.startsWith("weixin://wap/pay?")) {
//微信支付拦截
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(urlDecode));
context.startActivity(intent);
return true;
} else if (urlDecode.startsWith("tel:")) {
//电话拦截
callPhone(context,urlDecode);
return true;
} else if (urlDecode.contains("platformapi/startapp")
|| urlDecode.contains("platformapi/startApp")
|| urlDecode.startsWith("alipay")) {
//支付宝支付拦截
try {
// Uri uri = Uri.parse(url);
Intent intent;
intent = Intent.parseUri(urlDecode,
Intent.URI_INTENT_SCHEME);
intent.addCategory("android.intent.category.BROWSABLE");
intent.setComponent(null);
// intent.setSelector(null);
context.startActivity(intent);
} catch (Exception e) {
// Log.e(TAG, e.getMessage());
// ToastUtils.show("未检测到支付宝客户端,请安装后重试。");
}
return true;
} else if(urlDecode.startsWith("https://wap.amap.com/?from=m&type=m")){
//下载高德地图url
if(!isExistAmap(context)){
goBrower(context,urlDecode);
}
return true;
} else if(urlDecode.startsWith("amapuri://route")){
//跳转高德地图导航
if(isExistAmap(context)){
if(mIsX5Web){
skipAmap(context,urlDecode);
}else {
showDialog(context, "即将打开高德地图导航", new Runnable() {
@Override
public void run() {
skipAmap(context, urlDecode);
}
});
}
}
return true;
} else if(urlDecode.startsWith("baidumap://map/direction")){
//百度地图导航
if(isExistBaiduMap(context)){
skipBaiduMap(context,urlDecode);
}
return true;
} else if(urlDecode.startsWith("https://map.baidu.com/zt/client/index/?down")){
//下载百度地图
if(!isExistBaiduMap(context)) {
goBrower(context,urlDecode);
}
return true;
}else if(urlDecode.startsWith("https://mapdownload.map.qq.com/cloudctrl")) {
//下载腾讯地图
if(!isExistTencentMap(context)){
goBrower(context,urlDecode);
}
return true;
}else if(urlDecode.startsWith("qqmap://map/routeplan")){
//跳转腾讯地图
if(isExistTencentMap(context)){
skipQQMap(context,urlDecode);
}
return true;
}else if(urlDecode.startsWith("http://a.app.qq.com/o/simple.jsp")){
//pkgname=com.baidu.BaiduMap
Map<String,Object> map = getQueryMapObj(urlDecode);
if(map.size()>0&&map.containsKey("pkgname")){
if(isAppExist(context,map.get("pkgname").toString())){
if(AMP_MAP_PKG.equals(map.get("pkgname").toString())&&map.containsKey("android_schema")){
try{
skipAmap(context,URLDecoder.decode(map.get("android_schema").toString(),"UTF-8"));
}catch(Exception exp){
exp.printStackTrace();
goBrower(context,urlDecode);
}
}
}else{
goBrower(context,urlDecode);
}
}
return true;
} else if(urlDecode.startsWith("market://details")){
Intent rateIntent = new Intent(Intent.ACTION_VIEW,
Uri.parse(urlDecode));
context.startActivity(rateIntent);
//跳转应用市场
return true;
} else if(urlDecode.startsWith("baidumap://map/show?")){
//baidumap://map/show?src=webapp.show.routesearchpg.floatbottombannerbtn_QQBrowser
//打开百度地图
if(isExistBaiduMap(context)) {
Intent i1 = new Intent();
i1.setData(Uri.parse(urlDecode));
context.startActivity(i1);
}
return true;
}
return false;
}
public static void goMarket(Context context,String marketUrl){
// market://details?id=com.baidu.BaiduMap
Intent rateIntent = new Intent(Intent.ACTION_VIEW,
Uri.parse(marketUrl));
context.startActivity(rateIntent);
}
public static boolean isExistAmap(Context context){
return isAppExist( context,AMP_MAP_PKG);
}
public static final String BAIDU_MAP_PKG = "com.baidu.BaiduMap";
public static final String AMP_MAP_PKG = "com.autonavi.minimap";
public static final String TENCENT_MAP_PKG = "com.tencent.map";
public static boolean isExistTencentMap(Context context){
return isAppExist( context,TENCENT_MAP_PKG);
}
public static boolean isAppExist(Context context,String packName){
final PackageManager packageManager = context.getPackageManager();
// 获取所有已安装程序的包信息
List<PackageInfo> pakInfo = packageManager.getInstalledPackages(0);
if (pakInfo != null) {
for (int i = 0; i < pakInfo.size(); i++) {
String pn = pakInfo.get(i).packageName;
if (pn.equals(packName)) {
return true;
}
}
}
return false;
}
public static boolean isExistBaiduMap(Context context){
return isAppExist( context,BAIDU_MAP_PKG);
}
public static Map<String, Object> getQueryMapObj(String uri) {
Map<String, Object> map = new HashMap<>();
try {
String[] queryParms = uri.split("\\?");
if (queryParms.length < 2) return map;
String[] params = queryParms[1].split("&");
for (String param : params) {
try{
String name = param.split("=")[0];
String value = param.split("=")[1];
map.put(name, value);
}catch(Exception exp){
exp.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
private static void skipBaiduMap(Context context,String schemeUrl){
try{
Intent intent = Intent.parseUri(schemeUrl,Intent.URI_INTENT_SCHEME);
context.startActivity(intent);
}catch(Exception exp){
exp.printStackTrace();
}
}
private static void skipAmap(Context context,String schemeUrl){
try{
Intent intent = new Intent();
intent.setData(Uri.parse(schemeUrl));
intent.addCategory("android.intent.category.DEFAULT");
intent.setPackage("com.autonavi.minimap");
context.startActivity(intent);
}catch(Exception exp){
exp.printStackTrace();
}
}
private static void skipQQMap(Context context,String schemeUrl){
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(schemeUrl));
context.startActivity(intent);
}
private static void goBrower(Context context,String url){
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
Uri content_url = Uri.parse(url);
intent.setData(content_url);
context.startActivity(intent);
}
private static void callPhone(Context context,final String url) {
Intent intent = new Intent();
intent.setAction("android.intent.action.VIEW");
intent.setData(Uri.parse(url));
context.startActivity(intent);
// AndPermission.with(context).runtime().permission(new String[]{"android.permission.CALL_PHONE"}).onGranted(new Action<List<String>>() {
// public void onAction(List<String> strings) {
// Intent intent = new Intent();
// intent.setAction("android.intent.action.VIEW");
// intent.setData(Uri.parse(url));
// context.startActivity(intent);
// }
// }).onDenied(new Action<List<String>>() {
// public void onAction(List<String> strings) {
// }
// }).start();
}
}
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349