001package net.gdface.facelog.client; 002 003import static com.google.common.base.Preconditions.*; 004 005import java.io.Closeable; 006import java.lang.reflect.Proxy; 007import java.net.MalformedURLException; 008import java.net.URI; 009import java.net.URISyntaxException; 010import java.net.URL; 011import java.util.List; 012import java.util.Map; 013import java.util.Map.Entry; 014import java.util.Set; 015import java.util.concurrent.ExecutionException; 016 017import com.google.common.base.Function; 018import com.google.common.base.MoreObjects; 019import com.google.common.base.Predicate; 020import com.google.common.base.Strings; 021import com.google.common.base.Supplier; 022import com.google.common.base.Suppliers; 023import com.google.common.base.Throwables; 024import com.google.common.collect.Maps; 025import com.google.common.collect.Sets; 026import com.google.common.net.HostAndPort; 027 028import gu.dtalk.MenuItem; 029import gu.dtalk.cmd.CmdManager; 030import gu.dtalk.cmd.TaskManager; 031import gu.dtalk.engine.BaseDispatcher; 032import gu.dtalk.engine.CmdDispatcher; 033import gu.dtalk.engine.TaskDispatcher; 034import gu.simplemq.IMessageAdapter; 035import gu.simplemq.IMessageQueueFactory; 036import gu.simplemq.ISubscriber; 037import gu.simplemq.MessageQueueFactorys; 038import gu.simplemq.json.BaseJsonEncoder; 039import gu.sql2java.SimpleLog; 040import net.gdface.facelog.IFaceLog; 041import net.gdface.facelog.MQParam; 042import net.gdface.facelog.ServiceSecurityException; 043import net.gdface.facelog.Token; 044import net.gdface.facelog.Token.TokenType; 045import net.gdface.facelog.client.dtalk.DtalkEngineForFacelog; 046import net.gdface.facelog.db.DeviceBean; 047import net.gdface.facelog.db.PersonBean; 048import net.gdface.facelog.hb.DynamicChannelListener; 049import net.gdface.facelog.hb.DeviceHeartbeat; 050import net.gdface.facelog.mq.BaseServiceHeartbeatListener; 051import net.gdface.facelog.mq.ServiceHeartbeatAdapter; 052import net.gdface.facelog.mq.ServiceHeartbeatListener; 053import net.gdface.facelog.mq.ServiceHeartbeatPackage; 054import net.gdface.facelog.thrift.IFaceLogThriftClient; 055import net.gdface.facelog.thrift.IFaceLogThriftClientAsync; 056import net.gdface.thrift.ClientFactory; 057import net.gdface.utils.Delegator; 058import net.gdface.utils.NetworkUtil; 059 060/** 061 * client端便利性扩展工具类 062 * @author guyadong 063 * 064 */ 065public class ClientExtendTools{ 066 private final IFaceLog syncInstance; 067 private final IFaceLogThriftClientAsync asyncInstance; 068 private final ClientFactory factory; 069 private TokenHelper tokenHelper; 070 private volatile Map<MQParam, String> redisParameters = null; 071 private volatile Map<MQParam, String> mqParameters = null; 072 private Map<String, String> faceapiParameters = null; 073 074 private final ServiceHeartbeatListener tokenRefreshListener = new BaseServiceHeartbeatListener(){ 075 @Override 076 public boolean doServiceOnline(ServiceHeartbeatPackage heartbeatPackage){ 077 // 执行令牌刷新,更新redis参数 078 return tokenRefresh !=null && tokenRefresh.refresh(); 079 }}; 080 081 private final DispatcherListener dispatcherListener = new DispatcherListener(); 082 private TokenRefresh tokenRefresh; 083 private class TokenRefresh implements Supplier<Token>{ 084 private Token token; 085 private final Object lock = new Object(); 086 private volatile boolean relead = true; 087 private final TokenHelper helper; 088 private TokenRefresh(TokenHelper helper,Token token) { 089 this.helper = checkNotNull(helper,"helper is null"); 090 this.token = checkNotNull(token,"token is null"); 091 } 092 @Override 093 public Token get() { 094 // double check 095 if(relead){ 096 synchronized (lock) { 097 if(relead){ 098 Token t = refreshToken(helper, token); 099 if(null != t){ 100 relead = false; 101 token.assignFrom(t); 102 } 103 return t; 104 } 105 } 106 } 107 return token; 108 } 109 /** 110 * 刷新令牌和redis参数 111 * @return 刷新成功返回{@code true},否则返回{@code false} 112 */ 113 private boolean refresh(){ 114 relead = true; 115 Token token = get(); 116 if(null != token){ 117 try { 118 redisParameters = null; 119 getRedisParametersLazy(token); 120 mqParameters = null; 121 getMessageQueueParametersLazy(token); 122 faceapiParameters = null; 123 getFaceApiParametersLazy(token); 124 return true; 125 } catch (Exception e) { 126 // DO NOTHING 127 SimpleLog.log(MoreObjects.firstNonNull(e.getMessage(),e.getClass().getName())); 128 } 129 } 130 return false; 131 } 132 } 133 private static class DispatcherListener extends BaseServiceHeartbeatListener{ 134 private final Set<BaseDispatcher> dispatchers = Sets.newLinkedHashSet(); 135 @Override 136 protected boolean doServiceOnline(ServiceHeartbeatPackage heartbeatPackage) { 137 synchronized (dispatchers) { 138 for(BaseDispatcher dispatcher: dispatchers){ 139 if(dispatcher.isEnable()){ 140 dispatcher.unregister(); 141 dispatcher.register(); 142 } 143 } 144 } 145 return true; 146 } 147 boolean addDispatcher(BaseDispatcher dispatcher){ 148 synchronized (dispatchers) { 149 return dispatchers.add(dispatcher); 150 } 151 } 152/* boolean removeDispatcher(BaseDispatcher dispatcher){ 153 synchronized (dispatchers) { 154 return dispatchers.remove(dispatcher); 155 } 156 }*/ 157 } 158 public static interface ParameterSupplier<T> extends Supplier<T>,Closeable{ 159 public Object key(); 160 } 161 private class MessageQueueParameterSupplier implements ParameterSupplier<String>{ 162 private final MQParam mqParam; 163 private final Token token; 164 private MessageQueueParameterSupplier(MQParam mqParam,Token token) { 165 this.mqParam = checkNotNull(mqParam,"mqParam is null"); 166 this.token = checkNotNull(token,"token is null"); 167 } 168 @Override 169 public String get() { 170 return getMessageQueueParametersLazy(token).get(mqParam); 171 } 172 @Override 173 public Object key(){ 174 return mqParam; 175 } 176 @Override 177 public void close() { 178 } 179 } 180 private class TaskQueueSupplier extends BaseServiceHeartbeatListener implements ParameterSupplier<String>{ 181 private final String task; 182 private final Token token; 183 protected String taskQueue = null; 184 private TaskQueueSupplier(String task,Token token) { 185 this.task = checkNotNull(task,"task is null"); 186 this.token = checkNotNull(token,"token is null"); 187 addServiceEventListener(this); 188 } 189 @Override 190 public String get() { 191 if(taskQueue == null){ 192 doServiceOnline(null); 193 } 194 return taskQueue; 195 } 196 @Override 197 public Object key(){ 198 return task; 199 } 200 201 @Override 202 protected boolean doServiceOnline(ServiceHeartbeatPackage heartbeatPackage) { 203 taskQueue = taskQueueOf(task,token); 204 return true; 205 } 206 /** 207 * 当对象不再被需要时,执行此方法将其从服务心跳侦听器列表中删除 208 * @see java.io.Closeable#close() 209 */ 210 @Override 211 public void close() { 212 removeServiceEventListener(this); 213 } 214 } 215 private static <T>T unwrap(Object value,Class<T> clazz){ 216 if(Proxy.isProxyClass(value.getClass())){ 217 return unwrap(Proxy.getInvocationHandler(value),clazz); 218 }else if(value instanceof Delegator){ 219 return unwrap(((Delegator<?>)value).delegate(),clazz); 220 } 221 return clazz.cast(value); 222 } 223 ClientExtendTools(IFaceLog syncInstance) { 224 super(); 225 this.syncInstance = checkNotNull(syncInstance,"syncInstance is null"); 226 this.asyncInstance = null; 227 this.factory = unwrap(syncInstance,IFaceLogThriftClient.class).getFactory(); 228 } 229 ClientExtendTools(IFaceLogThriftClientAsync asyncInstance) { 230 super(); 231 this.syncInstance = null; 232 this.asyncInstance = checkNotNull(asyncInstance,"asyncInstance is null"); 233 this.factory = asyncInstance.getFactory(); 234 } 235 /** 236 * 如果{@code host}是本机地址则用facelog服务主机名替换 237 * @param host 238 * @return {@code host} or host in {@link #factory} 239 */ 240 public String insteadHostIfLocalhost(String host){ 241 if(NetworkUtil.isLoopbackAddress(host)){ 242 return factory.getHostAndPort().getHost(); 243 } 244 return host; 245 } 246 /** 247 * 如果{@code host}是本机地址则用facelog服务主机名替换 248 * @param host 249 * @return {@code host} or host in {@link #factory} 250 */ 251 public HostAndPort insteadHostIfLocalhost(HostAndPort host){ 252 return HostAndPort.fromParts(insteadHostIfLocalhost(host.getHost()), host.getPort()); 253 } 254 /** 255 * 如果{@code uri}的主机名是本机地址则用facelog服务主机名替换 256 * @param uri 257 * @return {@code uri} or new URI instead with host of facelog 258 */ 259 public URI insteadHostIfLocalhost(URI uri){ 260 if(null != uri && NetworkUtil.isLoopbackAddress(uri.getHost())){ 261 try { 262 return new URI(uri.getScheme(), 263 uri.getUserInfo(), 264 factory.getHostAndPort().getHost(), 265 uri.getPort(), 266 uri.getPath(), 267 uri.getQuery(), 268 uri.getFragment()); 269 } catch (URISyntaxException e) { 270 // DO NOTHING 271 } 272 } 273 return uri; 274 } 275 /** 276 * 如果{@code url}的主机名是本机地址则用facelog服务主机名替换 277 * @param url 278 * @return {@code url} or new URI instead with host of facelog 279 */ 280 public URL insteadHostIfLocalhost(URL url){ 281 if(null != url && NetworkUtil.isLoopbackAddress(url.getHost())){ 282 try { 283 return new URL(url.getProtocol(), 284 factory.getHostAndPort().getHost(), 285 url.getPort(), 286 url.getFile()); 287 } catch (MalformedURLException e) { 288 // DO NOTHING 289 } 290 } 291 return url; 292 } 293 /** 294 * 如果{@code json}中的值为URI,且主机名是本机地址则用facelog服务主机名替换 295 * @param json 296 * @return {@code json} or new json string instead with host of facelog 297 */ 298 public String insteadHostJsonIfLocalhost(String json){ 299 if(!Strings.isNullOrEmpty(json)){ 300 Map<String, String> map = MessageQueueFactorys.asMQConnParam2(json); 301 for(Entry<String, String> entry : map.entrySet()){ 302 try { 303 URI uri = new URI (entry.getValue()); 304 URI insteaded = insteadHostIfLocalhost(uri); 305 entry.setValue(insteaded.toString()); 306 } catch (URISyntaxException e) { 307 // 不是URI则跳过 308 } 309 } 310 return BaseJsonEncoder.getEncoder().toJsonString(map); 311 } 312 return json; 313 } 314 /** 315 * 如果{@code uri}的主机名是本机地址则用facelog服务主机名替换 316 * @param uri 317 * @return {@code uri} or new URI string instead with host of facelog 318 */ 319 public String insteadHostOfURIIfLocalhost(String uri){ 320 try { 321 return Strings.isNullOrEmpty(uri) ? uri : insteadHostIfLocalhost(new URI(uri)).toString(); 322 } catch (URISyntaxException e) { 323 // DO NOTHING 324 } 325 return uri; 326 } 327 /** 328 * 如果{@code url}的主机名是本机地址则用facelog服务主机名替换 329 * @param url 330 * @return {@code url} or new URL string instead with host of facelog 331 */ 332 public String insteadHostOfURLIfLocalhost(String url){ 333 try { 334 return Strings.isNullOrEmpty(url) ? url : insteadHostIfLocalhost(new URL(url)).toString(); 335 } catch (MalformedURLException e) { 336 // DO NOTHING 337 } 338 return url; 339 } 340 341 /** 342 * 如果{@code parameters}的参数({@link MQParam#REDIS_URI},{@link MQParam#WEBREDIS_URL}, 343 * 或{@link MQParam#MQ_CONNECT} (JSON)中的值)是本机地址则用facelog服务主机名替换 344 * @param parameters 345 * @return 替换主机名参数后的新对象 {@link java.util.HashMap} 346 */ 347 public Map<MQParam, String> insteadHostOfMQParamIfLocalhost(Map<MQParam, String> parameters) { 348 if(parameters != null){ 349 Map<MQParam, String> out = Maps.newHashMap(); 350 for (Entry<MQParam, String> entry : parameters.entrySet()) { 351 MQParam key = entry.getKey(); 352 String value = entry.getValue(); 353 if(MQParam.REDIS_URI.equals(key)){ 354 String insteaded = insteadHostOfURIIfLocalhost(value); 355 out.put(key, insteaded); 356 }else if(MQParam.WEBREDIS_URL.equals(key)){ 357 String insteaded = insteadHostOfURLIfLocalhost(value); 358 out.put(key, insteaded); 359 }else if(MQParam.MQ_CONNECT.equals(key)){ 360 String insteaded = insteadHostJsonIfLocalhost(value); 361 out.put(key, insteaded); 362 }else{ 363 out.put(key,value); 364 } 365 } 366 return out; 367 } 368 return parameters; 369 } 370 /** 371 * 如果{@code parameters}的值(host:port格式)是本机地址则用facelog服务主机名替换 372 * @param parameters 373 * @return 替换主机名参数后的新对象 {@link java.util.HashMap} 374 * @see #insteadHostIfLocalhost(HostAndPort) 375 */ 376 public Map<String, String> insteadHostOfValueIfLocalhost(Map<String, String>parameters){ 377 if(parameters != null){ 378 Map<String, String> out = Maps.newHashMap(); 379 for( Entry<String, String> entry:parameters.entrySet()){ 380 String value = entry.getValue(); 381 HostAndPort insteaded = insteadHostIfLocalhost(HostAndPort.fromString(value)); 382 out.put(entry.getKey(), insteaded.toString()); 383 return out; 384 } 385 } 386 return parameters; 387 } 388 /** 389 * 根据设备ID返回设备所属的设备组ID的{@code Function}实例, 390 * 设备ID无效则返回{@code null} 391 */ 392 public final Function<Integer,Integer> deviceGroupIdGetter = 393 new Function<Integer,Integer>(){ 394 @Override 395 public Integer apply(Integer input) { 396 try{ 397 DeviceBean device = syncInstance != null 398 ? syncInstance.getDevice(input) 399 : asyncInstance.getDevice(input).get(); 400 return null == device ? null : device.getGroupId(); 401 } catch (ExecutionException e) { 402 Throwables.throwIfUnchecked(e.getCause()); 403 throw new RuntimeException(e.getCause()); 404 } catch(Exception e){ 405 Throwables.throwIfUnchecked(e); 406 throw new RuntimeException(e); 407 } 408 }}; 409 /** 410 * 根据设备ID返回一个获取设备组ID的{@code Supplier}实例 411 * @param deviceId 412 * @return 对应的groupId,如果{@code deviceId}无效则返回{@code null} 413 * @see #deviceGroupIdGetter 414 */ 415 public Supplier<Integer> getDeviceGroupIdSupplier(final int deviceId){ 416 return new Supplier<Integer>(){ 417 @Override 418 public Integer get() { 419 return deviceGroupIdGetter.apply(deviceId); 420 } 421 }; 422 } 423 /** 424 * 根据人员ID返回人员所属的所有组ID的{@code Function}实例 425 * 如果人员ID无效则返回空表 426 */ 427 public final Function<Integer,List<Integer>> personGroupBelonsGetter = 428 new Function<Integer,List<Integer>>(){ 429 @Override 430 public List<Integer> apply(Integer personId) { 431 try{ 432 return syncInstance != null 433 ? syncInstance.getPersonGroupsBelongs(personId) 434 : asyncInstance.getPersonGroupsBelongs(personId).get(); 435 } catch (ExecutionException e) { 436 Throwables.throwIfUnchecked(e.getCause()); 437 throw new RuntimeException(e.getCause()); 438 } catch(Exception e){ 439 Throwables.throwIfUnchecked(e); 440 throw new RuntimeException(e); 441 } 442 }}; 443 /** 444 * 根据人员ID返回一个获取所属组ID列表的{@code Supplier}实例 445 * @param personId 446 * @return 人员组ID列表,如果{@code personId}无效则返回空表 447 * @see #personGroupBelonsGetter 448 */ 449 public Supplier<List<Integer>> getPersonGroupBelonsSupplier(final int personId){ 450 return new Supplier<List<Integer>>(){ 451 @Override 452 public List<Integer> get() { 453 return personGroupBelonsGetter.apply(personId); 454 } 455 }; 456 } 457 /** 458 * (管理端)创建{@link CmdManager}实例<br> 459 * @param token 访问令牌(person Token or root Token) 460 * @return {@link CmdManager}实例 461 */ 462 public CmdManager makeCmdManager(Token token){ 463 try{ 464 checkArgument(checkNotNull(token,"token is null").getType() == TokenType.PERSON 465 || token.getType() == TokenType.ROOT,"person or root token required"); 466 checkArgument(tokenRank.apply(token)>=2,"person or root token required"); 467 IMessageQueueFactory mqFactory = MessageQueueFactorys.getDefaultFactory(); 468 return new CmdManager( 469 mqFactory.getPublisher(), 470 mqFactory.getSubscriber(), 471 getCmdChannelSupplier(token)) 472 /** 设置命令序列号 */ 473 .setCmdSn(getCmdSnSupplier(token)) 474 /** 设置命令响应通道 */ 475 .setAckChannel(getAckChannelSupplier(token)) 476 .self(); 477 } catch(Exception e){ 478 Throwables.throwIfUnchecked(e); 479 throw new RuntimeException(e); 480 } 481 } 482 /** 483 * (管理端)创建{@link TaskManager}实例<br> 484 * @param token 访问令牌(person Token or root Token) 485 * @param cmdpath 设备(菜单)命令路径 486 * @param taskQueueSupplier 任务队列 487 * @return {@link TaskManager}实例 488 */ 489 public TaskManager makeTaskManager(Token token, String cmdpath, Supplier<String> taskQueueSupplier){ 490 try{ 491 checkArgument(checkNotNull(token,"token is null").getType() == TokenType.PERSON 492 || token.getType() == TokenType.ROOT,"person or root token required"); 493 checkArgument(tokenRank.apply(token)>=2,"person or root token required"); 494 IMessageQueueFactory mqFactory = MessageQueueFactorys.getDefaultFactory(); 495 return new TaskManager( 496 mqFactory.getPublisher(), 497 mqFactory.getSubscriber(), 498 mqFactory.getProducer(), 499 cmdpath, taskQueueSupplier) 500 /** 设置命令序列号 */ 501 .setCmdSn(getCmdSnSupplier(token)) 502 /** 设置命令响应通道 */ 503 .setAckChannel(getAckChannelSupplier(token)) 504 .self(); 505 } catch(Exception e){ 506 Throwables.throwIfUnchecked(e); 507 throw new RuntimeException(e); 508 } 509 } 510 /** 511 * (设备端)创建设备多目标命令分发器<br> 512 * @param token 设备令牌 513 * @return {@link CmdDispatcher}实例 514 */ 515 public CmdDispatcher makeCmdDispatcher(Token token){ 516 try{ 517 checkArgument(checkNotNull(token,"token is null").getType() == TokenType.DEVICE,"device token required"); 518 int deviceId = token.getId(); 519 IMessageQueueFactory mqFactory = MessageQueueFactorys.getDefaultFactory(); 520 CmdDispatcher dispatcher = new CmdDispatcher(deviceId, mqFactory.getPublisher(), mqFactory.getSubscriber()) 521 .setGroupIdSupplier(this.getDeviceGroupIdSupplier(deviceId)) 522 .setCmdSnValidator(cmdSnValidator) 523 .setChannelSupplier(new MessageQueueParameterSupplier(MQParam.CMD_CHANNEL,token)) 524 .register() 525 .self(); 526 dispatcherListener.addDispatcher(dispatcher); 527 return dispatcher; 528 } catch(Exception e){ 529 Throwables.throwIfUnchecked(e); 530 throw new RuntimeException(e); 531 } 532 } 533 /** 534 * (设备端)创建设备任务分发器<br> 535 * @param token 设备令牌 536 * @param taskQueueSupplier 任务队列名,创建的分发器对象注册到任务队列,可为{@code null} 537 * @return {@link TaskDispatcher}实例 538 */ 539 public TaskDispatcher makeTaskDispatcher(Token token,Supplier<String> taskQueueSupplier){ 540 try{ 541 checkArgument(checkNotNull(token,"token is null").getType() == TokenType.DEVICE,"device token required"); 542 int deviceId = token.getId(); 543 IMessageQueueFactory mqFactory = MessageQueueFactorys.getDefaultFactory(); 544 TaskDispatcher dispatcher = new TaskDispatcher(deviceId, 545 mqFactory.getPublisher(), mqFactory.getConsumer()) 546 .setCmdSnValidator(cmdSnValidator) 547 .setChannelSupplier(taskQueueSupplier) 548 .register() 549 .self(); 550 dispatcherListener.addDispatcher(dispatcher); 551 return dispatcher; 552 } catch(Exception e){ 553 Throwables.throwIfUnchecked(e); 554 throw new RuntimeException(e); 555 } 556 } 557 /** 558 * 返回一个申请命令响应通道的{@link Supplier}实例 559 * @param duration 命令通道有效时间(秒) 大于0有效,否则使用默认的有效期 560 * @param token 访问令牌 561 * @return {@link Supplier}实例 562 */ 563 public Supplier<String> 564 getAckChannelSupplier(final int duration,final Token token){ 565 return new Supplier<String>(){ 566 @Override 567 public String get() { 568 try{ 569 return syncInstance != null 570 ? syncInstance.applyAckChannel(duration,token) 571 : asyncInstance.applyAckChannel(duration,token).get(); 572 } catch (ExecutionException e) { 573 Throwables.throwIfUnchecked(e.getCause()); 574 throw new RuntimeException(e.getCause()); 575 } catch(Exception e){ 576 Throwables.throwIfUnchecked(e); 577 throw new RuntimeException(e); 578 } 579 } 580 }; 581 } 582 /** 583 * 返回一个申请命令响应通道的{@link Supplier}实例 584 * @param token 访问令牌 585 * @return {@link Supplier}实例 586 */ 587 public Supplier<String> 588 getAckChannelSupplier(final Token token){ 589 return getAckChannelSupplier(0,token); 590 } 591 /** 592 * 返回一个申请命令序号的{@code Supplier}实例 593 * @param token 594 * @return {@code Supplier}实例 595 */ 596 public Supplier<Integer> 597 getCmdSnSupplier(final Token token){ 598 return new Supplier<Integer>(){ 599 @Override 600 public Integer get() { 601 try{ 602 return syncInstance != null 603 ? syncInstance.applyCmdSn(token) 604 : asyncInstance.applyCmdSn(token).get(); 605 } catch (ExecutionException e) { 606 Throwables.throwIfUnchecked(e.getCause()); 607 throw new RuntimeException(e.getCause()); 608 } catch(Exception e){ 609 Throwables.throwIfUnchecked(e); 610 throw new RuntimeException(e); 611 } 612 } 613 }; 614 } 615 /** 616 * 返回一个获取设备命令通道名的{@code Supplier}实例 617 * @param token 618 * @return {@code Supplier}实例 619 */ 620 public Supplier<String> 621 getCmdChannelSupplier(final Token token){ 622 return new Supplier<String>(){ 623 @Override 624 public String get() { 625 try{ 626 return getRedisParametersLazy(token).get(MQParam.CMD_CHANNEL); 627 } catch(Exception e){ 628 Throwables.throwIfUnchecked(e); 629 throw new RuntimeException(e); 630 } 631 } 632 }; 633 } 634 /** 设备命令序列号验证器 */ 635 public final Predicate<Integer> cmdSnValidator = 636 new Predicate<Integer>(){ 637 @Override 638 public boolean apply(Integer input) { 639 try{ 640 return null == input ? false 641 : (syncInstance != null 642 ? syncInstance.isValidCmdSn(input) 643 : asyncInstance.isValidCmdSn(input).get()); 644 } catch (ExecutionException e) { 645 Throwables.throwIfUnchecked(e.getCause()); 646 throw new RuntimeException(e.getCause()); 647 } catch(Exception e){ 648 Throwables.throwIfUnchecked(e); 649 throw new RuntimeException(e); 650 } 651 }}; 652 /** 设备命令响应通道验证器 */ 653 public final Predicate<String> ackChannelValidator = 654 new Predicate<String>(){ 655 @Override 656 public boolean apply(String input) { 657 try{ 658 return null == input || input.isEmpty() ? false 659 : (syncInstance != null 660 ? syncInstance.isValidAckChannel(input) 661 : asyncInstance.isValidAckChannel(input).get()); 662 } catch (ExecutionException e) { 663 Throwables.throwIfUnchecked(e.getCause()); 664 throw new RuntimeException(e.getCause()); 665 } catch(Exception e){ 666 Throwables.throwIfUnchecked(e); 667 throw new RuntimeException(e); 668 } 669 }}; 670 /** 设备令牌验证器 */ 671 public final Predicate<Token> deviceTokenValidator = 672 new Predicate<Token>(){ 673 @Override 674 public boolean apply(Token input) { 675 try{ 676 return null == input ? false 677 : (syncInstance != null 678 ? syncInstance.isValidDeviceToken(input) 679 : asyncInstance.isValidDeviceToken(input).get()); 680 } catch (ExecutionException e) { 681 Throwables.throwIfUnchecked(e.getCause()); 682 throw new RuntimeException(e.getCause()); 683 } catch(Exception e){ 684 Throwables.throwIfUnchecked(e); 685 throw new RuntimeException(e); 686 } 687 }}; 688 /** 人员令牌验证器 */ 689 public final Predicate<Token> personTokenValidator = 690 new Predicate<Token>(){ 691 @Override 692 public boolean apply(Token input) { 693 try{ 694 return null == input ? false 695 : (syncInstance != null 696 ? syncInstance.isValidPersonToken(input) 697 : asyncInstance.isValidPersonToken(input).get()); 698 } catch (ExecutionException e) { 699 Throwables.throwIfUnchecked(e.getCause()); 700 throw new RuntimeException(e.getCause()); 701 } catch(Exception e){ 702 Throwables.throwIfUnchecked(e); 703 throw new RuntimeException(e); 704 } 705 }}; 706 /** root令牌验证器 */ 707 public final Predicate<Token> rootTokenValidator = 708 new Predicate<Token>(){ 709 @Override 710 public boolean apply(Token input) { 711 try{ 712 return null == input ? false 713 : (syncInstance != null 714 ? syncInstance.isValidRootToken(input) 715 : asyncInstance.isValidRootToken(input).get()); 716 } catch (ExecutionException e) { 717 Throwables.throwIfUnchecked(e.getCause()); 718 throw new RuntimeException(e.getCause()); 719 } catch(Exception e){ 720 Throwables.throwIfUnchecked(e); 721 throw new RuntimeException(e); 722 } 723 }}; 724 /** 725 * 返回令牌的管理等级,输入为{@code null}或设备令牌返回-1 726 * <li> 4---root</li> 727 * <li> 3---管理员</li> 728 * <li> 2---操作员</li> 729 * <li> 0---普通用户</li> 730 * <li>-1---未定义</li> 731 */ 732 public final Function<Token,Integer> tokenRank = 733 new Function<Token,Integer>(){ 734 @Override 735 public Integer apply(Token input) { 736 try{ 737 if(null != input){ 738 switch (input.getType()) { 739 case ROOT: 740 if(rootTokenValidator.apply(input)){ 741 return 4; 742 } 743 break; 744 case PERSON:{ 745 if(personTokenValidator.apply(input)){ 746 PersonBean bean = (syncInstance != null 747 ? syncInstance.getPerson(input.getId()) 748 : asyncInstance.getPerson(input.getId()).get()); 749 Integer rank = bean.getRank(); 750 return rank != null ? rank : 0; 751 } 752 break; 753 } 754 default: 755 break; 756 } 757 } 758 return -1; 759 } catch (ExecutionException e) { 760 Throwables.throwIfUnchecked(e.getCause()); 761 throw new RuntimeException(e.getCause()); 762 } catch(Exception e){ 763 Throwables.throwIfUnchecked(e); 764 throw new RuntimeException(e); 765 } 766 }}; 767 /** 768 * 申请用户令牌 769 * @param userid 用户id,为-1代表root 770 * @param password 用户密码 771 * @param isMd5 密码是否为md5校验码 772 * @return 773 * @throws ServiceSecurityException 774 */ 775 private Token applyUserToken(int userid,String password,boolean isMd5) throws ServiceSecurityException{ 776 Token token; 777 try { 778 token = syncInstance != null 779 ? syncInstance.applyUserToken(userid, password, isMd5) 780 : asyncInstance.applyUserToken(userid, password, isMd5).get(); 781 return token; 782 } catch (ExecutionException e) { 783 Throwables.propagateIfPossible(e.getCause(), ServiceSecurityException.class); 784 throw new RuntimeException(e.getCause()); 785 } catch(Exception e){ 786 Throwables.propagateIfPossible(e, ServiceSecurityException.class); 787 throw new RuntimeException(e); 788 } 789 } 790 /** 791 * 获取redis连接参数 792 * @param token 793 * @return redis连接参数名-参数值映射 794 */ 795 private Map<MQParam, String> getRedisParametersLazy(Token token){ 796 // double check 797 if(redisParameters == null){ 798 synchronized (this) { 799 if(redisParameters == null){ 800 checkArgument(token != null,"token is null"); 801 // 获取redis连接参数 802 try { 803 Map<MQParam, String> param = syncInstance != null 804 ? syncInstance.getRedisParameters(token) 805 : asyncInstance.getRedisParameters(token).get(); 806 redisParameters = insteadHostOfMQParamIfLocalhost(param); 807 } catch (ExecutionException e) { 808 Throwables.throwIfUnchecked(e.getCause()); 809 throw new RuntimeException(e.getCause()); 810 } catch(Exception e){ 811 Throwables.throwIfUnchecked(e); 812 throw new RuntimeException(e); 813 } 814 } 815 } 816 } 817 return redisParameters; 818 } 819 /** 820 * @param token 调用 {@link #getRedisParametersLazy(Token)}所需要的令牌 821 * @return 返回一个获取redis参数的{@link Supplier}实例 822 */ 823 public Supplier<Map<MQParam, String>> getRedisParametersSupplier(final Token token){ 824 checkArgument(token != null,"token is null"); 825 return new Supplier<Map<MQParam, String>>(){ 826 827 @Override 828 public Map<MQParam, String> get() { 829 return getRedisParametersLazy(token); 830 }}; 831 } 832 /** 833 834 * 获取消息系统连接参数 835 * @param token 836 * @return 消息系统连接参数名-参数值映射 837 */ 838 private Map<MQParam, String> getMessageQueueParametersLazy(Token token){ 839 // double check 840 if(mqParameters == null){ 841 synchronized (this) { 842 if(mqParameters == null){ 843 checkArgument(token != null,"token is null"); 844 // 获取redis连接参数 845 try { 846 Map<MQParam, String> param = syncInstance != null 847 ? syncInstance.getMessageQueueParameters(token) 848 : asyncInstance.getMessageQueueParameters(token).get(); 849 mqParameters = insteadHostOfMQParamIfLocalhost(param); 850 } catch (ExecutionException e) { 851 Throwables.throwIfUnchecked(e.getCause()); 852 throw new RuntimeException(e.getCause()); 853 } catch(Exception e){ 854 Throwables.throwIfUnchecked(e); 855 throw new RuntimeException(e); 856 } 857 } 858 } 859 } 860 return mqParameters; 861 } 862 /** 863 * @param token 调用 {@link #getMessageQueueParametersLazy(Token)}所需要的令牌 864 * @return 返回一个获取消息系统参数的{@link Supplier}实例 865 */ 866 public Supplier<Map<MQParam, String>> getMessageQueueParametersSupplier(final Token token){ 867 checkArgument(token != null,"token is null"); 868 return new Supplier<Map<MQParam, String>>(){ 869 870 @Override 871 public Map<MQParam, String> get() { 872 return getMessageQueueParametersLazy(token); 873 }}; 874 } 875 /** 876 * 获取faceapi连接参数 877 * @param token 878 * @return 879 */ 880 private Map<String, String> getFaceApiParametersLazy(Token token){ 881 if(faceapiParameters == null){ 882 checkArgument(token != null,"token is null"); 883 // 获取faceapi连接参数 884 try { 885 Map<String, String> param = syncInstance != null 886 ? syncInstance.getFaceApiParameters(token) 887 : asyncInstance.getFaceApiParameters(token).get(); 888 faceapiParameters = insteadHostOfValueIfLocalhost(param); 889 } catch (ExecutionException e) { 890 Throwables.throwIfUnchecked(e.getCause()); 891 throw new RuntimeException(e.getCause()); 892 } catch(Exception e){ 893 Throwables.throwIfUnchecked(e); 894 throw new RuntimeException(e); 895 } 896 } 897 return faceapiParameters; 898 } 899 /** 900 * @param token 调用 {@link #getFaceApiParametersLazy(Token)}所需要的令牌 901 * @return 返回一个获取redis参数的{@link Supplier}实例 902 */ 903 public Supplier<Map<String, String>> getFaceApiParametersSupplier(final Token token){ 904 checkArgument(token != null,"token is null"); 905 return new Supplier<Map<String, String>>(){ 906 907 @Override 908 public Map<String, String> get() { 909 return getFaceApiParametersLazy(token); 910 }}; 911 } 912 /** 913 * @param mqParam 914 * @param token 调用 {@link #getRedisParametersLazy(Token)}所需要的令牌 915 * @return 返回一个动态获取指定消息参数的{@link Supplier}实例 916 */ 917 public Supplier<String> getDynamicParamSupplier(final MQParam mqParam,Token token){ 918 checkNotNull(mqParam,"mqParam is null"); 919 return Suppliers.compose(new Function<Map<MQParam, String>,String>(){ 920 @Override 921 public String apply(Map<MQParam, String> input) { 922 return input.get(mqParam); 923 } 924 }, getRedisParametersSupplier(token)); 925 } 926 /** 927 * 创建dtalk引擎 928 * @param deviceToken 设备令牌,不可为{@code null} 929 * @param rootMenu 包括所有菜单的根菜单对象,不可为{@code null} 930 * @return {@link DtalkEngineForFacelog}实例 931 */ 932 public DtalkEngineForFacelog initDtalkEngine(Token deviceToken, MenuItem rootMenu){ 933 // 设备端才能调用此方法 934 checkArgument(deviceTokenValidator.apply(deviceToken),"device token REQUIRED"); 935 initMQDefaultFactory(deviceToken); 936 IMessageQueueFactory mqFactory = MessageQueueFactorys.getDefaultFactory(); 937 return new DtalkEngineForFacelog(checkNotNull(rootMenu,"rootMenu is null"), tokenRank,mqFactory); 938 } 939 940 /** 941 * 从facelog获取消息系统参数,初始化消息系统的默认实例<br> 942 * 该方法只能在应用启动时调用一次 943 * @param token 944 */ 945 public void initMQDefaultFactory(Token token){ 946 Map<MQParam, String> mqParam = getMessageQueueParametersLazy(token); 947 MessageQueueFactorys.getFactory(mqParam.get(MQParam.MQ_TYPE)) 948 .init(mqParam.get(MQParam.MQ_CONNECT)) 949 .asDefaultFactory(); 950 } 951 private Token online(DeviceBean deviceBean) throws ServiceSecurityException{ 952 try{ 953 return syncInstance != null 954 ? syncInstance.online(deviceBean) 955 : asyncInstance.online(deviceBean).get(); 956 } catch (ExecutionException e) { 957 Throwables.propagateIfPossible(e.getCause(), ServiceSecurityException.class); 958 throw new RuntimeException(e.getCause()); 959 } catch(Exception e){ 960 Throwables.propagateIfPossible(e, ServiceSecurityException.class); 961 throw new RuntimeException(e); 962 } 963 } 964 965 /** 966 * 令牌刷新 967 * @param helper 968 * @param token 969 * @return 刷新的令牌,刷新失败返回{@code null} 970 */ 971 public Token refreshToken(TokenHelper helper,Token token){ 972 // 最后一个参数为token 973 Token freshToken = null; 974 try{ 975 // 重新申请令牌 976 switch(token.getType()){ 977 case DEVICE:{ 978 if(helper.deviceBean() != null){ 979 freshToken = online(helper.deviceBean()); 980 } 981 break; 982 } 983 case ROOT: 984 case PERSON:{ 985 String pwd = helper.passwordOf(token.getId()); 986 if(pwd != null){ 987 freshToken = applyUserToken(token.getId(),pwd,helper.isHashedPwd()); 988 } 989 break; 990 } 991 default: 992 break; 993 } 994 // 如果申请令牌成功则更新令牌参数,重新执行方法调用 995 if(freshToken != null){ 996 // 用申请的新令牌更新参数 997 helper.saveFreshedToken(freshToken); 998 } 999 }catch (Exception er) { 1000 // DO NOTHING 1001 } 1002 return freshToken; 1003 } 1004 1005 /** 1006 * 返回有效令牌的{@link Supplier}实例<br> 1007 * @return {@link Supplier}实例 1008 */ 1009 public Supplier<Token> getTokenSupplier(){ 1010 return checkNotNull(this.tokenRefresh,"tokenRefresh field must be initialized by startServiceHeartbeatListener firstly"); 1011 } 1012 public ClientExtendTools addServiceEventListener(ServiceHeartbeatListener listener){ 1013 ServiceHeartbeatAdapter.INSTANCE.addServiceEventListener(listener); 1014 return this; 1015 } 1016 public ClientExtendTools removeServiceEventListener(ServiceHeartbeatListener listener){ 1017 ServiceHeartbeatAdapter.INSTANCE.removeServiceEventListener(listener); 1018 return this; 1019 } 1020 1021 /** 1022 * 创建动态频道名侦听对象 1023 * 动态频道是指定当服务重启后,频道名会动态改变的频道, 1024 * 对于这种频道,通过侦听服务心跳判断服务是否重启,如果重启则重新获取频道名继续保持侦听 1025 * @param listener 1026 * @param channelType 频道消息数据类型 1027 * @param mqParam 参数名 1028 * @param token 1029 * @param factory 消息系统工厂类实例 1030 * @return 返回{@link DynamicChannelListener}实例 1031 */ 1032 public <T>DynamicChannelListener<T> makeDynamicChannelListener(IMessageAdapter<T> listener,Class<T> channelType, MQParam mqParam, Token token, IMessageQueueFactory factory) { 1033 DynamicChannelListener<T> monitor = new DynamicChannelListener<T>( 1034 listener, 1035 channelType, 1036 getDynamicParamSupplier(mqParam, token), factory); 1037 addServiceEventListener(monitor); 1038 return monitor; 1039 } 1040 /** 1041 * 创建设备心跳包发送对象<br> 1042 * {@link DeviceHeartbeat}为单实例,该方法只能调用一次 1043 * @param deviceID 设备ID 1044 * @param token 设备令牌 1045 * @return {@link DeviceHeartbeat}实例 1046 */ 1047 public DeviceHeartbeat makeHeartbeat(int deviceID,Token token){ 1048 DeviceHeartbeat heartbeat = DeviceHeartbeat.makeHeartbeat(deviceID) 1049 /** 将设备心跳包数据发送到指定的设备心跳监控通道名,否则监控端无法收到设备心跳包 */ 1050 .setMonitorChannelSupplier(getDynamicParamSupplier(MQParam.HB_MONITOR_CHANNEL,token)); 1051 addServiceEventListener(heartbeat); 1052 return heartbeat; 1053 } 1054 /** 1055 * @param tokenHelper 要设置的 tokenHelper 1056 * @return 当前{@link ClientExtendTools}实例 1057 */ 1058 public ClientExtendTools setTokenHelper(TokenHelper tokenHelper) { 1059 this.tokenHelper = tokenHelper; 1060 return this; 1061 } 1062 /** 1063 * 启动服务心跳侦听器<br> 1064 * 启动侦听器后CLIENT端才能感知服务端断线,并执行相应动作。 1065 * 调用前必须先执行{@link #setTokenHelper(TokenHelper)}初始化 1066 * @param token 令牌 1067 * @param initMQDefaultFactoryInstance 是否初始化 {@link IMessageQueueFactory}默认实例 1068 * @return 返回当前{@link ClientExtendTools}实例 1069 */ 1070 public ClientExtendTools startServiceHeartbeatListener(Token token,boolean initMQDefaultFactoryInstance){ 1071 checkState(tokenHelper != null,"tokenHelper field must be initialized by setTokenHelper firstly"); 1072 this.tokenRefresh = new TokenRefresh(tokenHelper, token); 1073 ISubscriber subscriber; 1074 if(initMQDefaultFactoryInstance){ 1075 initMQDefaultFactory(token); 1076 subscriber = MessageQueueFactorys.getDefaultFactory().getSubscriber(); 1077 }else{ 1078 Map<MQParam, String> mqParam = getMessageQueueParametersLazy(token); 1079 IMessageQueueFactory mqFactory = MessageQueueFactorys.getFactory(mqParam.get(MQParam.MQ_TYPE)).init(mqParam.get(MQParam.MQ_CONNECT)); 1080 subscriber = mqFactory.getSubscriber(); 1081 } 1082 subscriber.register(ServiceHeartbeatAdapter.SERVICE_HB_CHANNEL); 1083 addServiceEventListener(tokenRefreshListener); 1084 addServiceEventListener(dispatcherListener); 1085 return this; 1086 } 1087 private String taskQueueOf(String task,Token token) { 1088 checkArgument(token != null,"token is null"); 1089 try { 1090 return syncInstance != null 1091 ? syncInstance.taskQueueOf(task, token) 1092 : asyncInstance.taskQueueOf(task,token).get(); 1093 } catch (ExecutionException e) { 1094 Throwables.throwIfUnchecked(e.getCause()); 1095 throw new RuntimeException(e.getCause()); 1096 } catch(Exception e){ 1097 Throwables.throwIfUnchecked(e); 1098 throw new RuntimeException(e); 1099 } 1100 } 1101 public ParameterSupplier<String> getTaskQueueSupplier(String task,Token token){ 1102 return new TaskQueueSupplier(task,token); 1103 } 1104 public ParameterSupplier<String> getSdkTaskQueueSupplier(String task,String sdkVersion,Token token){ 1105 return new TaskQueueSupplier(checkNotNull(task,"task is null") + checkNotNull(sdkVersion,"sdkVersion is null"),token); 1106 } 1107 public ClientFactory getFactory() { 1108 return factory; 1109 } 1110}