From 2e45c9e71d4a2b2c52cabd4ff87201c4e70fd76e Mon Sep 17 00:00:00 2001 From: rongchao Date: Thu, 5 Sep 2019 10:35:02 +0800 Subject: [PATCH 01/10] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E9=9B=86=E6=88=90?= =?UTF-8?q?=E5=88=B0api=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- esua-epdc/epdc-commons/epdc-wx/.editorconfig | 14 ++ esua-epdc/epdc-commons/epdc-wx/.travis.yml | 13 ++ esua-epdc/epdc-commons/epdc-wx/README.md | 58 +++++++ esua-epdc/epdc-commons/epdc-wx/pom.xml | 61 +++++++ .../epdc/wx/mp/builder/AbstractBuilder.java | 17 ++ .../esua/epdc/wx/mp/builder/ImageBuilder.java | 24 +++ .../esua/epdc/wx/mp/builder/TextBuilder.java | 22 +++ .../epdc/wx/mp/config/WxMpConfiguration.java | 113 +++++++++++++ .../epdc/wx/mp/config/WxMpProperties.java | 46 +++++ .../epdc/wx/mp/handler/AbstractHandler.java | 12 ++ .../epdc/wx/mp/handler/KfSessionHandler.java | 25 +++ .../epdc/wx/mp/handler/LocationHandler.java | 44 +++++ .../esua/epdc/wx/mp/handler/LogHandler.java | 25 +++ .../esua/epdc/wx/mp/handler/MenuHandler.java | 36 ++++ .../esua/epdc/wx/mp/handler/MsgHandler.java | 52 ++++++ .../esua/epdc/wx/mp/handler/NullHandler.java | 24 +++ .../esua/epdc/wx/mp/handler/ScanHandler.java | 25 +++ .../mp/handler/StoreCheckNotifyHandler.java | 27 +++ .../epdc/wx/mp/handler/SubscribeHandler.java | 71 ++++++++ .../wx/mp/handler/UnsubscribeHandler.java | 27 +++ .../esua/epdc/wx/mp/utils/JsonUtils.java | 16 ++ esua-epdc/epdc-commons/pom.xml | 2 +- .../epdc-api/epdc-api-client/pom.xml | 30 ++++ .../epdc-api/epdc-api-server/pom.xml | 100 +++++++++++ .../com/elink/esua/epdc/ApiApplication.java | 6 +- .../esua/epdc/wx/controller/WxController.java | 33 ++++ .../elink/esua/epdc/wx/service/WxService.java | 20 +++ .../epdc/wx/service/impl/WxServiceImpl.java | 38 +++++ .../src/main/resources/application.yml | 28 +-- .../epdc-app/epdc-app-client/pom.xml | 53 ------ .../java/com/elink/esua/epdc/epdc.gitkeep | 0 .../epdc-app/epdc-app-server/pom.xml | 146 ---------------- .../com/elink/esua/epdc/AppApplication.java | 31 ---- .../esua/epdc/config/ModuleConfigImpl.java | 26 --- .../com/elink/esua/epdc/config/WebConfig.java | 34 ---- .../src/main/resources/application.yml | 68 -------- .../main/resources/i18n/messages.properties | 0 .../resources/i18n/messages_en_US.properties | 0 .../resources/i18n/messages_zh_CN.properties | 0 .../resources/i18n/messages_zh_TW.properties | 0 .../main/resources/i18n/validation.properties | 0 .../i18n/validation_en_US.properties | 0 .../i18n/validation_zh_CN.properties | 0 .../i18n/validation_zh_TW.properties | 0 .../src/main/resources/logback-spring.xml | 159 ------------------ .../src/main/resources/registry.conf | 21 --- esua-epdc/epdc-module/epdc-app/pom.xml | 20 --- esua-epdc/epdc-module/pom.xml | 1 - 48 files changed, 995 insertions(+), 573 deletions(-) create mode 100644 esua-epdc/epdc-commons/epdc-wx/.editorconfig create mode 100644 esua-epdc/epdc-commons/epdc-wx/.travis.yml create mode 100644 esua-epdc/epdc-commons/epdc-wx/README.md create mode 100644 esua-epdc/epdc-commons/epdc-wx/pom.xml create mode 100644 esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/builder/AbstractBuilder.java create mode 100644 esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/builder/ImageBuilder.java create mode 100644 esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/builder/TextBuilder.java create mode 100644 esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/config/WxMpConfiguration.java create mode 100644 esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/config/WxMpProperties.java create mode 100644 esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/AbstractHandler.java create mode 100644 esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/KfSessionHandler.java create mode 100644 esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/LocationHandler.java create mode 100644 esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/LogHandler.java create mode 100644 esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/MenuHandler.java create mode 100644 esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/MsgHandler.java create mode 100644 esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/NullHandler.java create mode 100644 esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/ScanHandler.java create mode 100644 esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/StoreCheckNotifyHandler.java create mode 100644 esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/SubscribeHandler.java create mode 100644 esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/UnsubscribeHandler.java create mode 100644 esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/utils/JsonUtils.java create mode 100644 esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/wx/controller/WxController.java create mode 100644 esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/wx/service/WxService.java create mode 100644 esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/wx/service/impl/WxServiceImpl.java delete mode 100644 esua-epdc/epdc-module/epdc-app/epdc-app-client/pom.xml delete mode 100644 esua-epdc/epdc-module/epdc-app/epdc-app-client/src/main/java/com/elink/esua/epdc/epdc.gitkeep delete mode 100644 esua-epdc/epdc-module/epdc-app/epdc-app-server/pom.xml delete mode 100644 esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/java/com/elink/esua/epdc/AppApplication.java delete mode 100644 esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/java/com/elink/esua/epdc/config/ModuleConfigImpl.java delete mode 100644 esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/java/com/elink/esua/epdc/config/WebConfig.java delete mode 100644 esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/application.yml delete mode 100644 esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/messages.properties delete mode 100644 esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/messages_en_US.properties delete mode 100644 esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/messages_zh_CN.properties delete mode 100644 esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/messages_zh_TW.properties delete mode 100644 esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/validation.properties delete mode 100644 esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/validation_en_US.properties delete mode 100644 esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/validation_zh_CN.properties delete mode 100644 esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/validation_zh_TW.properties delete mode 100644 esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/logback-spring.xml delete mode 100644 esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/registry.conf delete mode 100644 esua-epdc/epdc-module/epdc-app/pom.xml diff --git a/esua-epdc/epdc-commons/epdc-wx/.editorconfig b/esua-epdc/epdc-commons/epdc-wx/.editorconfig new file mode 100644 index 000000000..775be5ba0 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/.editorconfig @@ -0,0 +1,14 @@ +# EditorConfig: http://editorconfig.org/ + +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/esua-epdc/epdc-commons/epdc-wx/.travis.yml b/esua-epdc/epdc-commons/epdc-wx/.travis.yml new file mode 100644 index 000000000..5d2a7698b --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/.travis.yml @@ -0,0 +1,13 @@ +language: java +jdk: + - openjdk8 + +script: "mvn clean package -Dmaven.test.skip=true" + +branches: + only: + - master + +notifications: + email: + - binarywang@vip.qq.com diff --git a/esua-epdc/epdc-commons/epdc-wx/README.md b/esua-epdc/epdc-commons/epdc-wx/README.md new file mode 100644 index 000000000..790d56ba1 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/README.md @@ -0,0 +1,58 @@ +[![码云Gitee](https://gitee.com/binary/weixin-java-mp-demo-springboot/badge/star.svg?theme=blue)](https://gitee.com/binary/weixin-java-mp-demo-springboot) +[![Github](http://github-svg-buttons.herokuapp.com/star.svg?user=binarywang&repo=weixin-java-mp-demo-springboot&style=flat&background=1081C1)](https://github.com/binarywang/weixin-java-mp-demo-springboot) +[![Build Status](https://travis-ci.org/binarywang/weixin-java-mp-demo-springboot.svg?branch=master)](https://travis-ci.org/binarywang/weixin-java-mp-demo-springboot) +----------------------- + +### 本Demo基于Spring Boot构建,实现微信公众号后端开发功能。 +### 本项目为WxJava的Demo演示程序,更多Demo请[查阅此处](https://github.com/Wechat-Group/WxJava/blob/master/demo.md)。 +#### 如有问题请[【在此提问】](https://github.com/binarywang/weixin-java-mp-demo-springboot/issues),谢谢配合。 + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ +## 使用步骤: +1. 请注意,本demo为简化代码编译时加入了lombok支持,如果不了解lombok的话,请先学习下相关知识,比如可以阅读[此文章](https://mp.weixin.qq.com/s/cUc-bUcprycADfNepnSwZQ); +1. 另外,新手遇到问题,请务必先阅读[【开发文档首页】](https://github.com/Wechat-Group/WxJava/wiki)的常见问题部分,可以少走很多弯路,节省不少时间。 +1. 配置:复制 `/src/main/resources/application.yml.template` 或修改其扩展名生成 `application.yml` 文件,根据自己需要填写相关配置(需要注意的是:yml文件内的属性冒号后面的文字之前需要加空格,可参考已有配置,否则属性会设置不成功); +2. 主要配置说明如下: +``` +wx: + mp: + configs: + - appId: 1111 (一个公众号的appid) + secret: 1111(公众号的appsecret) + token: 111 (接口配置里的Token值) + aesKey: 111 (接口配置里的EncodingAESKey值) + - appId: 2222 (另一个公众号的appid,以下同上) + secret: 1111 + token: 111 + aesKey: 111 +``` +3. 运行Java程序:`WxMpDemoApplication`; +4. 配置微信公众号中的接口地址:http://公网可访问域名/wx/portal/xxxxx (注意,xxxxx为对应公众号的appid值); +5. 根据自己需要修改各个handler的实现,加入自己的业务逻辑。 + diff --git a/esua-epdc/epdc-commons/epdc-wx/pom.xml b/esua-epdc/epdc-commons/epdc-wx/pom.xml new file mode 100644 index 000000000..081d0e49c --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + + com.esua.epdc + epdc-commons + 1.0.0 + + + 1.0.0 + epdc-wx + jar + + Wechat mp demo with Spring Boot and WxJava + 基于 WxJava 和 Spring Boot 实现的微信公众号后端开发演示项目 + + + 3.5.0 + + 1.8 + 1.8 + UTF-8 + UTF-8 + zh_CN + wechat-mp-demo + + + + + com.github.binarywang + weixin-java-mp + ${weixin-java-mp.version} + + + + org.projectlombok + lombok + provided + + + + org.springframework.boot + spring-boot-autoconfigure + compile + + + org.springframework.boot + spring-boot-configuration-processor + compile + true + + + org.springframework.boot + spring-boot-autoconfigure-processor + compile + true + + + diff --git a/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/builder/AbstractBuilder.java b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/builder/AbstractBuilder.java new file mode 100644 index 000000000..dc270da56 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/builder/AbstractBuilder.java @@ -0,0 +1,17 @@ +package com.elink.esua.epdc.wx.mp.builder; + +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Binary Wang(https://github.com/binarywang) + */ +public abstract class AbstractBuilder { + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + public abstract WxMpXmlOutMessage build(String content, + WxMpXmlMessage wxMessage, WxMpService service); +} diff --git a/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/builder/ImageBuilder.java b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/builder/ImageBuilder.java new file mode 100644 index 000000000..8daf22d2c --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/builder/ImageBuilder.java @@ -0,0 +1,24 @@ +package com.elink.esua.epdc.wx.mp.builder; + +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutImageMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; + +/** + * @author Binary Wang(https://github.com/binarywang) + */ +public class ImageBuilder extends AbstractBuilder { + + @Override + public WxMpXmlOutMessage build(String content, WxMpXmlMessage wxMessage, + WxMpService service) { + + WxMpXmlOutImageMessage m = WxMpXmlOutMessage.IMAGE().mediaId(content) + .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()) + .build(); + + return m; + } + +} diff --git a/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/builder/TextBuilder.java b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/builder/TextBuilder.java new file mode 100644 index 000000000..93ea48018 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/builder/TextBuilder.java @@ -0,0 +1,22 @@ +package com.elink.esua.epdc.wx.mp.builder; + +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutTextMessage; + +/** + * @author Binary Wang(https://github.com/binarywang) + */ +public class TextBuilder extends AbstractBuilder { + + @Override + public WxMpXmlOutMessage build(String content, WxMpXmlMessage wxMessage, + WxMpService service) { + WxMpXmlOutTextMessage m = WxMpXmlOutMessage.TEXT().content(content) + .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()) + .build(); + return m; + } + +} diff --git a/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/config/WxMpConfiguration.java b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/config/WxMpConfiguration.java new file mode 100644 index 000000000..59a027c9d --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/config/WxMpConfiguration.java @@ -0,0 +1,113 @@ +package com.elink.esua.epdc.wx.mp.config; + +import com.elink.esua.epdc.wx.mp.handler.*; +import lombok.AllArgsConstructor; +import me.chanjar.weixin.mp.api.WxMpMessageRouter; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; +import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; +import java.util.stream.Collectors; + +import static me.chanjar.weixin.common.api.WxConsts.EventType; +import static me.chanjar.weixin.common.api.WxConsts.EventType.SUBSCRIBE; +import static me.chanjar.weixin.common.api.WxConsts.EventType.UNSUBSCRIBE; +import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType.CLICK; +import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType.VIEW; +import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType; +import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType.EVENT; +import static me.chanjar.weixin.mp.constant.WxMpEventConstants.CustomerService.*; +import static me.chanjar.weixin.mp.constant.WxMpEventConstants.POI_CHECK_NOTIFY; + +/** + * wechat mp configuration + * + * @author Binary Wang(https://github.com/binarywang) + */ +@AllArgsConstructor +@Configuration +@EnableConfigurationProperties(WxMpProperties.class) +public class WxMpConfiguration { + private final LogHandler logHandler; + private final NullHandler nullHandler; + private final KfSessionHandler kfSessionHandler; + private final StoreCheckNotifyHandler storeCheckNotifyHandler; + private final LocationHandler locationHandler; + private final MenuHandler menuHandler; + private final MsgHandler msgHandler; + private final UnsubscribeHandler unsubscribeHandler; + private final SubscribeHandler subscribeHandler; + private final ScanHandler scanHandler; + private final WxMpProperties properties; + + @Bean + public WxMpService wxMpService() { + // 代码里 getConfigs()处报错的同学,请注意仔细阅读项目说明,你的IDE需要引入lombok插件!!!! + final List configs = this.properties.getConfigs(); + if (configs == null) { + throw new RuntimeException("大哥,拜托先看下项目首页的说明(readme文件),添加下相关配置,注意别配错了!"); + } + + WxMpService service = new WxMpServiceImpl(); + service.setMultiConfigStorages(configs + .stream().map(a -> { + WxMpDefaultConfigImpl configStorage = new WxMpDefaultConfigImpl(); + configStorage.setAppId(a.getAppId()); + configStorage.setSecret(a.getSecret()); + configStorage.setToken(a.getToken()); + configStorage.setAesKey(a.getAesKey()); + return configStorage; + }).collect(Collectors.toMap(WxMpDefaultConfigImpl::getAppId, a -> a, (o, n) -> o))); + return service; + } + + @Bean + public WxMpMessageRouter messageRouter(WxMpService wxMpService) { + final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService); + + // 记录所有事件的日志 (异步执行) + newRouter.rule().handler(this.logHandler).next(); + + // 接收客服会话管理事件 + newRouter.rule().async(false).msgType(EVENT).event(KF_CREATE_SESSION) + .handler(this.kfSessionHandler).end(); + newRouter.rule().async(false).msgType(EVENT).event(KF_CLOSE_SESSION) + .handler(this.kfSessionHandler).end(); + newRouter.rule().async(false).msgType(EVENT).event(KF_SWITCH_SESSION) + .handler(this.kfSessionHandler).end(); + + // 门店审核事件 + newRouter.rule().async(false).msgType(EVENT).event(POI_CHECK_NOTIFY).handler(this.storeCheckNotifyHandler).end(); + + // 自定义菜单事件 + newRouter.rule().async(false).msgType(EVENT).event(CLICK).handler(this.menuHandler).end(); + + // 点击菜单连接事件 + newRouter.rule().async(false).msgType(EVENT).event(VIEW).handler(this.nullHandler).end(); + + // 关注事件 + newRouter.rule().async(false).msgType(EVENT).event(SUBSCRIBE).handler(this.subscribeHandler).end(); + + // 取消关注事件 + newRouter.rule().async(false).msgType(EVENT).event(UNSUBSCRIBE).handler(this.unsubscribeHandler).end(); + + // 上报地理位置事件 + newRouter.rule().async(false).msgType(EVENT).event(EventType.LOCATION).handler(this.locationHandler).end(); + + // 接收地理位置消息 + newRouter.rule().async(false).msgType(XmlMsgType.LOCATION).handler(this.locationHandler).end(); + + // 扫码事件 + newRouter.rule().async(false).msgType(EVENT).event(EventType.SCAN).handler(this.scanHandler).end(); + + // 默认 + newRouter.rule().async(false).handler(this.msgHandler).end(); + + return newRouter; + } + +} diff --git a/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/config/WxMpProperties.java b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/config/WxMpProperties.java new file mode 100644 index 000000000..6a1aa5947 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/config/WxMpProperties.java @@ -0,0 +1,46 @@ +package com.elink.esua.epdc.wx.mp.config; + +import com.elink.esua.epdc.wx.mp.utils.JsonUtils; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; + +/** + * wechat mp properties + * + * @author Binary Wang(https://github.com/binarywang) + */ +@Data +@ConfigurationProperties(prefix = "wx.mp") +public class WxMpProperties { + private List configs; + + @Data + public static class MpConfig { + /** + * 设置微信公众号的appid + */ + private String appId; + + /** + * 设置微信公众号的app secret + */ + private String secret; + + /** + * 设置微信公众号的token + */ + private String token; + + /** + * 设置微信公众号的EncodingAESKey + */ + private String aesKey; + } + + @Override + public String toString() { + return JsonUtils.toJson(this); + } +} diff --git a/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/AbstractHandler.java b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/AbstractHandler.java new file mode 100644 index 000000000..b3a89e72b --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/AbstractHandler.java @@ -0,0 +1,12 @@ +package com.elink.esua.epdc.wx.mp.handler; + +import me.chanjar.weixin.mp.api.WxMpMessageHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Binary Wang(https://github.com/binarywang) + */ +public abstract class AbstractHandler implements WxMpMessageHandler { + protected Logger logger = LoggerFactory.getLogger(getClass()); +} diff --git a/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/KfSessionHandler.java b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/KfSessionHandler.java new file mode 100644 index 000000000..46df4a7a2 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/KfSessionHandler.java @@ -0,0 +1,25 @@ +package com.elink.esua.epdc.wx.mp.handler; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * @author Binary Wang(https://github.com/binarywang) + */ +@Component +public class KfSessionHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + //TODO 对会话做处理 + return null; + } + +} diff --git a/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/LocationHandler.java b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/LocationHandler.java new file mode 100644 index 000000000..9089a15a2 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/LocationHandler.java @@ -0,0 +1,44 @@ +package com.elink.esua.epdc.wx.mp.handler; + +import com.elink.esua.epdc.wx.mp.builder.TextBuilder; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType; + +/** + * @author Binary Wang(https://github.com/binarywang) + */ +@Component +public class LocationHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + if (wxMessage.getMsgType().equals(XmlMsgType.LOCATION)) { + //TODO 接收处理用户发送的地理位置消息 + try { + String content = "感谢反馈,您的的地理位置已收到!"; + return new TextBuilder().build(content, wxMessage, null); + } catch (Exception e) { + this.logger.error("位置消息接收处理失败", e); + return null; + } + } + + //上报地理位置事件 + this.logger.info("上报地理位置,纬度 : {},经度 : {},精度 : {}", + wxMessage.getLatitude(), wxMessage.getLongitude(), String.valueOf(wxMessage.getPrecision())); + + //TODO 可以将用户地理位置信息保存到本地数据库,以便以后使用 + + return null; + } + +} diff --git a/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/LogHandler.java b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/LogHandler.java new file mode 100644 index 000000000..1ff3ec3de --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/LogHandler.java @@ -0,0 +1,25 @@ +package com.elink.esua.epdc.wx.mp.handler; + +import com.elink.esua.epdc.wx.mp.utils.JsonUtils; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * @author Binary Wang(https://github.com/binarywang) + */ +@Component +public class LogHandler extends AbstractHandler { + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + this.logger.info("\n接收到请求消息,内容:{}", JsonUtils.toJson(wxMessage)); + return null; + } + +} diff --git a/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/MenuHandler.java b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/MenuHandler.java new file mode 100644 index 000000000..249d87e3c --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/MenuHandler.java @@ -0,0 +1,36 @@ +package com.elink.esua.epdc.wx.mp.handler; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static me.chanjar.weixin.common.api.WxConsts.MenuButtonType; + +/** + * @author Binary Wang(https://github.com/binarywang) + */ +@Component +public class MenuHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService weixinService, + WxSessionManager sessionManager) { + + String msg = String.format("type:%s, event:%s, key:%s", + wxMessage.getMsgType(), wxMessage.getEvent(), + wxMessage.getEventKey()); + if (MenuButtonType.VIEW.equals(wxMessage.getEvent())) { + return null; + } + + return WxMpXmlOutMessage.TEXT().content(msg) + .fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()) + .build(); + } + +} diff --git a/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/MsgHandler.java b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/MsgHandler.java new file mode 100644 index 000000000..21f8b48b2 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/MsgHandler.java @@ -0,0 +1,52 @@ +package com.elink.esua.epdc.wx.mp.handler; + +import com.elink.esua.epdc.wx.mp.builder.TextBuilder; +import com.elink.esua.epdc.wx.mp.utils.JsonUtils; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType; + +/** + * @author Binary Wang(https://github.com/binarywang) + */ +@Component +public class MsgHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService weixinService, + WxSessionManager sessionManager) { + + if (!wxMessage.getMsgType().equals(XmlMsgType.EVENT)) { + //TODO 可以选择将消息保存到本地 + } + + //当用户输入关键词如“你好”,“客服”等,并且有客服在线时,把消息转发给在线客服 + try { + if (StringUtils.startsWithAny(wxMessage.getContent(), "你好", "客服") + && weixinService.getKefuService().kfOnlineList() + .getKfOnlineList().size() > 0) { + return WxMpXmlOutMessage.TRANSFER_CUSTOMER_SERVICE() + .fromUser(wxMessage.getToUser()) + .toUser(wxMessage.getFromUser()).build(); + } + } catch (WxErrorException e) { + e.printStackTrace(); + } + + //TODO 组装回复消息 + String content = "收到信息内容:" + JsonUtils.toJson(wxMessage); + + return new TextBuilder().build(content, wxMessage, weixinService); + + } + +} diff --git a/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/NullHandler.java b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/NullHandler.java new file mode 100644 index 000000000..f76290842 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/NullHandler.java @@ -0,0 +1,24 @@ +package com.elink.esua.epdc.wx.mp.handler; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * @author Binary Wang(https://github.com/binarywang) + */ +@Component +public class NullHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + return null; + } + +} diff --git a/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/ScanHandler.java b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/ScanHandler.java new file mode 100644 index 000000000..407f94676 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/ScanHandler.java @@ -0,0 +1,25 @@ +package com.elink.esua.epdc.wx.mp.handler; + +import java.util.Map; + +import org.springframework.stereotype.Component; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; + +/** + * @author Binary Wang(https://github.com/binarywang) + */ +@Component +public class ScanHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map map, + WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException { + // 扫码事件处理 + return null; + } +} diff --git a/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/StoreCheckNotifyHandler.java b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/StoreCheckNotifyHandler.java new file mode 100644 index 000000000..cd1cd25ed --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/StoreCheckNotifyHandler.java @@ -0,0 +1,27 @@ +package com.elink.esua.epdc.wx.mp.handler; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * 门店审核事件处理 + * + * @author Binary Wang(https://github.com/binarywang) + */ +@Component +public class StoreCheckNotifyHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + // TODO 处理门店审核事件 + return null; + } + +} diff --git a/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/SubscribeHandler.java b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/SubscribeHandler.java new file mode 100644 index 000000000..88aac68bd --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/SubscribeHandler.java @@ -0,0 +1,71 @@ +package com.elink.esua.epdc.wx.mp.handler; + +import java.util.Map; + +import com.elink.esua.epdc.wx.mp.builder.TextBuilder; +import org.springframework.stereotype.Component; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import me.chanjar.weixin.mp.bean.result.WxMpUser; + +/** + * @author Binary Wang(https://github.com/binarywang) + */ +@Component +public class SubscribeHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService weixinService, + WxSessionManager sessionManager) throws WxErrorException { + + this.logger.info("新关注用户 OPENID: " + wxMessage.getFromUser()); + + // 获取微信用户基本信息 + try { + WxMpUser userWxInfo = weixinService.getUserService() + .userInfo(wxMessage.getFromUser(), null); + if (userWxInfo != null) { + // TODO 可以添加关注用户到本地数据库 + } + } catch (WxErrorException e) { + if (e.getError().getErrorCode() == 48001) { + this.logger.info("该公众号没有获取用户信息权限!"); + } + } + + + WxMpXmlOutMessage responseResult = null; + try { + responseResult = this.handleSpecial(wxMessage); + } catch (Exception e) { + this.logger.error(e.getMessage(), e); + } + + if (responseResult != null) { + return responseResult; + } + + try { + return new TextBuilder().build("感谢关注", wxMessage, weixinService); + } catch (Exception e) { + this.logger.error(e.getMessage(), e); + } + + return null; + } + + /** + * 处理特殊请求,比如如果是扫码进来的,可以做相应处理 + */ + private WxMpXmlOutMessage handleSpecial(WxMpXmlMessage wxMessage) + throws Exception { + //TODO + return null; + } + +} diff --git a/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/UnsubscribeHandler.java b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/UnsubscribeHandler.java new file mode 100644 index 000000000..4a4723d45 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/handler/UnsubscribeHandler.java @@ -0,0 +1,27 @@ +package com.elink.esua.epdc.wx.mp.handler; + +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * @author Binary Wang(https://github.com/binarywang) + */ +@Component +public class UnsubscribeHandler extends AbstractHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + String openId = wxMessage.getFromUser(); + this.logger.info("取消关注用户 OPENID: " + openId); + // TODO 可以更新本地数据库为取消关注状态 + return null; + } + +} diff --git a/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/utils/JsonUtils.java b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/utils/JsonUtils.java new file mode 100644 index 000000000..0c88b1568 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-wx/src/main/java/com/elink/esua/epdc/wx/mp/utils/JsonUtils.java @@ -0,0 +1,16 @@ +package com.elink.esua.epdc.wx.mp.utils; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * @author Binary Wang(https://github.com/binarywang) + */ +public class JsonUtils { + public static String toJson(Object obj) { + Gson gson = new GsonBuilder() + .setPrettyPrinting() + .create(); + return gson.toJson(obj); + } +} diff --git a/esua-epdc/epdc-commons/pom.xml b/esua-epdc/epdc-commons/pom.xml index 9709e2771..f2e733488 100644 --- a/esua-epdc/epdc-commons/pom.xml +++ b/esua-epdc/epdc-commons/pom.xml @@ -9,7 +9,6 @@ 1.0.0 - com.esua.epdc epdc-commons pom @@ -19,5 +18,6 @@ epdc-commons-dynamic-datasource epdc-commons-api-version-control epdc-commons-tools-phone + epdc-wx diff --git a/esua-epdc/epdc-module/epdc-api/epdc-api-client/pom.xml b/esua-epdc/epdc-module/epdc-api/epdc-api-client/pom.xml index baf672df8..c41c11e02 100644 --- a/esua-epdc/epdc-module/epdc-api/epdc-api-client/pom.xml +++ b/esua-epdc/epdc-module/epdc-api/epdc-api-client/pom.xml @@ -18,6 +18,36 @@ epdc-commons-tools 1.0.0 + + com.esua.epdc + epdc-heart-client + 1.0.0 + + + com.esua.epdc + epdc-neighbor-client + 1.0.0 + + + com.esua.epdc + epdc-news-client + 1.0.0 + + + com.esua.epdc + epdc-events-client + 1.0.0 + + + com.esua.epdc + epdc-services-client + 1.0.0 + + + com.esua.epdc + epdc-user-client + 1.0.0 + diff --git a/esua-epdc/epdc-module/epdc-api/epdc-api-server/pom.xml b/esua-epdc/epdc-module/epdc-api/epdc-api-server/pom.xml index 1762d3b84..402037d25 100644 --- a/esua-epdc/epdc-module/epdc-api/epdc-api-server/pom.xml +++ b/esua-epdc/epdc-module/epdc-api/epdc-api-server/pom.xml @@ -12,6 +12,10 @@ epdc-api-server jar + + 3.5.0 + + com.esua.epdc @@ -53,6 +57,13 @@ epdc-commons-api-version-control ${project.version} + + + + com.esua.epdc + epdc-wx + ${project.version} + @@ -76,4 +87,93 @@ + + + dev + + true + + + dev + + 9040 + + 2 + 47.104.224.45 + 6379 + elink@888 + + + + + epdc + elink833066 + + true + 47.104.224.45:8848 + + wx6ff4e50840cf7dfc + caf82e454ae4e2cb9697651194c37784 + 111 + 111 + + + + + test + + test + + 9040 + + 2 + 47.104.224.45 + 6379 + elink@888 + + + + + epdc + elink833066 + + true + 47.104.224.45:8848 + + wx6ff4e50840cf7dfc + caf82e454ae4e2cb9697651194c37784 + 111 + 111 + + + + + prod + + prod + + 9040 + + 2 + 47.104.224.45 + 6379 + elink@888 + + + + + epdc + elink888 + + false + 47.104.224.45:8848 + + wx62aba559696345af + a93c3dd28ce34fb96228830141e51549 + 111 + 111 + + + + diff --git a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/ApiApplication.java b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/ApiApplication.java index 6e75e8720..116122eeb 100644 --- a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/ApiApplication.java +++ b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/ApiApplication.java @@ -1,8 +1,8 @@ /** * Copyright (c) 2018 人人开源 All rights reserved. - * + *

* https://www.renren.io - * + *

* 版权所有,侵权必究! */ @@ -19,7 +19,7 @@ import org.springframework.cloud.openfeign.EnableFeignClients; * @author Mark sunlightcs@gmail.com * @since 1.0.0 */ -@SpringBootApplication +@SpringBootApplication(scanBasePackages = {"com.elink.esua.epdc"}) @EnableDiscoveryClient @EnableFeignClients public class ApiApplication { diff --git a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/wx/controller/WxController.java b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/wx/controller/WxController.java new file mode 100644 index 000000000..0357ff904 --- /dev/null +++ b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/wx/controller/WxController.java @@ -0,0 +1,33 @@ +package com.elink.esua.epdc.wx.controller; + +import com.elink.esua.epdc.commons.tools.utils.Result; +import com.elink.esua.epdc.wx.service.WxService; +import me.chanjar.weixin.common.bean.WxJsapiSignature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 微信Controller + * + * @author rongchao + * @Date 19-9-4 + */ +@RestController +@RequestMapping("wx") +public class WxController { + + @Autowired + private WxService wxService; + + /** + * 获取用户信息 + * + * @return + */ + @GetMapping("getWxConfig") + public Result getWxConfig(String url) { + return wxService.getWxConfig(url); + } +} diff --git a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/wx/service/WxService.java b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/wx/service/WxService.java new file mode 100644 index 000000000..17ae1c39a --- /dev/null +++ b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/wx/service/WxService.java @@ -0,0 +1,20 @@ +package com.elink.esua.epdc.wx.service; + +import com.elink.esua.epdc.commons.tools.utils.Result; +import me.chanjar.weixin.common.bean.WxJsapiSignature; + +/** + * 微信接口类 + * + * @author rongchao + * @Date 19-9-4 + */ +public interface WxService { + + /** + * 获取微信jssdk配置信息 + * + * @return + */ + Result getWxConfig(String url); +} diff --git a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/wx/service/impl/WxServiceImpl.java b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/wx/service/impl/WxServiceImpl.java new file mode 100644 index 000000000..803e1c31e --- /dev/null +++ b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/wx/service/impl/WxServiceImpl.java @@ -0,0 +1,38 @@ +package com.elink.esua.epdc.wx.service.impl; + +import com.elink.esua.epdc.commons.tools.exception.RenException; +import com.elink.esua.epdc.commons.tools.utils.Result; +import com.elink.esua.epdc.wx.service.WxService; +import me.chanjar.weixin.common.bean.WxJsapiSignature; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * 微信相关服务实现类 + * + * @author rongchao + * @Date 19-9-4 + */ +@Service +public class WxServiceImpl implements WxService { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + @Autowired + private WxMpService wxMpService; + + @Override + public Result getWxConfig(String url) { + try { + WxJsapiSignature wxJsapiSignature = wxMpService.createJsapiSignature(url); + return new Result().ok(wxJsapiSignature); + } catch (WxErrorException e) { + log.error("获取微信Jssdk相关配置失败"); + throw new RenException("获取微信Jssdk相关配置失败"); + } + } +} diff --git a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/resources/application.yml b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/resources/application.yml index a3a7d17a5..c1236c0cb 100644 --- a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/resources/application.yml +++ b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/resources/application.yml @@ -1,5 +1,5 @@ server: - port: 8087 + port: @server.port@ servlet: context-path: /api @@ -16,22 +16,22 @@ spring: time-zone: GMT+8 date-format: yyyy-MM-dd HH:mm:ss redis: - database: 0 - host: 47.104.224.45 + database: @spring.redis.index@ + host: @spring.redis.host@ timeout: 30s - port: 6379 - password: elink@888 + port: @spring.redis.port@ + password: @spring.redis.password@ datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://47.104.224.45:3308/epdc_api?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC - username: root - password: shibei@888 + url: @spring.datasource.druid.url@ + username: @spring.datasource.druid.username@ + password: @spring.datasource.druid.password@ cloud: nacos: discovery: - server-addr: 47.104.224.45:8848 - register-enabled: true + server-addr: @nacos.server-addr@ + register-enabled: @nacos.register-enabled@ alibaba: seata: tx-service-group: epdc-api-server-fescar-service-group @@ -65,3 +65,11 @@ mybatis-plus: cache-enabled: false call-setters-on-nulls: true jdbc-type-for-null: 'null' + +wx: + mp: + configs: + - appId: @wx.mp.configs.appId@ + secret: @wx.mp.configs.secret@ + token: @wx.mp.configs.token@ + aesKey: @wx.mp.configs.aesKey@ diff --git a/esua-epdc/epdc-module/epdc-app/epdc-app-client/pom.xml b/esua-epdc/epdc-module/epdc-app/epdc-app-client/pom.xml deleted file mode 100644 index 2b9c90eb5..000000000 --- a/esua-epdc/epdc-module/epdc-app/epdc-app-client/pom.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - epdc-app - com.esua.epdc - 1.0.0 - - 4.0.0 - - epdc-app-client - jar - - - - com.esua.epdc - epdc-commons-tools - 1.0.0 - - - com.esua.epdc - epdc-heart-client - 1.0.0 - - - com.esua.epdc - epdc-neighbor-client - 1.0.0 - - - com.esua.epdc - epdc-news-client - 1.0.0 - - - com.esua.epdc - epdc-events-client - 1.0.0 - - - com.esua.epdc - epdc-services-client - 1.0.0 - - - com.esua.epdc - epdc-user-client - 1.0.0 - - - - diff --git a/esua-epdc/epdc-module/epdc-app/epdc-app-client/src/main/java/com/elink/esua/epdc/epdc.gitkeep b/esua-epdc/epdc-module/epdc-app/epdc-app-client/src/main/java/com/elink/esua/epdc/epdc.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/esua-epdc/epdc-module/epdc-app/epdc-app-server/pom.xml b/esua-epdc/epdc-module/epdc-app/epdc-app-server/pom.xml deleted file mode 100644 index 42ce442e0..000000000 --- a/esua-epdc/epdc-module/epdc-app/epdc-app-server/pom.xml +++ /dev/null @@ -1,146 +0,0 @@ - - - - epdc-app - com.esua.epdc - 1.0.0 - - 4.0.0 - - epdc-app-server - jar - - - - com.esua.epdc - epdc-app-client - 1.0.0 - - - com.esua.epdc - epdc-commons-tools - 1.0.0 - - - com.esua.epdc - epdc-commons-mybatis - 1.0.0 - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework - spring-context-support - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-discovery - - - de.codecentric - spring-boot-admin-starter-client - ${spring.boot.admin.version} - - - - - ${project.artifactId} - - - org.springframework.boot - spring-boot-maven-plugin - - - org.apache.maven.plugins - maven-surefire-plugin - - true - - - - com.spotify - docker-maven-plugin - - - - - - - - dev - - true - - - dev - - 9058 - - 2 - 47.104.224.45 - 6379 - elink@888 - - - - - epdc - elink888 - - false - 47.104.224.45:8848 - - - - - test - - test - - 9058 - - 2 - 47.104.224.45 - 6379 - elink@888 - - - - - epdc - elink888 - - false - 47.104.224.45:8848 - - - - - prod - - prod - - 9058 - - 2 - 47.104.224.45 - 6379 - elink@888 - - - - - epdc - elink888 - - false - 47.104.224.45:8848 - - - - - \ No newline at end of file diff --git a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/java/com/elink/esua/epdc/AppApplication.java b/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/java/com/elink/esua/epdc/AppApplication.java deleted file mode 100644 index 5fa453d6b..000000000 --- a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/java/com/elink/esua/epdc/AppApplication.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2018 人人开源 All rights reserved. - *

- * https://www.renren.io - *

- * 版权所有,侵权必究! - */ - -package com.elink.esua.epdc; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; -import org.springframework.cloud.openfeign.EnableFeignClients; - -/** - * 移动端接口模块 - * - * @author Mark sunlightcs@gmail.com - * @since 1.1.0 - */ -@SpringBootApplication -@EnableDiscoveryClient -@EnableFeignClients -public class AppApplication { - - public static void main(String[] args) { - SpringApplication.run(AppApplication.class, args); - } - -} diff --git a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/java/com/elink/esua/epdc/config/ModuleConfigImpl.java b/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/java/com/elink/esua/epdc/config/ModuleConfigImpl.java deleted file mode 100644 index 0a6891524..000000000 --- a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/java/com/elink/esua/epdc/config/ModuleConfigImpl.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2018 人人开源 All rights reserved. - *

- * https://www.renren.io - *

- * 版权所有,侵权必究! - */ - -package com.elink.esua.epdc.config; - -import com.elink.esua.epdc.commons.tools.config.ModuleConfig; -import org.springframework.stereotype.Service; - -/** - * 模块配置信息-移动端接口模块 - * - * @author Mark sunlightcs@gmail.com - * @since 1.0.0 - */ -@Service -public class ModuleConfigImpl implements ModuleConfig { - @Override - public String getName() { - return "app"; - } -} diff --git a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/java/com/elink/esua/epdc/config/WebConfig.java b/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/java/com/elink/esua/epdc/config/WebConfig.java deleted file mode 100644 index 1ca742ce7..000000000 --- a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/java/com/elink/esua/epdc/config/WebConfig.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2018 人人开源 All rights reserved. - *

- * https://www.renren.io - *

- * 版权所有,侵权必究! - */ - -package com.elink.esua.epdc.config; - -import com.elink.esua.epdc.commons.tools.resolver.LoginUserHandlerMethodArgumentResolver; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -import java.util.List; - -/** - * MVC配置 - * - * @author Mark sunlightcs@gmail.com - */ -@Configuration -public class WebConfig implements WebMvcConfigurer { - - @Autowired - private LoginUserHandlerMethodArgumentResolver loginUserHandlerMethodArgumentResolver; - - @Override - public void addArgumentResolvers(List argumentResolvers) { - argumentResolvers.add(loginUserHandlerMethodArgumentResolver); - } -} \ No newline at end of file diff --git a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/application.yml b/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/application.yml deleted file mode 100644 index 6d75d4801..000000000 --- a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/application.yml +++ /dev/null @@ -1,68 +0,0 @@ -server: - port: @server.port@ - servlet: - context-path: /epdc-app - -spring: - application: - name: epdc-app-server - # 环境 dev|test|prod - profiles: - active: @spring.profiles.active@ - messages: - encoding: UTF-8 - basename: i18n/messages,i18n/messages_common - jackson: - time-zone: GMT+8 - date-format: yyyy-MM-dd HH:mm:ss - redis: - database: @spring.redis.index@ - host: @spring.redis.host@ - timeout: 30s - port: @spring.redis.port@ - password: @spring.redis.password@ - cloud: - nacos: - discovery: - server-addr: @nacos.server-addr@ - register-enabled: @nacos.register-enabled@ - alibaba: - seata: - tx-service-group: epdc-app-server-fescar-service-group - datasource: - druid: - driver-class-name: com.mysql.jdbc.Driver - url: @spring.datasource.druid.url@ - username: @spring.datasource.druid.username@ - password: @spring.datasource.druid.password@ - - -management: - endpoints: - web: - exposure: - include: "*" - endpoint: - health: - show-details: ALWAYS - -mybatis-plus: - mapper-locations: classpath:/mapper/**/*.xml - #实体扫描,多个package用逗号或者分号分隔 - typeAliasesPackage: io.renren.entity;com.elink.esua.epdc.entity - global-config: - #数据库相关配置 - db-config: - #主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID", ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID"; - id-type: ID_WORKER - #字段策略 IGNORED:"忽略判断",NOT_NULL:"非 NULL 判断"),NOT_EMPTY:"非空判断" - field-strategy: NOT_NULL - #驼峰下划线转换 - column-underline: true - banner: false - #原生配置 - configuration: - map-underscore-to-camel-case: true - cache-enabled: false - call-setters-on-nulls: true - jdbc-type-for-null: 'null' diff --git a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/messages.properties b/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/messages.properties deleted file mode 100644 index e69de29bb..000000000 diff --git a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/messages_en_US.properties b/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/messages_en_US.properties deleted file mode 100644 index e69de29bb..000000000 diff --git a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/messages_zh_CN.properties b/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/messages_zh_CN.properties deleted file mode 100644 index e69de29bb..000000000 diff --git a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/messages_zh_TW.properties b/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/messages_zh_TW.properties deleted file mode 100644 index e69de29bb..000000000 diff --git a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/validation.properties b/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/validation.properties deleted file mode 100644 index e69de29bb..000000000 diff --git a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/validation_en_US.properties b/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/validation_en_US.properties deleted file mode 100644 index e69de29bb..000000000 diff --git a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/validation_zh_CN.properties b/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/validation_zh_CN.properties deleted file mode 100644 index e69de29bb..000000000 diff --git a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/validation_zh_TW.properties b/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/i18n/validation_zh_TW.properties deleted file mode 100644 index e69de29bb..000000000 diff --git a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/logback-spring.xml b/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/logback-spring.xml deleted file mode 100644 index dd5aa1d15..000000000 --- a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/logback-spring.xml +++ /dev/null @@ -1,159 +0,0 @@ - - - - - - - - - - - - - - debug - - - ${CONSOLE_LOG_PATTERN} - - UTF-8 - - - - - - - - ${log.path}/debug.log - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n - UTF-8 - - - - - ${log.path}/debug-%d{yyyy-MM-dd}.%i.log - - 100MB - - - 15 - - - - debug - ACCEPT - DENY - - - - - - - ${log.path}/info.log - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n - UTF-8 - - - - - ${log.path}/info-%d{yyyy-MM-dd}.%i.log - - 100MB - - - 15 - - - - info - ACCEPT - DENY - - - - - - - ${log.path}/warn.log - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n - UTF-8 - - - - ${log.path}/warn-%d{yyyy-MM-dd}.%i.log - - 100MB - - - 15 - - - - warn - ACCEPT - DENY - - - - - - - ${log.path}/error.log - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n - UTF-8 - - - - ${log.path}/error-%d{yyyy-MM-dd}.%i.log - - 100MB - - - 15 - - - - ERROR - ACCEPT - DENY - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/registry.conf b/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/registry.conf deleted file mode 100644 index 9cfedf9cc..000000000 --- a/esua-epdc/epdc-module/epdc-app/epdc-app-server/src/main/resources/registry.conf +++ /dev/null @@ -1,21 +0,0 @@ -registry { - # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa - type = "nacos" - - nacos { - serverAddr = "47.104.224.45" - namespace = "public" - cluster = "default" - } -} - -config { - # file、nacos 、apollo、zk、consul、etcd3 - type = "nacos" - - nacos { - serverAddr = "47.104.224.45" - namespace = "public" - cluster = "default" - } -} diff --git a/esua-epdc/epdc-module/epdc-app/pom.xml b/esua-epdc/epdc-module/epdc-app/pom.xml deleted file mode 100644 index cc01eb83b..000000000 --- a/esua-epdc/epdc-module/epdc-app/pom.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - epdc-module - com.esua.epdc - 1.0.0 - - 4.0.0 - - epdc-app - pom - - epdc-app-client - epdc-app-server - - - - \ No newline at end of file diff --git a/esua-epdc/epdc-module/pom.xml b/esua-epdc/epdc-module/pom.xml index 68c359200..4abd74cfe 100644 --- a/esua-epdc/epdc-module/pom.xml +++ b/esua-epdc/epdc-module/pom.xml @@ -24,7 +24,6 @@ epdc-events epdc-neighbor epdc-services - epdc-app epdc-user epdc-demo From bacefe8046ba1a12b459899aff28c7255fdb9fa9 Mon Sep 17 00:00:00 2001 From: rongchao Date: Thu, 5 Sep 2019 14:14:33 +0800 Subject: [PATCH 02/10] =?UTF-8?q?=E9=9B=86=E6=88=90token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../esua/common/token}/config/WebConfig.java | 12 +-- .../esua/common/token/error/IErrorCode.java | 11 +++ ...oginUserHandlerMethodArgumentResolver.java | 2 +- .../common/token/util}/CpUserDetailRedis.java | 42 ++++++++--- .../epdc/commons/tools/redis/RedisKeys.java | 27 ++++--- .../epdc/commons/tools/redis/RedisUtils.java | 38 +++++----- .../epdc/commons/tools/utils/WebUtil.java | 66 ++++++++++++++++ esua-epdc/epdc-commons/pom.xml | 1 + .../com/elink/esua/epdc/ApiApplication.java | 2 +- .../com/elink/esua/epdc/annotation/Login.java | 21 ------ .../elink/esua/epdc/annotation/LoginUser.java | 25 ------- .../interceptor/AuthorizationInterceptor.java | 75 ------------------- .../src/main/resources/application.yml | 3 + 13 files changed, 156 insertions(+), 169 deletions(-) rename esua-epdc/{epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc => epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token}/config/WebConfig.java (84%) create mode 100644 esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/error/IErrorCode.java rename esua-epdc/epdc-commons/{epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools => epdc-common-clienttoken/src/main/java/com/elink/esua/common/token}/resolver/LoginUserHandlerMethodArgumentResolver.java (97%) rename esua-epdc/epdc-commons/{epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/redis => epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util}/CpUserDetailRedis.java (50%) create mode 100644 esua-epdc/epdc-commons/epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/utils/WebUtil.java delete mode 100644 esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/annotation/Login.java delete mode 100644 esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/annotation/LoginUser.java delete mode 100644 esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/interceptor/AuthorizationInterceptor.java diff --git a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/config/WebConfig.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/config/WebConfig.java similarity index 84% rename from esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/config/WebConfig.java rename to esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/config/WebConfig.java index 89458d299..254b499fd 100644 --- a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/config/WebConfig.java +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/config/WebConfig.java @@ -1,15 +1,15 @@ /** * Copyright (c) 2018 人人开源 All rights reserved. - * + *

* https://www.renren.io - * + *

* 版权所有,侵权必究! */ -package com.elink.esua.epdc.config; +package com.elink.esua.common.token.config; -import com.elink.esua.epdc.commons.tools.resolver.LoginUserHandlerMethodArgumentResolver; -import com.elink.esua.epdc.interceptor.AuthorizationInterceptor; +import com.elink.esua.common.token.interceptor.AuthorizationInterceptor; +import com.elink.esua.common.token.resolver.LoginUserHandlerMethodArgumentResolver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; @@ -25,8 +25,10 @@ import java.util.List; */ @Configuration public class WebConfig implements WebMvcConfigurer { + @Autowired private AuthorizationInterceptor authorizationInterceptor; + @Autowired private LoginUserHandlerMethodArgumentResolver loginUserHandlerMethodArgumentResolver; diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/error/IErrorCode.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/error/IErrorCode.java new file mode 100644 index 000000000..f6a4a8033 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/error/IErrorCode.java @@ -0,0 +1,11 @@ +package com.elink.esua.common.token.error; + +/** + * @author rongchao + * @Date 18-11-20 + */ +public interface IErrorCode { + int getCode(); + + String getMsg(); +} diff --git a/esua-epdc/epdc-commons/epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/resolver/LoginUserHandlerMethodArgumentResolver.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/resolver/LoginUserHandlerMethodArgumentResolver.java similarity index 97% rename from esua-epdc/epdc-commons/epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/resolver/LoginUserHandlerMethodArgumentResolver.java rename to esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/resolver/LoginUserHandlerMethodArgumentResolver.java index 4d12e9e46..ec638592a 100644 --- a/esua-epdc/epdc-commons/epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/resolver/LoginUserHandlerMethodArgumentResolver.java +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/resolver/LoginUserHandlerMethodArgumentResolver.java @@ -6,7 +6,7 @@ * 版权所有,侵权必究! */ -package com.elink.esua.epdc.commons.tools.resolver; +package com.elink.esua.common.token.resolver; import com.elink.esua.epdc.commons.tools.annotation.LoginUser; import com.elink.esua.epdc.commons.tools.constant.Constant; diff --git a/esua-epdc/epdc-commons/epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/redis/CpUserDetailRedis.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/CpUserDetailRedis.java similarity index 50% rename from esua-epdc/epdc-commons/epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/redis/CpUserDetailRedis.java rename to esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/CpUserDetailRedis.java index 6ba73d60b..ce7aa3e21 100644 --- a/esua-epdc/epdc-commons/epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/redis/CpUserDetailRedis.java +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/CpUserDetailRedis.java @@ -6,10 +6,13 @@ * 版权所有,侵权必究! */ -package com.elink.esua.epdc.commons.tools.redis; +package com.elink.esua.common.token.util; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.map.MapUtil; +import com.elink.esua.common.token.dto.TokenDto; +import com.elink.esua.epdc.commons.tools.redis.RedisKeys; +import com.elink.esua.epdc.commons.tools.redis.RedisUtils; import com.elink.esua.epdc.commons.tools.security.user.CpUserDetail; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -28,18 +31,24 @@ public class CpUserDetailRedis { @Autowired private RedisUtils redisUtils; - public void set(CpUserDetail user, long expire) { + public void set(TokenDto user, long expire) { if (user == null) { return; } - String key = RedisKeys.getCpUserKey(user.getId()); + String key = RedisKeys.getCpUserKey(user.getUserInfoDto().getUserId()); //bean to map Map map = BeanUtil.beanToMap(user, false, true); redisUtils.hMSet(key, map, expire); } - public CpUserDetail get(String id) { - String key = RedisKeys.getCpUserKey(id); + /** + * 获取token信息 + * + * @param userId + * @return + */ + public TokenDto get(String userId) { + String key = RedisKeys.getCpUserKey(userId); Map map = redisUtils.hGetAll(key); if (MapUtil.isEmpty(map)) { @@ -47,17 +56,28 @@ public class CpUserDetailRedis { } //map to bean - CpUserDetail user = BeanUtil.mapToBean(map, CpUserDetail.class, true); + TokenDto user = BeanUtil.mapToBean(map, TokenDto.class, true); return user; } /** - * 用户退出 + * 删除用户信息 + * + * @param userId + */ + public void logout(String userId) { + redisUtils.delete(RedisKeys.getCpUserKey(userId)); + } + + /** + * 设置redis时间 * - * @param id + * @param userId + * @param expire + * @author rongchao */ - public void logout(String id) { - redisUtils.delete(RedisKeys.getCpUserKey(id)); + public boolean expire(String userId, long expire) { + return redisUtils.expire(RedisKeys.getCpUserKey(userId), expire); } -} \ No newline at end of file +} diff --git a/esua-epdc/epdc-commons/epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/redis/RedisKeys.java b/esua-epdc/epdc-commons/epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/redis/RedisKeys.java index 494c8c448..8e0b10c1d 100644 --- a/esua-epdc/epdc-commons/epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/redis/RedisKeys.java +++ b/esua-epdc/epdc-commons/epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/redis/RedisKeys.java @@ -14,64 +14,69 @@ package com.elink.esua.epdc.commons.tools.redis; */ public class RedisKeys { + /** + * 党建redis前缀 + */ + private static String rootPrefix = "epdc:"; + /** * 系统参数Key */ public static String getSysParamsKey() { - return "sys:params"; + return rootPrefix.concat("sys:params"); } /** * 登录验证码Key */ public static String getLoginCaptchaKey(String uuid) { - return "sys:captcha:" + uuid; + return rootPrefix.concat("sys:captcha:").concat(uuid); } /** * 登录用户Key */ public static String getSecurityUserKey(Long id) { - return "sys:security:user:" + id; + return rootPrefix.concat("sys:security:user:").concat(String.valueOf(id)); } /** * 系统日志Key */ public static String getSysLogKey() { - return "sys:log"; + return rootPrefix.concat("sys:log"); } /** * 系统资源Key */ public static String getSysResourceKey() { - return "sys:resource"; + return rootPrefix.concat("sys:resource"); } /** * 用户菜单导航Key */ public static String getUserMenuNavKey(Long userId, String language) { - return "sys:user:nav:" + userId + "_" + language; + return rootPrefix.concat("sys:user:nav:").concat(String.valueOf(userId)).concat("_").concat(language); } /** * 用户菜单导航Key */ public static String getUserMenuNavKey(Long userId) { - return "sys:user:nav:" + userId + "_*"; + return rootPrefix.concat("sys:user:nav:").concat(String.valueOf(userId)).concat("_*"); } /** * 用户权限标识Key */ public static String getUserPermissionsKey(Long userId) { - return "sys:user:permissions:" + userId; + return rootPrefix.concat("sys:user:permissions:").concat(String.valueOf(userId)); } public static String getCpUserKey(String id) { - return "sys:security:cpuser:" + id; + return rootPrefix.concat("sys:security:cpuser:").concat(id); } /** @@ -83,7 +88,7 @@ public class RedisKeys { * @date 2019/9/3 16:28 */ public static String getSimpleAreaKey(String areaId) { - return "epdc:config:simple:area:" + areaId; + return rootPrefix.concat("config:simple:area:").concat(areaId); } /** @@ -95,6 +100,6 @@ public class RedisKeys { * @date 2019/9/3 16:28 */ public static String getSimpleDictKey(String dictType) { - return "epdc:config:simple:dict:" + dictType; + return rootPrefix.concat("config:simple:dict:").concat(dictType); } } diff --git a/esua-epdc/epdc-commons/epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/redis/RedisUtils.java b/esua-epdc/epdc-commons/epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/redis/RedisUtils.java index f0724efaa..e09629633 100644 --- a/esua-epdc/epdc-commons/epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/redis/RedisUtils.java +++ b/esua-epdc/epdc-commons/epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/redis/RedisUtils.java @@ -1,8 +1,8 @@ /** * Copyright (c) 2018 人人开源 All rights reserved. - * + *

* https://www.renren.io - * + *

* 版权所有,侵权必究! */ @@ -38,20 +38,20 @@ public class RedisUtils { /** 不设置过期时长 */ public final static long NOT_EXPIRE = -1L; - public void set(String key, Object value, long expire){ + public void set(String key, Object value, long expire) { redisTemplate.opsForValue().set(key, value); - if(expire != NOT_EXPIRE){ + if (expire != NOT_EXPIRE) { expire(key, expire); } } - public void set(String key, Object value){ + public void set(String key, Object value) { set(key, value, DEFAULT_EXPIRE); } public Object get(String key, long expire) { Object value = redisTemplate.opsForValue().get(key); - if(expire != NOT_EXPIRE){ + if (expire != NOT_EXPIRE) { expire(key, expire); } return value; @@ -61,7 +61,7 @@ public class RedisUtils { return get(key, NOT_EXPIRE); } - public Set keys(String pattern){ + public Set keys(String pattern) { return redisTemplate.keys(pattern); } @@ -81,19 +81,19 @@ public class RedisUtils { return redisTemplate.opsForHash().get(key, field); } - public Map hGetAll(String key){ + public Map hGetAll(String key) { HashOperations hashOperations = redisTemplate.opsForHash(); return hashOperations.entries(key); } - public void hMSet(String key, Map map){ + public void hMSet(String key, Map map) { hMSet(key, map, DEFAULT_EXPIRE); } - public void hMSet(String key, Map map, long expire){ + public void hMSet(String key, Map map, long expire) { redisTemplate.opsForHash().putAll(key, map); - if(expire != NOT_EXPIRE){ + if (expire != NOT_EXPIRE) { expire(key, expire); } } @@ -105,32 +105,32 @@ public class RedisUtils { public void hSet(String key, String field, Object value, long expire) { redisTemplate.opsForHash().put(key, field, value); - if(expire != NOT_EXPIRE){ + if (expire != NOT_EXPIRE) { expire(key, expire); } } - public void expire(String key, long expire){ - redisTemplate.expire(key, expire, TimeUnit.SECONDS); + public boolean expire(String key, long expire) { + return redisTemplate.expire(key, expire, TimeUnit.SECONDS); } - public void hDel(String key, Object... fields){ + public void hDel(String key, Object... fields) { redisTemplate.opsForHash().delete(key, fields); } - public void leftPush(String key, Object value){ + public void leftPush(String key, Object value) { leftPush(key, value, DEFAULT_EXPIRE); } - public void leftPush(String key, Object value, long expire){ + public void leftPush(String key, Object value, long expire) { redisTemplate.opsForList().leftPush(key, value); - if(expire != NOT_EXPIRE){ + if (expire != NOT_EXPIRE) { expire(key, expire); } } - public Object rightPop(String key){ + public Object rightPop(String key) { return redisTemplate.opsForList().rightPop(key); } } diff --git a/esua-epdc/epdc-commons/epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/utils/WebUtil.java b/esua-epdc/epdc-commons/epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/utils/WebUtil.java new file mode 100644 index 000000000..5cf6c89cc --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-commons-tools/src/main/java/com/elink/esua/epdc/commons/tools/utils/WebUtil.java @@ -0,0 +1,66 @@ +package com.elink.esua.epdc.commons.tools.utils; + +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; + +/** + * Web工具类 + * + * @author rongchao + * @Date 18-11-20 + */ +public class WebUtil { + + public static HttpServletRequest getHttpServletRequest() { + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = requestAttributes.getRequest(); + return request; + } + + public static Object getAttributesFromRequest(String paramName) { + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + return requestAttributes.getAttribute(paramName, RequestAttributes.SCOPE_REQUEST); + } + + public static void setAttributesFromRequest(String paramName, Object obj) { + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + requestAttributes.setAttribute(paramName, obj, RequestAttributes.SCOPE_REQUEST); + } + + /** + * 获取用户真实IP地址,不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址, + *

+ * 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢? + * 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。 + *

+ * 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130, + * 192.168.1.100 + *

+ * 用户真实IP为: 192.168.1.110 + * + * @return + */ + public static String getIpAddress() { + HttpServletRequest request = getHttpServletRequest(); + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_CLIENT_IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + return ip; + } +} diff --git a/esua-epdc/epdc-commons/pom.xml b/esua-epdc/epdc-commons/pom.xml index f2e733488..3eb744925 100644 --- a/esua-epdc/epdc-commons/pom.xml +++ b/esua-epdc/epdc-commons/pom.xml @@ -19,5 +19,6 @@ epdc-commons-api-version-control epdc-commons-tools-phone epdc-wx + epdc-common-clienttoken diff --git a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/ApiApplication.java b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/ApiApplication.java index 116122eeb..96d647692 100644 --- a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/ApiApplication.java +++ b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/ApiApplication.java @@ -19,7 +19,7 @@ import org.springframework.cloud.openfeign.EnableFeignClients; * @author Mark sunlightcs@gmail.com * @since 1.0.0 */ -@SpringBootApplication(scanBasePackages = {"com.elink.esua.epdc"}) +@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class ApiApplication { diff --git a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/annotation/Login.java b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/annotation/Login.java deleted file mode 100644 index 041820a0d..000000000 --- a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/annotation/Login.java +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) 2018 人人开源 All rights reserved. - * - * https://www.renren.io - * - * 版权所有,侵权必究! - */ - -package com.elink.esua.epdc.annotation; - -import java.lang.annotation.*; - -/** - * 登录效验 - * @author Mark sunlightcs@gmail.com - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface Login { -} diff --git a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/annotation/LoginUser.java b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/annotation/LoginUser.java deleted file mode 100644 index 2c6ce717a..000000000 --- a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/annotation/LoginUser.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) 2018 人人开源 All rights reserved. - * - * https://www.renren.io - * - * 版权所有,侵权必究! - */ - -package com.elink.esua.epdc.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 登录用户信息 - * - * @author Mark sunlightcs@gmail.com - */ -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -public @interface LoginUser { - -} diff --git a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/interceptor/AuthorizationInterceptor.java b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/interceptor/AuthorizationInterceptor.java deleted file mode 100644 index a378ad2a1..000000000 --- a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/interceptor/AuthorizationInterceptor.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Copyright (c) 2018 人人开源 All rights reserved. - * - * https://www.renren.io - * - * 版权所有,侵权必究! - */ - -package com.elink.esua.epdc.interceptor; - -import com.elink.esua.epdc.annotation.Login; -import com.elink.esua.epdc.exception.ModuleErrorCode; -import com.elink.esua.epdc.annotation.Login; -import com.elink.esua.epdc.commons.tools.exception.RenException; -import com.elink.esua.epdc.entity.TokenEntity; -import com.elink.esua.epdc.exception.ModuleErrorCode; -import com.elink.esua.epdc.service.TokenService; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.web.method.HandlerMethod; -import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * 权限(Token)验证 - * - * @author Mark sunlightcs@gmail.com - */ -@Component -public class AuthorizationInterceptor extends HandlerInterceptorAdapter { - @Autowired - private TokenService tokenService; - - public static final String USER_KEY = "userId"; - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - Login annotation; - if(handler instanceof HandlerMethod) { - annotation = ((HandlerMethod) handler).getMethodAnnotation(Login.class); - }else{ - return true; - } - - if(annotation == null){ - return true; - } - - //从header中获取token - String token = request.getHeader("token"); - //如果header中不存在token,则从参数中获取token - if(StringUtils.isBlank(token)){ - token = request.getParameter("token"); - } - - //token为空 - if(StringUtils.isBlank(token)){ - throw new RenException(ModuleErrorCode.TOKEN_NOT_EMPTY); - } - - //查询token信息 - TokenEntity tokenEntity = tokenService.getByToken(token); - if(tokenEntity == null || tokenEntity.getExpireDate().getTime() < System.currentTimeMillis()){ - throw new RenException(ModuleErrorCode.TOKEN_INVALID); - } - - //设置userId到request里,后续根据userId,获取用户信息 - request.setAttribute(USER_KEY, tokenEntity.getUserId()); - - return true; - } -} diff --git a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/resources/application.yml b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/resources/application.yml index c1236c0cb..94809a5be 100644 --- a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/resources/application.yml +++ b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/resources/application.yml @@ -73,3 +73,6 @@ wx: secret: @wx.mp.configs.secret@ token: @wx.mp.configs.token@ aesKey: @wx.mp.configs.aesKey@ + +token: + expire: 21600L From 84166a39f9c14e45b50fc6b73ae5c26523364de2 Mon Sep 17 00:00:00 2001 From: rongchao Date: Thu, 5 Sep 2019 14:15:09 +0800 Subject: [PATCH 03/10] =?UTF-8?q?=E9=9B=86=E6=88=90token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../epdc-common-clienttoken/pom.xml | 120 ++++++++++++++++++ .../esua/common/token/annotation/Login.java | 31 +++++ .../common/token/annotation/LoginUser.java | 35 +++++ .../token/annotation/NeedClientToken.java | 13 ++ .../elink/esua/common/token/dto/TokenDto.java | 38 ++++++ .../esua/common/token/dto/UserTokenDto.java | 43 +++++++ .../esua/common/token/enums/ErrorCode.java | 46 +++++++ .../interceptor/AuthorizationInterceptor.java | 83 ++++++++++++ .../token/property/TokenPropertise.java | 23 ++++ .../esua/common/token/util/TokenUtil.java | 68 ++++++++++ .../esua/common/token/util/UserUtil.java | 56 ++++++++ 11 files changed, 556 insertions(+) create mode 100644 esua-epdc/epdc-commons/epdc-common-clienttoken/pom.xml create mode 100644 esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/Login.java create mode 100644 esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/LoginUser.java create mode 100644 esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/NeedClientToken.java create mode 100644 esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/dto/TokenDto.java create mode 100644 esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/dto/UserTokenDto.java create mode 100644 esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/enums/ErrorCode.java create mode 100644 esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/interceptor/AuthorizationInterceptor.java create mode 100644 esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/property/TokenPropertise.java create mode 100644 esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/TokenUtil.java create mode 100644 esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/UserUtil.java diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/pom.xml b/esua-epdc/epdc-commons/epdc-common-clienttoken/pom.xml new file mode 100644 index 000000000..e6c04ec31 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/pom.xml @@ -0,0 +1,120 @@ + + + + + epdc-commons + com.esua.epdc + 1.0.0 + + 4.0.0 + + epdc-common-clienttoken + jar + + epdc-common-clienttoken + http://www.example.com + 客户端token + + + + com.esua.epdc + epdc-commons-tools + ${project.version} + + + + org.springframework.boot + spring-boot-starter-web + provided + + + + org.springframework.boot + spring-boot-starter-aop + provided + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + com.vaadin.external.google + android-json + + + + + + org.springframework.boot + spring-boot-autoconfigure + compile + + + + org.springframework.boot + spring-boot-autoconfigure-processor + compile + true + + + + org.springframework.boot + spring-boot-starter-log4j2 + provided + + + + + ${project.artifactId} + + + + maven-clean-plugin + + + + maven-resources-plugin + + + maven-compiler-plugin + + true + ${maven.compiler.source} + ${maven.compiler.target} + ${project.build.sourceEncoding} + + + + maven-surefire-plugin + + + maven-war-plugin + + + maven-install-plugin + + + maven-deploy-plugin + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + true + + + + + + + ${project.basedir}/src/main/java + + diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/Login.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/Login.java new file mode 100644 index 000000000..3eead233b --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/Login.java @@ -0,0 +1,31 @@ +/** + * Copyright 2018 人人开源 http://www.renren.io + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.elink.esua.common.token.annotation; + +import java.lang.annotation.*; + +/** + * 登录效验 + * @author chenshun + * @email sunlightcs@gmail.com + * @date 2017/9/23 14:30 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Login { +} diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/LoginUser.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/LoginUser.java new file mode 100644 index 000000000..9fdaae4c9 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/LoginUser.java @@ -0,0 +1,35 @@ +/** + * Copyright 2018 人人开源 http://www.renren.io + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.elink.esua.common.token.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 登录用户信息 + * + * @author chenshun + * @email sunlightcs@gmail.com + * @date 2017-03-23 20:39 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface LoginUser { + +} diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/NeedClientToken.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/NeedClientToken.java new file mode 100644 index 000000000..ff6e5fbe5 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/NeedClientToken.java @@ -0,0 +1,13 @@ +package com.elink.esua.common.token.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS)//生命注释保留时长,这里无需反射使用,使用CLASS级别 +@Target(ElementType.METHOD)//生命可以使用此注解的元素级别类型(如类、方法变量等) +public @interface NeedClientToken { + + boolean value() default true; +} diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/dto/TokenDto.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/dto/TokenDto.java new file mode 100644 index 000000000..90fe6269a --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/dto/TokenDto.java @@ -0,0 +1,38 @@ +package com.elink.esua.common.token.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 用户token + * + * @author rongchao + * @Date 18-10-31 + */ +@Data +public class TokenDto implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 用户信息 + */ + private UserTokenDto userInfoDto; + + /** + * 令牌 + */ + private String token; + + /** + * 过期时间 + */ + private Date expireTime; + + /** + * 更新时间 + */ + private Date updateTime; +} diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/dto/UserTokenDto.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/dto/UserTokenDto.java new file mode 100644 index 000000000..0e2478ef1 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/dto/UserTokenDto.java @@ -0,0 +1,43 @@ +package com.elink.esua.common.token.dto; + +import lombok.Data; + +/** + * 用户信息DTO + * + * @author rongchao + * @Date 18-12-1 + */ +@Data +public class UserTokenDto { + + /** + * 用户ID + */ + private String userId; + + /** + * 昵称 + */ + private String nickName; + + /** + * 用户头像 + */ + private String faceImg; + + /** + * 手机号 + */ + private String mobile; + + /** + * 真是姓名 + */ + private String realName; + + /** + * 网格ID + */ + private String gridId; +} diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/enums/ErrorCode.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/enums/ErrorCode.java new file mode 100644 index 000000000..0f8a8bc3a --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/enums/ErrorCode.java @@ -0,0 +1,46 @@ +package com.elink.esua.common.token.enums; + +import com.elink.esua.common.token.error.IErrorCode; + +/** + * client token错误码 + * + * @author rongchao + * @Date 18-11-24 + */ +public enum ErrorCode implements IErrorCode { + + SUCCESS(0, "请求成功"), + + ERR10001(10001, "clientToken不合法或者已过期"), + ERR10002(10002, "无法获取当前用户的信息,无法生成clientToken。"), + ERR10003(10003, "clientToken生成失败,请重试。"), + ERR10004(10004, "返回的Object类型不是EsuaResponse,无法添加token!"), + ERR10005(10005, "clentToken不能为空"), + + ERR500(500, "Internal Server Error"), + ERR501(501, "参数绑定异常"), + + ERR(ErrorCode.COMMON_ERR_CODE, "其他异常"); + + private int code; + + private String msg; + + ErrorCode(final int code, final String msg) { + this.code = code; + this.msg = msg; + } + + public static final int COMMON_ERR_CODE = -1; + + @Override + public int getCode() { + return code; + } + + @Override + public String getMsg() { + return msg; + } +} diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/interceptor/AuthorizationInterceptor.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/interceptor/AuthorizationInterceptor.java new file mode 100644 index 000000000..90f924370 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/interceptor/AuthorizationInterceptor.java @@ -0,0 +1,83 @@ +/** + * Copyright 2018 人人开源 http://www.renren.io + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.elink.esua.common.token.interceptor; + + +import com.elink.esua.common.token.annotation.Login; +import com.elink.esua.common.token.dto.TokenDto; +import com.elink.esua.common.token.enums.ErrorCode; +import com.elink.esua.common.token.util.TokenUtil; +import com.elink.esua.epdc.commons.tools.constant.Constant; +import com.elink.esua.epdc.commons.tools.exception.RenException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @author rongchao + * @Date 18-11-20 + */ +@Component +public class AuthorizationInterceptor extends HandlerInterceptorAdapter { + + @Autowired + private TokenUtil tokenUtil; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + Login annotation; + if (handler instanceof HandlerMethod) { + annotation = ((HandlerMethod) handler).getMethodAnnotation(Login.class); + } else { + return true; + } + + if (annotation == null) { + return true; + } + + //从header中获取token + String token = request.getHeader("Authorization"); + //如果header中不存在token,则从参数中获取token + if (StringUtils.isEmpty(token)) { + token = request.getParameter("token"); + } + + //token为空 + if (StringUtils.isEmpty(token)) { + // 表示请求信息中没有携带token,前端需要修改上送数据 + throw new RenException(ErrorCode.ERR10005.getCode(), ErrorCode.ERR10005.getMsg()); + } + + //查询token信息 + TokenDto tokenDto = tokenUtil.getTokenInfo(); + if (tokenDto == null || tokenDto.getExpireTime().getTime() < System.currentTimeMillis()) { + // token失效或已被清除,前端需要重新请求获取token的接口,并上送 + throw new RenException(ErrorCode.ERR10001.getCode(), ErrorCode.ERR10001.getMsg()); + } + + //设置userId到request里,后续根据userId,获取用户信息 + request.setAttribute(Constant.APP_USER_KEY, tokenDto); + + return true; + } +} diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/property/TokenPropertise.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/property/TokenPropertise.java new file mode 100644 index 000000000..06fa24477 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/property/TokenPropertise.java @@ -0,0 +1,23 @@ +package com.elink.esua.common.token.property; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @author rongchao + * @Date 18-12-3 + */ +@Component +@ConfigurationProperties(prefix = "token") +public class TokenPropertise { + + private long expire = 7200L; + + public long getExpire() { + return expire; + } + + public void setExpire(long expire) { + this.expire = expire; + } +} diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/TokenUtil.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/TokenUtil.java new file mode 100644 index 000000000..96aedcaba --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/TokenUtil.java @@ -0,0 +1,68 @@ +package com.elink.esua.common.token.util; + +import com.elink.esua.common.token.dto.TokenDto; +import com.elink.esua.common.token.dto.UserTokenDto; +import com.elink.esua.common.token.property.TokenPropertise; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.UUID; + +/** + * token服务类 + * + * @author rongchao + * @Date 18-10-31 + */ +@Component +public class TokenUtil { + + private Logger logger = LoggerFactory.getLogger(getClass()); + + @Autowired + private TokenPropertise tokenPropertise; + + @Autowired + private CpUserDetailRedis redisUtils; + + public TokenDto getTokenInfo(String userId) { + TokenDto tokenDto = redisUtils.get(userId); + return tokenDto; + } + + public TokenDto createToken(UserTokenDto user) { + // 当前时间 + Date now = new Date(); + // 过期时间 + Date expireTime = new Date(now.getTime() + tokenPropertise.getExpire() * 1000); + + // 生成token + String token = this.generateToken(); + + // 保存或更新用户token + TokenDto tokenDto = new TokenDto(); + tokenDto.setUserInfoDto(user); + tokenDto.setToken(token); + tokenDto.setUpdateTime(now); + tokenDto.setExpireTime(expireTime); + redisUtils.set(tokenDto, tokenPropertise.getExpire()); + return tokenDto; + } + + public void expireToken(String userId) { + redisUtils.logout(userId); + } + + public boolean delayToken(String token) { + return redisUtils.expire(token, tokenPropertise.getExpire()); + } + + private String generateToken() { + return UUID.randomUUID().toString().replace("-", ""); + } + + +} diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/UserUtil.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/UserUtil.java new file mode 100644 index 000000000..c69067be1 --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/UserUtil.java @@ -0,0 +1,56 @@ +package com.elink.esua.common.token.util; + +import com.elink.esua.common.token.dto.TokenDto; +import com.elink.esua.common.token.dto.UserTokenDto; +import com.elink.esua.common.token.interceptor.AuthorizationInterceptor; +import com.elink.esua.epdc.commons.tools.utils.WebUtil; + +/** + * 用户工具类 + * + * @author rongchao + * @Date 18-11-20 + */ +public class UserUtil { + + /** + * 获取当前用户信息 + * + * @return + */ + public static TokenDto getCurrentUser() { + return (TokenDto) WebUtil.getAttributesFromRequest(AuthorizationInterceptor.USER_KEY); + } + + /** + * 获取当前用户信息 + * + * @return com.elink.esua.common.token.dto.UserTokenDto + * @author yujintao + * @date 2018/12/5 9:24 + */ + public static UserTokenDto getCurrentUserInfo() { + TokenDto tokenDto = getCurrentUser(); + if (tokenDto == null || tokenDto.getUserInfoDto() == null) { + return null; + } + return tokenDto.getUserInfoDto(); + } + + /** + * 获取当前用户ID + * + * @return + */ + public static String getCurrentUserId() { + TokenDto tokenDto = getCurrentUser(); + if (tokenDto == null || tokenDto.getUserInfoDto() == null) { + return null; + } + return tokenDto.getUserInfoDto().getUserId(); + } + + public static void setCurrentUser(TokenDto user) { + WebUtil.setAttributesFromRequest(AuthorizationInterceptor.USER_KEY, user); + } +} From 604a507470a744bb06a71fe58239863add1d411a Mon Sep 17 00:00:00 2001 From: rongchao Date: Thu, 5 Sep 2019 14:15:33 +0800 Subject: [PATCH 04/10] =?UTF-8?q?=E9=9B=86=E6=88=90token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../esua/common/token/interceptor/AuthorizationInterceptor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/interceptor/AuthorizationInterceptor.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/interceptor/AuthorizationInterceptor.java index 90f924370..4143cb9ea 100644 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/interceptor/AuthorizationInterceptor.java +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/interceptor/AuthorizationInterceptor.java @@ -69,7 +69,7 @@ public class AuthorizationInterceptor extends HandlerInterceptorAdapter { } //查询token信息 - TokenDto tokenDto = tokenUtil.getTokenInfo(); + TokenDto tokenDto = tokenUtil.getTokenInfo("123"); if (tokenDto == null || tokenDto.getExpireTime().getTime() < System.currentTimeMillis()) { // token失效或已被清除,前端需要重新请求获取token的接口,并上送 throw new RenException(ErrorCode.ERR10001.getCode(), ErrorCode.ERR10001.getMsg()); From c48c008ef3a47270b65aa5b6d4fdc8f5d3bc694a Mon Sep 17 00:00:00 2001 From: liuchuang Date: Thu, 5 Sep 2019 14:41:31 +0800 Subject: [PATCH 05/10] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E5=AD=97=E6=AE=B5f?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/mapper/events/EpdcEventsDao.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esua-epdc/epdc-module/epdc-events/epdc-events-server/src/main/resources/mapper/events/EpdcEventsDao.xml b/esua-epdc/epdc-module/epdc-events/epdc-events-server/src/main/resources/mapper/events/EpdcEventsDao.xml index 69b013b2b..e18c7a0d1 100644 --- a/esua-epdc/epdc-module/epdc-events/epdc-events-server/src/main/resources/mapper/events/EpdcEventsDao.xml +++ b/esua-epdc/epdc-module/epdc-events/epdc-events-server/src/main/resources/mapper/events/EpdcEventsDao.xml @@ -5,7 +5,7 @@ - SELECT - e.ID, - e.EVENT_CONTENT AS eventContent, - i.IMG_URL AS images, - e.NICK_NAME AS nickName, - e.CREATED_TIME AS createdTime - FROM - epdc_events e - LEFT JOIN epdc_img i ON e.ID = i.REFERENCE_ID - AND i.DEL_FLAG = '0' - WHERE - e.DEL_FLAG = '0' - AND e.ID = #{id} - ORDER BY - i.CREATED_TIME +SELECT + e.ID, + e.EVENT_CONTENT AS eventContent, + GROUP_CONCAT( DISTINCT ( i.IMG_URL ) ) AS images, + e.NICK_NAME AS nickName, + e.CREATED_TIME AS createdTime +FROM + epdc_events e + LEFT JOIN epdc_img i ON e.ID = i.REFERENCE_ID + AND i.DEL_FLAG = '0' +WHERE + e.DEL_FLAG = '0' + AND e.ID = #{id} +GROUP BY + e.ID +ORDER BY + i.CREATED_TIME diff --git a/esua-epdc/epdc-module/epdc-user/epdc-user-server/src/main/resources/mapper/PartyMembersDao.xml b/esua-epdc/epdc-module/epdc-user/epdc-user-server/src/main/resources/mapper/PartyMembersDao.xml index 8ddc60bba..4cf0f16d2 100755 --- a/esua-epdc/epdc-module/epdc-user/epdc-user-server/src/main/resources/mapper/PartyMembersDao.xml +++ b/esua-epdc/epdc-module/epdc-user/epdc-user-server/src/main/resources/mapper/PartyMembersDao.xml @@ -103,6 +103,7 @@ and pm.ID = #{id} and pm.DEL_FLAG = '0' and partytag.DEL_FLAG = '0' + GROUP BY pm.ID From 3ecc98582c118682a71bb5505e42aacefe1616ea Mon Sep 17 00:00:00 2001 From: rongchao Date: Thu, 5 Sep 2019 16:03:47 +0800 Subject: [PATCH 07/10] =?UTF-8?q?=E4=BF=AE=E6=94=B9auth=E6=A8=A1=E5=9D=97b?= =?UTF-8?q?ug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- esua-epdc/epdc-auth/pom.xml | 7 ++ .../esua/epdc/controller/AuthController.java | 36 +++++----- .../elink/esua/epdc/service/AuthService.java | 8 +-- .../epdc/service/impl/AuthServiceImpl.java | 20 +++--- .../epdc-common-clienttoken/pom.xml | 6 ++ .../elink/esua/common/token/dto/TokenDto.java | 38 ----------- .../esua/common/token/util/TokenUtil.java | 68 ------------------- .../common/token/annotation/Login.java | 2 +- .../common/token/annotation/LoginUser.java | 2 +- .../token/annotation/NeedClientToken.java | 2 +- .../common/token/dto/TokenDto.java} | 13 ++-- .../common/token/enums/ErrorCode.java | 4 +- .../common/token/error/IErrorCode.java | 2 +- .../interceptor/AuthorizationInterceptor.java | 28 +++++--- .../common/token/jwt/JwtTokenProperties.java | 41 +++++++++++ .../epdc/common/token/jwt/JwtTokenUtils.java | 68 +++++++++++++++++++ .../token/property/TokenPropertise.java | 2 +- ...oginUserHandlerMethodArgumentResolver.java | 7 +- .../common/token/util/CpUserDetailRedis.java | 7 +- .../epdc/common/token/util/TokenUtil.java | 48 +++++++++++++ .../common/token/util/UserUtil.java | 21 +++--- esua-epdc/epdc-gateway/pom.xml | 62 +++++++++++------ .../esua/epdc/feign/ResourceFeignClient.java | 4 +- .../fallback/ResourceFeignClientFallback.java | 10 +-- .../filter/CpAuthGatewayFilterFactory.java | 10 +-- .../epdc-api/epdc-api-server/pom.xml | 7 ++ .../elink/esua/epdc}/config/WebConfig.java | 6 +- .../epdc/controller/ApiLoginController.java | 2 +- .../epdc/controller/ApiTestController.java | 63 ----------------- 29 files changed, 321 insertions(+), 273 deletions(-) delete mode 100644 esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/dto/TokenDto.java delete mode 100644 esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/TokenUtil.java rename esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/{ => epdc}/common/token/annotation/Login.java (94%) rename esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/{ => epdc}/common/token/annotation/LoginUser.java (94%) rename esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/{ => epdc}/common/token/annotation/NeedClientToken.java (89%) rename esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/{common/token/dto/UserTokenDto.java => epdc/common/token/dto/TokenDto.java} (64%) rename esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/{ => epdc}/common/token/enums/ErrorCode.java (90%) rename esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/{ => epdc}/common/token/error/IErrorCode.java (71%) rename esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/{ => epdc}/common/token/interceptor/AuthorizationInterceptor.java (75%) create mode 100644 esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/jwt/JwtTokenProperties.java create mode 100644 esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/jwt/JwtTokenUtils.java rename esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/{ => epdc}/common/token/property/TokenPropertise.java (89%) rename esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/{ => epdc}/common/token/resolver/LoginUserHandlerMethodArgumentResolver.java (88%) rename esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/{ => epdc}/common/token/util/CpUserDetailRedis.java (87%) create mode 100644 esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/util/TokenUtil.java rename esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/{ => epdc}/common/token/util/UserUtil.java (53%) rename esua-epdc/{epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token => epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc}/config/WebConfig.java (85%) delete mode 100644 esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/controller/ApiTestController.java diff --git a/esua-epdc/epdc-auth/pom.xml b/esua-epdc/epdc-auth/pom.xml index fbff0aacd..053491122 100644 --- a/esua-epdc/epdc-auth/pom.xml +++ b/esua-epdc/epdc-auth/pom.xml @@ -23,6 +23,13 @@ epdc-admin-client 1.0.0 + + + com.esua.epdc + epdc-common-clienttoken + 1.0.0 + + org.springframework.boot spring-boot-starter-web diff --git a/esua-epdc/epdc-auth/src/main/java/com/elink/esua/epdc/controller/AuthController.java b/esua-epdc/epdc-auth/src/main/java/com/elink/esua/epdc/controller/AuthController.java index 806c6d7cd..a2bebca12 100644 --- a/esua-epdc/epdc-auth/src/main/java/com/elink/esua/epdc/controller/AuthController.java +++ b/esua-epdc/epdc-auth/src/main/java/com/elink/esua/epdc/controller/AuthController.java @@ -1,16 +1,16 @@ /** * Copyright (c) 2018 人人开源 All rights reserved. - * + *

* https://www.renren.io - * + *

* 版权所有,侵权必究! */ package com.elink.esua.epdc.controller; +import com.elink.esua.epdc.common.token.dto.TokenDto; import com.elink.esua.epdc.commons.tools.constant.Constant; import com.elink.esua.epdc.commons.tools.exception.ErrorCode; -import com.elink.esua.epdc.commons.tools.security.user.CpUserDetail; import com.elink.esua.epdc.commons.tools.security.user.UserDetail; import com.elink.esua.epdc.commons.tools.utils.Result; import com.elink.esua.epdc.commons.tools.validator.AssertUtils; @@ -40,7 +40,7 @@ import java.io.IOException; * @since 1.0.0 */ @RestController -@Api(tags="授权管理") +@Api(tags = "授权管理") public class AuthController { @Autowired private AuthService authService; @@ -50,9 +50,9 @@ public class AuthController { private CaptchaService captchaService; @GetMapping("captcha") - @ApiOperation(value = "验证码", produces="application/octet-stream") - @ApiImplicitParam(paramType = "query", dataType="string", name = "uuid", required = true) - public void captcha(HttpServletResponse response, String uuid)throws IOException { + @ApiOperation(value = "验证码", produces = "application/octet-stream") + @ApiImplicitParam(paramType = "query", dataType = "string", name = "uuid", required = true) + public void captcha(HttpServletResponse response, String uuid) throws IOException { //uuid不能为空 AssertUtils.isBlank(uuid, ErrorCode.IDENTIFIER_NOT_NULL); @@ -68,13 +68,13 @@ public class AuthController { @PostMapping(value = "login") @ApiOperation(value = "登录") - public Result login(@RequestBody LoginDTO login){ + public Result login(@RequestBody LoginDTO login) { //效验数据 ValidatorUtils.validateEntity(login); //验证码是否正确 boolean flag = captchaService.validate(login.getUuid(), login.getCaptcha()); - if(!flag){ + if (!flag) { return new Result().error(ErrorCode.CAPTCHA_ERROR); } @@ -86,7 +86,7 @@ public class AuthController { @PostMapping(value = "logout") @ApiOperation(value = "退出") - public Result logout(HttpServletRequest request){ + public Result logout(HttpServletRequest request) { String userId = request.getHeader(Constant.USER_KEY); authService.logout(Long.parseLong(userId)); @@ -96,27 +96,27 @@ public class AuthController { /** * 是否有资源访问权限 - * @param token token - * @param url 资源URL - * @param method 请求方式 * + * @param token token + * @param url 资源URL + * @param method 请求方式 * @return 有访问权限,则返回用户信息 */ @PostMapping("resource") public Result resource(@RequestParam(value = "token", required = false) String token, - @RequestParam("url") String url, @RequestParam("method") String method){ + @RequestParam("url") String url, @RequestParam("method") String method) { UserDetail data = resourceService.resource(token, url, method); return new Result().ok(data); } @GetMapping(value = "getLoginUserInfo") - public Result getLoginUserInfo(String token) { + public Result getLoginUserInfo(String token) { - CpUserDetail cpUserDetail = authService.getLoginUserInfo(token); + TokenDto cpUserDetail = authService.getLoginUserInfo(token); if (cpUserDetail != null) { - return new Result().ok(cpUserDetail); + return new Result().ok(cpUserDetail); } - return new Result().error(); + return new Result().error(); } } diff --git a/esua-epdc/epdc-auth/src/main/java/com/elink/esua/epdc/service/AuthService.java b/esua-epdc/epdc-auth/src/main/java/com/elink/esua/epdc/service/AuthService.java index 1f500829c..48c57434c 100644 --- a/esua-epdc/epdc-auth/src/main/java/com/elink/esua/epdc/service/AuthService.java +++ b/esua-epdc/epdc-auth/src/main/java/com/elink/esua/epdc/service/AuthService.java @@ -1,14 +1,14 @@ /** * Copyright (c) 2018 人人开源 All rights reserved. - * + *

* https://www.renren.io - * + *

* 版权所有,侵权必究! */ package com.elink.esua.epdc.service; -import com.elink.esua.epdc.commons.tools.security.user.CpUserDetail; +import com.elink.esua.epdc.common.token.dto.TokenDto; import com.elink.esua.epdc.dto.AuthorizationDTO; import com.elink.esua.epdc.dto.LoginDTO; @@ -30,5 +30,5 @@ public interface AuthService { */ void logout(Long userId); - CpUserDetail getLoginUserInfo(String token); + TokenDto getLoginUserInfo(String token); } diff --git a/esua-epdc/epdc-auth/src/main/java/com/elink/esua/epdc/service/impl/AuthServiceImpl.java b/esua-epdc/epdc-auth/src/main/java/com/elink/esua/epdc/service/impl/AuthServiceImpl.java index cd792001d..fa2f63e88 100644 --- a/esua-epdc/epdc-auth/src/main/java/com/elink/esua/epdc/service/impl/AuthServiceImpl.java +++ b/esua-epdc/epdc-auth/src/main/java/com/elink/esua/epdc/service/impl/AuthServiceImpl.java @@ -1,16 +1,15 @@ /** * Copyright (c) 2018 人人开源 All rights reserved. - * + *

* https://www.renren.io - * + *

* 版权所有,侵权必究! */ package com.elink.esua.epdc.service.impl; -import com.elink.esua.epdc.commons.tools.redis.CpUserDetailRedis; -import com.elink.esua.epdc.commons.tools.security.user.CpUserDetail; -import com.elink.esua.epdc.enums.UserStatusEnum; +import com.elink.esua.epdc.common.token.dto.TokenDto; +import com.elink.esua.epdc.common.token.util.CpUserDetailRedis; import com.elink.esua.epdc.commons.tools.exception.ErrorCode; import com.elink.esua.epdc.commons.tools.exception.RenException; import com.elink.esua.epdc.commons.tools.log.SysLogLogin; @@ -27,6 +26,7 @@ import com.elink.esua.epdc.commons.tools.utils.IpUtils; import com.elink.esua.epdc.commons.tools.utils.Result; import com.elink.esua.epdc.dto.AuthorizationDTO; import com.elink.esua.epdc.dto.LoginDTO; +import com.elink.esua.epdc.enums.UserStatusEnum; import com.elink.esua.epdc.feign.UserFeignClient; import com.elink.esua.epdc.jwt.JwtProperties; import com.elink.esua.epdc.jwt.JwtUtils; @@ -78,7 +78,7 @@ public class AuthServiceImpl implements AuthService { log.setIp(IpUtils.getIpAddr(request)); //账号不存在 - if(user == null){ + if (user == null) { log.setStatus(LoginStatusEnum.FAIL.value()); log.setCreatorName(login.getUsername()); logProducer.saveLog(log); @@ -87,7 +87,7 @@ public class AuthServiceImpl implements AuthService { } //密码错误 - if(!PasswordUtils.matches(login.getPassword(), user.getPassword())){ + if (!PasswordUtils.matches(login.getPassword(), user.getPassword())) { log.setStatus(LoginStatusEnum.FAIL.value()); log.setCreator(user.getId()); log.setCreatorName(user.getUsername()); @@ -97,7 +97,7 @@ public class AuthServiceImpl implements AuthService { } //账号停用 - if(user.getStatus() == UserStatusEnum.DISABLE.value()){ + if (user.getStatus() == UserStatusEnum.DISABLE.value()) { log.setStatus(LoginStatusEnum.LOCK.value()); log.setCreator(user.getId()); log.setCreatorName(user.getUsername()); @@ -149,7 +149,7 @@ public class AuthServiceImpl implements AuthService { } @Override - public CpUserDetail getLoginUserInfo(String token) { + public TokenDto getLoginUserInfo(String token) { //是否过期 Claims claims = jwtUtils.getClaimByToken(token); if (claims == null || jwtUtils.isTokenExpired(claims.getExpiration())) { @@ -160,7 +160,7 @@ public class AuthServiceImpl implements AuthService { String userId = claims.getSubject(); //查询Redis,如果没数据,则保持用户信息到Redis - CpUserDetail cpUserDetail = cpUserDetailRedis.get(userId); + TokenDto cpUserDetail = cpUserDetailRedis.get(userId); if (cpUserDetail != null) { //过期时间 long expire = (claims.getExpiration().getTime() - System.currentTimeMillis()) / 1000; diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/pom.xml b/esua-epdc/epdc-commons/epdc-common-clienttoken/pom.xml index e6c04ec31..dd4f4e01c 100644 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/pom.xml +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/pom.xml @@ -65,6 +65,12 @@ spring-boot-starter-log4j2 provided + + + io.jsonwebtoken + jjwt + 0.7.0 + diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/dto/TokenDto.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/dto/TokenDto.java deleted file mode 100644 index 90fe6269a..000000000 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/dto/TokenDto.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.elink.esua.common.token.dto; - -import lombok.Data; - -import java.io.Serializable; -import java.util.Date; - -/** - * 用户token - * - * @author rongchao - * @Date 18-10-31 - */ -@Data -public class TokenDto implements Serializable { - - private static final long serialVersionUID = 1L; - - /** - * 用户信息 - */ - private UserTokenDto userInfoDto; - - /** - * 令牌 - */ - private String token; - - /** - * 过期时间 - */ - private Date expireTime; - - /** - * 更新时间 - */ - private Date updateTime; -} diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/TokenUtil.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/TokenUtil.java deleted file mode 100644 index 96aedcaba..000000000 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/TokenUtil.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.elink.esua.common.token.util; - -import com.elink.esua.common.token.dto.TokenDto; -import com.elink.esua.common.token.dto.UserTokenDto; -import com.elink.esua.common.token.property.TokenPropertise; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.util.Date; -import java.util.UUID; - -/** - * token服务类 - * - * @author rongchao - * @Date 18-10-31 - */ -@Component -public class TokenUtil { - - private Logger logger = LoggerFactory.getLogger(getClass()); - - @Autowired - private TokenPropertise tokenPropertise; - - @Autowired - private CpUserDetailRedis redisUtils; - - public TokenDto getTokenInfo(String userId) { - TokenDto tokenDto = redisUtils.get(userId); - return tokenDto; - } - - public TokenDto createToken(UserTokenDto user) { - // 当前时间 - Date now = new Date(); - // 过期时间 - Date expireTime = new Date(now.getTime() + tokenPropertise.getExpire() * 1000); - - // 生成token - String token = this.generateToken(); - - // 保存或更新用户token - TokenDto tokenDto = new TokenDto(); - tokenDto.setUserInfoDto(user); - tokenDto.setToken(token); - tokenDto.setUpdateTime(now); - tokenDto.setExpireTime(expireTime); - redisUtils.set(tokenDto, tokenPropertise.getExpire()); - return tokenDto; - } - - public void expireToken(String userId) { - redisUtils.logout(userId); - } - - public boolean delayToken(String token) { - return redisUtils.expire(token, tokenPropertise.getExpire()); - } - - private String generateToken() { - return UUID.randomUUID().toString().replace("-", ""); - } - - -} diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/Login.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/annotation/Login.java similarity index 94% rename from esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/Login.java rename to esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/annotation/Login.java index 3eead233b..9b622b179 100644 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/Login.java +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/annotation/Login.java @@ -14,7 +14,7 @@ * the License. */ -package com.elink.esua.common.token.annotation; +package com.elink.esua.epdc.common.token.annotation; import java.lang.annotation.*; diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/LoginUser.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/annotation/LoginUser.java similarity index 94% rename from esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/LoginUser.java rename to esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/annotation/LoginUser.java index 9fdaae4c9..e19370c0e 100644 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/LoginUser.java +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/annotation/LoginUser.java @@ -14,7 +14,7 @@ * the License. */ -package com.elink.esua.common.token.annotation; +package com.elink.esua.epdc.common.token.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/NeedClientToken.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/annotation/NeedClientToken.java similarity index 89% rename from esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/NeedClientToken.java rename to esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/annotation/NeedClientToken.java index ff6e5fbe5..897a0048e 100644 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/annotation/NeedClientToken.java +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/annotation/NeedClientToken.java @@ -1,4 +1,4 @@ -package com.elink.esua.common.token.annotation; +package com.elink.esua.epdc.common.token.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/dto/UserTokenDto.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/dto/TokenDto.java similarity index 64% rename from esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/dto/UserTokenDto.java rename to esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/dto/TokenDto.java index 0e2478ef1..fc5b1b155 100644 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/dto/UserTokenDto.java +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/dto/TokenDto.java @@ -1,15 +1,20 @@ -package com.elink.esua.common.token.dto; +package com.elink.esua.epdc.common.token.dto; import lombok.Data; +import java.io.Serializable; +import java.util.Date; + /** - * 用户信息DTO + * 用户token * * @author rongchao - * @Date 18-12-1 + * @Date 18-10-31 */ @Data -public class UserTokenDto { +public class TokenDto implements Serializable { + + private static final long serialVersionUID = 1L; /** * 用户ID diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/enums/ErrorCode.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/enums/ErrorCode.java similarity index 90% rename from esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/enums/ErrorCode.java rename to esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/enums/ErrorCode.java index 0f8a8bc3a..711187292 100644 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/enums/ErrorCode.java +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/enums/ErrorCode.java @@ -1,6 +1,6 @@ -package com.elink.esua.common.token.enums; +package com.elink.esua.epdc.common.token.enums; -import com.elink.esua.common.token.error.IErrorCode; +import com.elink.esua.epdc.common.token.error.IErrorCode; /** * client token错误码 diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/error/IErrorCode.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/error/IErrorCode.java similarity index 71% rename from esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/error/IErrorCode.java rename to esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/error/IErrorCode.java index f6a4a8033..3d83f9fd2 100644 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/error/IErrorCode.java +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/error/IErrorCode.java @@ -1,4 +1,4 @@ -package com.elink.esua.common.token.error; +package com.elink.esua.epdc.common.token.error; /** * @author rongchao diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/interceptor/AuthorizationInterceptor.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/interceptor/AuthorizationInterceptor.java similarity index 75% rename from esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/interceptor/AuthorizationInterceptor.java rename to esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/interceptor/AuthorizationInterceptor.java index 4143cb9ea..3877934e7 100644 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/interceptor/AuthorizationInterceptor.java +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/interceptor/AuthorizationInterceptor.java @@ -14,15 +14,17 @@ * the License. */ -package com.elink.esua.common.token.interceptor; +package com.elink.esua.epdc.common.token.interceptor; -import com.elink.esua.common.token.annotation.Login; -import com.elink.esua.common.token.dto.TokenDto; -import com.elink.esua.common.token.enums.ErrorCode; -import com.elink.esua.common.token.util.TokenUtil; +import com.elink.esua.epdc.common.token.annotation.Login; +import com.elink.esua.epdc.common.token.dto.TokenDto; +import com.elink.esua.epdc.common.token.enums.ErrorCode; +import com.elink.esua.epdc.common.token.jwt.JwtTokenUtils; +import com.elink.esua.epdc.common.token.util.TokenUtil; import com.elink.esua.epdc.commons.tools.constant.Constant; import com.elink.esua.epdc.commons.tools.exception.RenException; +import io.jsonwebtoken.Claims; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -42,6 +44,9 @@ public class AuthorizationInterceptor extends HandlerInterceptorAdapter { @Autowired private TokenUtil tokenUtil; + @Autowired + private JwtTokenUtils jwtUtils; + @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Login annotation; @@ -68,10 +73,17 @@ public class AuthorizationInterceptor extends HandlerInterceptorAdapter { throw new RenException(ErrorCode.ERR10005.getCode(), ErrorCode.ERR10005.getMsg()); } + Claims claims = jwtUtils.getClaimByToken(token); + + if (claims == null || jwtUtils.isTokenExpired(claims.getExpiration())) { + throw new RenException(ErrorCode.ERR10001.getCode(), ErrorCode.ERR10001.getMsg()); + } + + //获取用户ID + String userId = claims.getSubject(); //查询token信息 - TokenDto tokenDto = tokenUtil.getTokenInfo("123"); - if (tokenDto == null || tokenDto.getExpireTime().getTime() < System.currentTimeMillis()) { - // token失效或已被清除,前端需要重新请求获取token的接口,并上送 + TokenDto tokenDto = tokenUtil.getTokenInfo(userId); + if (tokenDto == null) { throw new RenException(ErrorCode.ERR10001.getCode(), ErrorCode.ERR10001.getMsg()); } diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/jwt/JwtTokenProperties.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/jwt/JwtTokenProperties.java new file mode 100644 index 000000000..ad484aa5a --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/jwt/JwtTokenProperties.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2018 人人开源 All rights reserved. + * + * https://www.renren.io + * + * 版权所有,侵权必究! + */ + +package com.elink.esua.epdc.common.token.jwt; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * Jwt + * + * @author Mark sunlightcs@gmail.com + * @since 1.0.0 + */ +@Configuration +@ConfigurationProperties(prefix = "jwt.token") +public class JwtTokenProperties { + private String secret; + private int expire; + + public String getSecret() { + return secret; + } + + public void setSecret(String secret) { + this.secret = secret; + } + + public int getExpire() { + return expire; + } + + public void setExpire(int expire) { + this.expire = expire; + } +} diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/jwt/JwtTokenUtils.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/jwt/JwtTokenUtils.java new file mode 100644 index 000000000..2dd2ba97f --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/jwt/JwtTokenUtils.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2018 人人开源 All rights reserved. + *

+ * https://www.renren.io + *

+ * 版权所有,侵权必究! + */ + +package com.elink.esua.epdc.common.token.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.joda.time.DateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + * Jwt工具类 + * + * @author Mark sunlightcs@gmail.com + * @since 1.0.0 + */ +@Component +public class JwtTokenUtils { + private static final Logger logger = LoggerFactory.getLogger(JwtTokenUtils.class); + + @Autowired + private JwtTokenProperties jwtProperties; + + /** + * 生成jwt token + */ + public String generateToken(String userId) { + return Jwts.builder() + .setHeaderParam("typ", "JWT") + .setSubject(userId) + .setIssuedAt(new Date()) + .setExpiration(DateTime.now().plusSeconds(jwtProperties.getExpire()).toDate()) + .signWith(SignatureAlgorithm.HS512, jwtProperties.getSecret()) + .compact(); + } + + public Claims getClaimByToken(String token) { + try { + return Jwts.parser() + .setSigningKey(jwtProperties.getSecret()) + .parseClaimsJws(token) + .getBody(); + } catch (Exception e) { + logger.debug("validate is token error, token = " + token, e); + return null; + } + } + + /** + * token是否过期 + * + * @return true:过期 + */ + public boolean isTokenExpired(Date expiration) { + return expiration.before(new Date()); + } +} diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/property/TokenPropertise.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/property/TokenPropertise.java similarity index 89% rename from esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/property/TokenPropertise.java rename to esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/property/TokenPropertise.java index 06fa24477..352c03eb4 100644 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/property/TokenPropertise.java +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/property/TokenPropertise.java @@ -1,4 +1,4 @@ -package com.elink.esua.common.token.property; +package com.elink.esua.epdc.common.token.property; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/resolver/LoginUserHandlerMethodArgumentResolver.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/resolver/LoginUserHandlerMethodArgumentResolver.java similarity index 88% rename from esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/resolver/LoginUserHandlerMethodArgumentResolver.java rename to esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/resolver/LoginUserHandlerMethodArgumentResolver.java index ec638592a..0a6dc910e 100644 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/resolver/LoginUserHandlerMethodArgumentResolver.java +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/resolver/LoginUserHandlerMethodArgumentResolver.java @@ -6,11 +6,12 @@ * 版权所有,侵权必究! */ -package com.elink.esua.common.token.resolver; +package com.elink.esua.epdc.common.token.resolver; +import com.elink.esua.epdc.common.token.dto.TokenDto; +import com.elink.esua.epdc.common.token.util.CpUserDetailRedis; import com.elink.esua.epdc.commons.tools.annotation.LoginUser; import com.elink.esua.epdc.commons.tools.constant.Constant; -import com.elink.esua.epdc.commons.tools.redis.CpUserDetailRedis; import com.elink.esua.epdc.commons.tools.security.user.CpUserDetail; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -46,7 +47,7 @@ public class LoginUserHandlerMethodArgumentResolver implements HandlerMethodArgu if (StringUtils.isEmpty(userId)) { return null; } - CpUserDetail user = cpUserDetailRedis.get(userId); + TokenDto user = cpUserDetailRedis.get(userId); return user; } } diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/CpUserDetailRedis.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/util/CpUserDetailRedis.java similarity index 87% rename from esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/CpUserDetailRedis.java rename to esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/util/CpUserDetailRedis.java index ce7aa3e21..6101c8f1f 100644 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/CpUserDetailRedis.java +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/util/CpUserDetailRedis.java @@ -6,14 +6,13 @@ * 版权所有,侵权必究! */ -package com.elink.esua.common.token.util; +package com.elink.esua.epdc.common.token.util; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.map.MapUtil; -import com.elink.esua.common.token.dto.TokenDto; +import com.elink.esua.epdc.common.token.dto.TokenDto; import com.elink.esua.epdc.commons.tools.redis.RedisKeys; import com.elink.esua.epdc.commons.tools.redis.RedisUtils; -import com.elink.esua.epdc.commons.tools.security.user.CpUserDetail; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -35,7 +34,7 @@ public class CpUserDetailRedis { if (user == null) { return; } - String key = RedisKeys.getCpUserKey(user.getUserInfoDto().getUserId()); + String key = RedisKeys.getCpUserKey(user.getUserId()); //bean to map Map map = BeanUtil.beanToMap(user, false, true); redisUtils.hMSet(key, map, expire); diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/util/TokenUtil.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/util/TokenUtil.java new file mode 100644 index 000000000..530069e8a --- /dev/null +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/util/TokenUtil.java @@ -0,0 +1,48 @@ +package com.elink.esua.epdc.common.token.util; + +import com.elink.esua.epdc.common.token.dto.TokenDto; +import com.elink.esua.epdc.common.token.jwt.JwtTokenUtils; +import com.elink.esua.epdc.common.token.property.TokenPropertise; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * token服务类 + * + * @author rongchao + * @Date 18-10-31 + */ +@Component +public class TokenUtil { + + private Logger logger = LoggerFactory.getLogger(getClass()); + + @Autowired + private TokenPropertise tokenPropertise; + + @Autowired + private CpUserDetailRedis redisUtils; + + @Autowired + private JwtTokenUtils jwtUtils; + + public TokenDto getTokenInfo(String userId) { + TokenDto tokenDto = redisUtils.get(userId); + return tokenDto; + } + + public String createToken(TokenDto tokenDto) { + redisUtils.set(tokenDto, tokenPropertise.getExpire()); + return jwtUtils.generateToken(tokenDto.getUserId()); + } + + public void expireToken(String userId) { + redisUtils.logout(userId); + } + + public boolean delayToken(String token) { + return redisUtils.expire(token, tokenPropertise.getExpire()); + } +} diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/UserUtil.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/util/UserUtil.java similarity index 53% rename from esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/UserUtil.java rename to esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/util/UserUtil.java index c69067be1..2e09b381f 100644 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/util/UserUtil.java +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/util/UserUtil.java @@ -1,8 +1,7 @@ -package com.elink.esua.common.token.util; +package com.elink.esua.epdc.common.token.util; -import com.elink.esua.common.token.dto.TokenDto; -import com.elink.esua.common.token.dto.UserTokenDto; -import com.elink.esua.common.token.interceptor.AuthorizationInterceptor; +import com.elink.esua.epdc.common.token.dto.TokenDto; +import com.elink.esua.epdc.commons.tools.constant.Constant; import com.elink.esua.epdc.commons.tools.utils.WebUtil; /** @@ -19,7 +18,7 @@ public class UserUtil { * @return */ public static TokenDto getCurrentUser() { - return (TokenDto) WebUtil.getAttributesFromRequest(AuthorizationInterceptor.USER_KEY); + return (TokenDto) WebUtil.getAttributesFromRequest(Constant.APP_USER_KEY); } /** @@ -29,12 +28,12 @@ public class UserUtil { * @author yujintao * @date 2018/12/5 9:24 */ - public static UserTokenDto getCurrentUserInfo() { + public static TokenDto getCurrentUserInfo() { TokenDto tokenDto = getCurrentUser(); - if (tokenDto == null || tokenDto.getUserInfoDto() == null) { + if (tokenDto == null) { return null; } - return tokenDto.getUserInfoDto(); + return tokenDto; } /** @@ -44,13 +43,13 @@ public class UserUtil { */ public static String getCurrentUserId() { TokenDto tokenDto = getCurrentUser(); - if (tokenDto == null || tokenDto.getUserInfoDto() == null) { + if (tokenDto == null) { return null; } - return tokenDto.getUserInfoDto().getUserId(); + return tokenDto.getUserId(); } public static void setCurrentUser(TokenDto user) { - WebUtil.setAttributesFromRequest(AuthorizationInterceptor.USER_KEY, user); + WebUtil.setAttributesFromRequest(Constant.APP_USER_KEY, user); } } diff --git a/esua-epdc/epdc-gateway/pom.xml b/esua-epdc/epdc-gateway/pom.xml index b75f10e3b..b1f5a18f7 100644 --- a/esua-epdc/epdc-gateway/pom.xml +++ b/esua-epdc/epdc-gateway/pom.xml @@ -40,6 +40,18 @@ org.springframework.cloud spring-cloud-starter-zipkin + + com.esua.epdc + epdc-common-clienttoken + 1.0.0 + compile + + + + com.esua.epdc + epdc-common-clienttoken + 1.0.0 + @@ -81,25 +93,29 @@ lb://epdc-auth-server lb://epdc-admin-server - - lb://epdc-activiti-server + + lb://epdc-activiti-server + lb://epdc-api-server lb://epdc-app-server - + lb://epdc-heart-server - + lb://epdc-job-server - - lb://epdc-message-server - lb://epdc-neighbor-server - + + lb://epdc-message-server + + lb://epdc-neighbor-server + + http://127.0.0.1:9064 lb://epdc-oss-server lb://epdc-events-server - - lb://epdc-services-server - + + lb://epdc-services-server + + http://127.0.0.1:9068 lb://epdc-demo-server @@ -124,17 +140,21 @@ lb://epdc-auth-server lb://epdc-admin-server - lb://epdc-activiti-server + lb://epdc-activiti-server + lb://epdc-api-server lb://epdc-app-server lb://epdc-heart-server lb://epdc-job-server - lb://epdc-message-server - lb://epdc-neighbor-server + lb://epdc-message-server + + lb://epdc-neighbor-server + lb://epdc-news-server lb://epdc-oss-server lb://epdc-events-server - lb://epdc-services-server + lb://epdc-services-server + lb://epdc-user-server lb://epdc-demo-server @@ -159,17 +179,21 @@ lb://epdc-auth-server lb://epdc-admin-server - lb://epdc-activiti-server + lb://epdc-activiti-server + lb://epdc-api-server lb://epdc-app-server lb://epdc-heart-server lb://epdc-job-server - lb://epdc-message-server - lb://epdc-neighbor-server + lb://epdc-message-server + + lb://epdc-neighbor-server + lb://epdc-news-server lb://epdc-oss-server lb://epdc-events-server - lb://epdc-services-server + lb://epdc-services-server + lb://epdc-user-server lb://epdc-demo-server diff --git a/esua-epdc/epdc-gateway/src/main/java/com/elink/esua/epdc/feign/ResourceFeignClient.java b/esua-epdc/epdc-gateway/src/main/java/com/elink/esua/epdc/feign/ResourceFeignClient.java index 68f229806..b52b556f8 100644 --- a/esua-epdc/epdc-gateway/src/main/java/com/elink/esua/epdc/feign/ResourceFeignClient.java +++ b/esua-epdc/epdc-gateway/src/main/java/com/elink/esua/epdc/feign/ResourceFeignClient.java @@ -8,7 +8,7 @@ package com.elink.esua.epdc.feign; -import com.elink.esua.epdc.commons.tools.security.user.CpUserDetail; +import com.elink.esua.epdc.common.token.dto.TokenDto; import com.elink.esua.epdc.feign.fallback.ResourceFeignClientFallback; import com.elink.esua.epdc.commons.tools.constant.ServiceConstant; import com.elink.esua.epdc.commons.tools.security.user.UserDetail; @@ -50,5 +50,5 @@ public interface ResourceFeignClient { * @date 2019/8/19 17:19 */ @GetMapping("auth/getLoginUserInfo") - Result getLoginUserInfo(@RequestParam("token") String token); + Result getLoginUserInfo(@RequestParam("token") String token); } diff --git a/esua-epdc/epdc-gateway/src/main/java/com/elink/esua/epdc/feign/fallback/ResourceFeignClientFallback.java b/esua-epdc/epdc-gateway/src/main/java/com/elink/esua/epdc/feign/fallback/ResourceFeignClientFallback.java index da14350a3..987d2ff79 100644 --- a/esua-epdc/epdc-gateway/src/main/java/com/elink/esua/epdc/feign/fallback/ResourceFeignClientFallback.java +++ b/esua-epdc/epdc-gateway/src/main/java/com/elink/esua/epdc/feign/fallback/ResourceFeignClientFallback.java @@ -1,14 +1,14 @@ /** * Copyright (c) 2018 人人开源 All rights reserved. - * + *

* https://www.renren.io - * + *

* 版权所有,侵权必究! */ package com.elink.esua.epdc.feign.fallback; -import com.elink.esua.epdc.commons.tools.security.user.CpUserDetail; +import com.elink.esua.epdc.common.token.dto.TokenDto; import com.elink.esua.epdc.commons.tools.security.user.UserDetail; import com.elink.esua.epdc.commons.tools.utils.Result; import com.elink.esua.epdc.feign.ResourceFeignClient; @@ -29,7 +29,7 @@ public class ResourceFeignClientFallback implements ResourceFeignClient { } @Override - public Result getLoginUserInfo(String token) { - return new Result().error(); + public Result getLoginUserInfo(String token) { + return new Result().error(); } } diff --git a/esua-epdc/epdc-gateway/src/main/java/com/elink/esua/epdc/filter/CpAuthGatewayFilterFactory.java b/esua-epdc/epdc-gateway/src/main/java/com/elink/esua/epdc/filter/CpAuthGatewayFilterFactory.java index babf5a4ce..b264baa24 100644 --- a/esua-epdc/epdc-gateway/src/main/java/com/elink/esua/epdc/filter/CpAuthGatewayFilterFactory.java +++ b/esua-epdc/epdc-gateway/src/main/java/com/elink/esua/epdc/filter/CpAuthGatewayFilterFactory.java @@ -2,8 +2,8 @@ package com.elink.esua.epdc.filter; import com.alibaba.fastjson.JSON; +import com.elink.esua.epdc.common.token.dto.TokenDto; import com.elink.esua.epdc.commons.tools.constant.Constant; -import com.elink.esua.epdc.commons.tools.security.user.CpUserDetail; import com.elink.esua.epdc.commons.tools.utils.Result; import com.elink.esua.epdc.feign.ResourceFeignClient; import org.springframework.beans.factory.annotation.Autowired; @@ -70,14 +70,14 @@ public class CpAuthGatewayFilterFactory extends AbstractGatewayFilterFactory result = resourceFeignClient.getLoginUserInfo(token); + Result result = resourceFeignClient.getLoginUserInfo(token); if (!result.success()) { return response(exchange, result); } - CpUserDetail user = result.getData(); + TokenDto user = result.getData(); //当前登录用户userId,添加到header中 if (user != null) { - ServerHttpRequest build = exchange.getRequest().mutate().header(Constant.APP_USER_KEY, user.getId()).build(); + ServerHttpRequest build = exchange.getRequest().mutate().header(Constant.APP_USER_KEY, user.getUserId()).build(); return chain.filter(exchange.mutate().request(build).build()); } return chain.filter(exchange); @@ -119,4 +119,4 @@ public class CpAuthGatewayFilterFactory extends AbstractGatewayFilterFactoryepdc-wx ${project.version} + + + + com.esua.epdc + epdc-common-clienttoken + ${project.version} + diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/config/WebConfig.java b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/config/WebConfig.java similarity index 85% rename from esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/config/WebConfig.java rename to esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/config/WebConfig.java index 254b499fd..e7e5c8015 100644 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/common/token/config/WebConfig.java +++ b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/config/WebConfig.java @@ -6,10 +6,10 @@ * 版权所有,侵权必究! */ -package com.elink.esua.common.token.config; +package com.elink.esua.epdc.config; -import com.elink.esua.common.token.interceptor.AuthorizationInterceptor; -import com.elink.esua.common.token.resolver.LoginUserHandlerMethodArgumentResolver; +import com.elink.esua.epdc.common.token.interceptor.AuthorizationInterceptor; +import com.elink.esua.epdc.common.token.resolver.LoginUserHandlerMethodArgumentResolver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; diff --git a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/controller/ApiLoginController.java b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/controller/ApiLoginController.java index 8f4033c4f..26cce4c81 100644 --- a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/controller/ApiLoginController.java +++ b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/controller/ApiLoginController.java @@ -9,7 +9,7 @@ package com.elink.esua.epdc.controller; -import com.elink.esua.epdc.annotation.Login; +import com.elink.esua.epdc.common.token.annotation.Login; import com.elink.esua.epdc.commons.tools.utils.Result; import com.elink.esua.epdc.commons.tools.validator.ValidatorUtils; import com.elink.esua.epdc.dto.LoginDTO; diff --git a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/controller/ApiTestController.java b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/controller/ApiTestController.java deleted file mode 100644 index 85a08fc37..000000000 --- a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/controller/ApiTestController.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (c) 2018 人人开源 All rights reserved. - *

- * https://www.renren.io - *

- * 版权所有,侵权必究! - */ - -package com.elink.esua.epdc.controller; - -import com.elink.esua.epdc.annotation.Login; -import com.elink.esua.epdc.annotation.LoginUser; -import com.elink.esua.epdc.commons.tools.utils.Result; -import com.elink.esua.epdc.entity.UserEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestAttribute; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * 测试接口 - * - * @author Mark sunlightcs@gmail.com - */ -@RestController -@RequestMapping("test") -public class ApiTestController { - - /** - * 获取用户信息 - * - * @param user - * @return - */ - @Login - @GetMapping("userInfo") - public Result userInfo(@LoginUser UserEntity user) { - return new Result().ok(user); - } - - /** - * 获取用户ID - * - * @param userId - * @return - */ - @Login - @GetMapping("userId") - public Result userInfo(@RequestAttribute("userId") Long userId) { - return new Result().ok(userId); - } - - /** - * 忽略Token验证测试 - * - * @return - */ - @GetMapping("notToken") - public Result notToken() { - return new Result().ok("无需token也能访问。。。"); - } - -} From 1d26c2346fa791bd6e5e515d397c63a637252b01 Mon Sep 17 00:00:00 2001 From: rongchao Date: Thu, 5 Sep 2019 16:19:58 +0800 Subject: [PATCH 08/10] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=BD=91=E5=85=B3?= =?UTF-8?q?=E6=A8=A1=E5=9D=97bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../elink/esua/epdc/common/token/util/TokenUtil.java | 9 --------- .../epdc-gateway/src/main/resources/application.yml | 10 +--------- .../java/com/elink/esua/epdc/config/WebConfig.java | 4 ++-- .../epdc}/interceptor/AuthorizationInterceptor.java | 4 ++-- .../com/elink/esua/epdc}/jwt/JwtTokenProperties.java | 2 +- .../java/com/elink/esua/epdc}/jwt/JwtTokenUtils.java | 2 +- .../LoginUserHandlerMethodArgumentResolver.java | 2 +- 7 files changed, 8 insertions(+), 25 deletions(-) rename esua-epdc/{epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token => epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc}/interceptor/AuthorizationInterceptor.java (96%) rename esua-epdc/{epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token => epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc}/jwt/JwtTokenProperties.java (94%) rename esua-epdc/{epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token => epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc}/jwt/JwtTokenUtils.java (97%) rename esua-epdc/{epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token => epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc}/resolver/LoginUserHandlerMethodArgumentResolver.java (97%) diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/util/TokenUtil.java b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/util/TokenUtil.java index 530069e8a..ba8a32beb 100644 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/util/TokenUtil.java +++ b/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/util/TokenUtil.java @@ -1,7 +1,6 @@ package com.elink.esua.epdc.common.token.util; import com.elink.esua.epdc.common.token.dto.TokenDto; -import com.elink.esua.epdc.common.token.jwt.JwtTokenUtils; import com.elink.esua.epdc.common.token.property.TokenPropertise; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,19 +24,11 @@ public class TokenUtil { @Autowired private CpUserDetailRedis redisUtils; - @Autowired - private JwtTokenUtils jwtUtils; - public TokenDto getTokenInfo(String userId) { TokenDto tokenDto = redisUtils.get(userId); return tokenDto; } - public String createToken(TokenDto tokenDto) { - redisUtils.set(tokenDto, tokenPropertise.getExpire()); - return jwtUtils.generateToken(tokenDto.getUserId()); - } - public void expireToken(String userId) { redisUtils.logout(userId); } diff --git a/esua-epdc/epdc-gateway/src/main/resources/application.yml b/esua-epdc/epdc-gateway/src/main/resources/application.yml index 667a89c9a..da2c4d266 100644 --- a/esua-epdc/epdc-gateway/src/main/resources/application.yml +++ b/esua-epdc/epdc-gateway/src/main/resources/application.yml @@ -71,6 +71,7 @@ spring: - Path=/api/** filters: - StripPrefix=0 + - CpAuth=true #爱心互助模块 - id: epdc-heart-server uri: @gateway.routes.epdc-heart-server.uri@ @@ -111,15 +112,6 @@ spring: - Path=/services/** filters: - StripPrefix=0 - #移动端接口模块 - - id: epdc-app-server - uri: @gateway.routes.epdc-app-server.uri@ - order: 13 - predicates: - - Path=/epdc-app/** - filters: - - StripPrefix=0 - - CpAuth=true #APP用户模块 - id: epdc-user-server uri: @gateway.routes.epdc-user-server.uri@ diff --git a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/config/WebConfig.java b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/config/WebConfig.java index e7e5c8015..21ddc3bd4 100644 --- a/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/config/WebConfig.java +++ b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/config/WebConfig.java @@ -8,8 +8,8 @@ package com.elink.esua.epdc.config; -import com.elink.esua.epdc.common.token.interceptor.AuthorizationInterceptor; -import com.elink.esua.epdc.common.token.resolver.LoginUserHandlerMethodArgumentResolver; +import com.elink.esua.epdc.interceptor.AuthorizationInterceptor; +import com.elink.esua.epdc.resolver.LoginUserHandlerMethodArgumentResolver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/interceptor/AuthorizationInterceptor.java b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/interceptor/AuthorizationInterceptor.java similarity index 96% rename from esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/interceptor/AuthorizationInterceptor.java rename to esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/interceptor/AuthorizationInterceptor.java index 3877934e7..de7aecf63 100644 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/interceptor/AuthorizationInterceptor.java +++ b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/interceptor/AuthorizationInterceptor.java @@ -14,16 +14,16 @@ * the License. */ -package com.elink.esua.epdc.common.token.interceptor; +package com.elink.esua.epdc.interceptor; import com.elink.esua.epdc.common.token.annotation.Login; import com.elink.esua.epdc.common.token.dto.TokenDto; import com.elink.esua.epdc.common.token.enums.ErrorCode; -import com.elink.esua.epdc.common.token.jwt.JwtTokenUtils; import com.elink.esua.epdc.common.token.util.TokenUtil; import com.elink.esua.epdc.commons.tools.constant.Constant; import com.elink.esua.epdc.commons.tools.exception.RenException; +import com.elink.esua.epdc.jwt.JwtTokenUtils; import io.jsonwebtoken.Claims; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/jwt/JwtTokenProperties.java b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/jwt/JwtTokenProperties.java similarity index 94% rename from esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/jwt/JwtTokenProperties.java rename to esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/jwt/JwtTokenProperties.java index ad484aa5a..3e938f90d 100644 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/jwt/JwtTokenProperties.java +++ b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/jwt/JwtTokenProperties.java @@ -6,7 +6,7 @@ * 版权所有,侵权必究! */ -package com.elink.esua.epdc.common.token.jwt; +package com.elink.esua.epdc.jwt; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/jwt/JwtTokenUtils.java b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/jwt/JwtTokenUtils.java similarity index 97% rename from esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/jwt/JwtTokenUtils.java rename to esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/jwt/JwtTokenUtils.java index 2dd2ba97f..736be6d35 100644 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/jwt/JwtTokenUtils.java +++ b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/jwt/JwtTokenUtils.java @@ -6,7 +6,7 @@ * 版权所有,侵权必究! */ -package com.elink.esua.epdc.common.token.jwt; +package com.elink.esua.epdc.jwt; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; diff --git a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/resolver/LoginUserHandlerMethodArgumentResolver.java b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/resolver/LoginUserHandlerMethodArgumentResolver.java similarity index 97% rename from esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/resolver/LoginUserHandlerMethodArgumentResolver.java rename to esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/resolver/LoginUserHandlerMethodArgumentResolver.java index 0a6dc910e..6e999b148 100644 --- a/esua-epdc/epdc-commons/epdc-common-clienttoken/src/main/java/com/elink/esua/epdc/common/token/resolver/LoginUserHandlerMethodArgumentResolver.java +++ b/esua-epdc/epdc-module/epdc-api/epdc-api-server/src/main/java/com/elink/esua/epdc/resolver/LoginUserHandlerMethodArgumentResolver.java @@ -6,7 +6,7 @@ * 版权所有,侵权必究! */ -package com.elink.esua.epdc.common.token.resolver; +package com.elink.esua.epdc.resolver; import com.elink.esua.epdc.common.token.dto.TokenDto; import com.elink.esua.epdc.common.token.util.CpUserDetailRedis; From 73b82d60a8d7b4dd5be429071cc50f78eb38494e Mon Sep 17 00:00:00 2001 From: rongchao Date: Thu, 5 Sep 2019 16:58:38 +0800 Subject: [PATCH 09/10] =?UTF-8?q?=E5=BC=80=E5=8F=91=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- esua-epdc/{ => doc}/db/mysql.sql | 0 esua-epdc/{ => doc}/db/newupdate.sql | 0 esua-epdc/{ => doc}/db/oracle.sql | 0 esua-epdc/{ => doc}/db/postgresql.sql | 0 esua-epdc/{ => doc}/db/sqlserver.sql | 0 esua-epdc/doc/开发规范/命名规范.txt | 19 ++++++++++++++++++ .../阿里巴巴java开发规范1.4.pdf | Bin 0 -> 1163395 bytes 7 files changed, 19 insertions(+) rename esua-epdc/{ => doc}/db/mysql.sql (100%) rename esua-epdc/{ => doc}/db/newupdate.sql (100%) rename esua-epdc/{ => doc}/db/oracle.sql (100%) rename esua-epdc/{ => doc}/db/postgresql.sql (100%) rename esua-epdc/{ => doc}/db/sqlserver.sql (100%) create mode 100644 esua-epdc/doc/开发规范/命名规范.txt create mode 100755 esua-epdc/doc/开发规范/阿里巴巴java开发规范1.4.pdf diff --git a/esua-epdc/db/mysql.sql b/esua-epdc/doc/db/mysql.sql similarity index 100% rename from esua-epdc/db/mysql.sql rename to esua-epdc/doc/db/mysql.sql diff --git a/esua-epdc/db/newupdate.sql b/esua-epdc/doc/db/newupdate.sql similarity index 100% rename from esua-epdc/db/newupdate.sql rename to esua-epdc/doc/db/newupdate.sql diff --git a/esua-epdc/db/oracle.sql b/esua-epdc/doc/db/oracle.sql similarity index 100% rename from esua-epdc/db/oracle.sql rename to esua-epdc/doc/db/oracle.sql diff --git a/esua-epdc/db/postgresql.sql b/esua-epdc/doc/db/postgresql.sql similarity index 100% rename from esua-epdc/db/postgresql.sql rename to esua-epdc/doc/db/postgresql.sql diff --git a/esua-epdc/db/sqlserver.sql b/esua-epdc/doc/db/sqlserver.sql similarity index 100% rename from esua-epdc/db/sqlserver.sql rename to esua-epdc/doc/db/sqlserver.sql diff --git a/esua-epdc/doc/开发规范/命名规范.txt b/esua-epdc/doc/开发规范/命名规范.txt new file mode 100644 index 000000000..684d4b35e --- /dev/null +++ b/esua-epdc/doc/开发规范/命名规范.txt @@ -0,0 +1,19 @@ +dao层: +1、结果集是集合用selectListXXX +2、结果集为一个实例用selectOneXXX +3、更新:updateXXX +4、插入:insertXXX +5、删除:deleteXXX +6、获取统计值的方法用 selectCount +service层: +1、结果集是集合:listXXX +2、结果集是一个实例:getXXX +3、插入:saveXXX +5、修改:modifyXXX +4、删除:removeXXX +5、获取统计值的方法用 countXXX + + +数据传输对象:xxxDTO,xxx 为业务领域相关的名称 + +controller层根据接口定义来取名,接口定的什么就取什么 diff --git a/esua-epdc/doc/开发规范/阿里巴巴java开发规范1.4.pdf b/esua-epdc/doc/开发规范/阿里巴巴java开发规范1.4.pdf new file mode 100755 index 0000000000000000000000000000000000000000..35865f05998e230f30a8ab336991af055a584aa7 GIT binary patch literal 1163395 zcmeFZ1z22LvM}0Ma1A6tu;2uDcXxNE=|DqpcLKrP3BjFUf#3v!OM+`~cXx-^31`ln znYnk~{P&yt?w$Wl!|uIzcU7%bt7=u%T2;F#l*ANcXN7C1sW1YRY!X#pre{S2&7_T z<4D57#PJJ8(aG@**cyVu%=!3%ys^EdwDoHn2;rZXAX5?uB1i!8LPY{uoBnF_C;7i< z{a34uqBc&}jwEa>jIv-;2VD}@pAC>OKb}~be%-OMKc2WAPu#ywY(KeCAz^!jWMg~0 z`_&y1HqOVpM+EjqD(sI4?7!F~VShwmeS(K)|2)(pC_qVP+wL{6pjb`AW&&2B->h z)MbRI9itk^)$tKTc#{6vbw+uRDcD%V##I-B%0$A>&Bg#>lZ%aofr(9@1fmhv zj*zZ9ko+1eMoD`cCtFB^zclvO4WlZfn!T~LgY8cifo_bVs*GYFXD|??A}Io)u4ZiF z00DW_-N#{YAYuCBi2Wq|rxB9?zi|ZFGfKRHFe?TE+L(eEsnrSciZenOfd`4V#OnxBX}h}v-qOJ^h$KoeNU(N=?n3H^A!_pIcQlhy zA%ye6zF`*XLzDMgYm}>Z3st(KL54{{;{+Yu!7BK)9GB6RiNTcZZvgl?@f~jTpG9QB zeJ2YPAPJgQT7J`FBqe}#JLlqIjw;Uh6ie9lS;R*%IVifU9vcnNk^K|P_cPx?RQ5%Y zOAhSiB3}A`#8t zZ}21tZ0?_2!jO1VV0V=q4GaroIw5B~CM;Qu9)&GFB-ZFicIdV+EZo9@@4H^O-isx> zCjU-hvfC0d*0Z=)P|>OX#%cinJI7iy5*0U#^T!?qU+JLM(|~0m^V~qK*pV0u>VsCM z5{)?aj$sQJ&#`F2Hkk%)S~(adZmS{D67$ndYp0G+RI3u&<5YZm&oS|WdM(5d-QVGp zPE(?GnaEVznH1O&N^G^4aP}!S8Cx$H7AKG!-ufx*63gUk+WoR#e^P)y8M2?#=1FU~6G(2k`IrKY_(S4nTXbt)q=S$)kmp0NXn_ikchSld!TtZ1iu}?3@s6Q%7?L z5>{r`U$gj+YW@@RZ#w#C%D*hZuU`ID&meQb3__8GQ5xbNz(8SZvo|0TCPrZg;7^Of z&CUdwZjaaWEG#S#FJdbN0-KpTl5lczF$z1I{pu142Qxdw8Mr>)u(3ic{7=R-A0gS8 zm>9Kw!(xFDRCNSdX+ThZ!HE1a4fM=xoa`h&TOeWKf|y+hafosFNdq!vK`cKx{$*kR z$nj4z^%orfZpRo!6f(r6M3@yjXWWeF(zYSlDDz8 z`G;lttK*FSu}qm+*#9ilNB8!}-u^`Wo8JB%q5iRZe_yc7On)I+i1_{>SuXZJNERZz z-zCe;#=-dyNS5i3l4bcNSY~z-W@d=f$!!hziVKJD09 znExOgPBzy6cj2%%4eM16jn`piG=fT+zI8+%n-V<70yZk$mB?C78bvKO_nvbC}PX?D0E!uaK> z#YEM9^V8ChIKkf5=BMZW6>t8@adWV-ak2jF;GZn_!;gnW0EUc&v;+VO3JTx~`2#%6 z07L-@PoBU(fkS|Yhet$2KtjPrML|YJ!NYorj!lS9OhkxJKtMvyKt)1IM@B$E%}GPY z#LULVMoh)c&&9&W05NGli9jJDBBCIp;G&}9vb-dC39(oIhp&eg044&QE6gSg6e$22 z6AA_s>Y)Qb3;;mEL3;bM!@s?tpkZL)p1>m@A|XQnsxbi2P%tpiurP3Nu&@wlFUa=* zSWGyqm(0RXo+=x|liFdkyp731AQP!*#ZehPBxg0T_d`T__8b=vpMsK#n&uT7I|nBh zHxI-wNk~db%c!cUYiMd|>i|tbug%QC77mV1&MvNQ?*8ur0)v7>LSy6N6B3h>-=}2f zDl?k z<<<31zMudwk8J(U*MG78V8;{wH5h&@PYz1``(UCG!(3VP$w@yQicqZxOIX zVzMe)5y@Cp4slHEhmoFY6$=E%5ZW8s^D`YF7}hrqWH%Js1D|6l9lqLsN4*S8`BKQXyuPz)8F5PB|QKzs_}k=Q3GoX zHHd&d0S=r3v}5Izu`fT$(hmyOg~|~;02;o$y~IEDU<~pmsJSYr^0??BpO-RKjXLKN zzNB}onW#CpEzw9k62>4Pm%>JGQTj9**CefHLnwnZ2Bug)GfUChB%-JvF&d*QFLUYn zkb>Bkc${^=Ke-d7BE)Z@FW7nA72!$GUbyNy8KkX=MSTXJgYJE~W01m=a^e%r88f%d zF@b^3;`+If(B2jK&cd=)MeooVWVFpE)|8-@Qgc4;IVd5it!yfShYOwSf+MRv1{|rX zFDJi;TXe5Az8af$YSaNI*6iCKi51RX0FX`wiFSxB#-1yLe08#RplW;ooCTef+TnPm z@t&CpAfxW>6guu~nh4aFNkurF-$Vz>Hzpjy5ZTg>d2$z{nu!PQkPX0?whwn34wkPN zSl!iw9{>p5=Uk&7yj|Yk4z3L90S~X$zrt7&PfZU_QM?g$d3P;%n9*IZOdE!-8|&OO z%J1onGnT!Y^=&4$#35_It*TEdWP(?MV!Ld}Fxr6TRVG_!~s z-y+hgr~Cte4773QOgvKEOnOv0^8lDc-?mVj4lkdzeg{&C4V5|adTy$F#F15~ZH~_z zrUWFTI-m@c3qj-AXac($b}$JqjU{Q0F_XzG$(6`rFpoAl%#%F;ViemUgYf+tsc}gV zeM=`QsG$CfFnQLL>~pej0O#%v9SH|@Lq_Z7z^8eV+c8dp?{^a}%p8D)Q`^e#>B@lb z)sB@Lyd*6^`xOP?foI3Z)LM}^A2Pm|y7|0gFs(U~Z)oet4Tx#Er)cHAV^y`_#0Omj zgKj7y`gceA?3&-uW(d^{%=1g)6imMG16u&;DkMJpTlX3ktT?+<)CO01^lDJE;}S_M zsii(W9n{(ICb4*~APz^wPW$%egpyJXr^g9Upq;pwWEdn)l^=^(Du z@n!=`EVG%1ZJ(lQg3f?zh(clIR&bl{ zA6%QrsXlrZ!hk;lsaLUCqDq5F-HrM zU1`c|daG9t@}gs>uAbTHV977_8%$8vRZi}&)hi~Gw^MJ7!cLS%Ry+W(aGg-q@p~(3 zgh^up2Y8%X@2mPXX7pn>`bN*FtrihuqY!`@rS?fF3G+pV_s1T+VzH$YEv+Jl2&W~f zO>v$@d&jo}!NQZu>)`F0;o{p^URizzx#ot%iHSXm6bicS%tO}j4cw32ohcX}zVU<$ ze-2;V8=S244dbUZ)wiGH}2y>Msbr;;zTJCd{orR$a zh{+b`hNQ_d>@PYM?y}!8s@5@08<5AoO-p_ybq0vEMAobIJLpV-<$Nh8t{2_zC)7in zjNQyA9;7r}mIY-UFp`bI{mMNm53OW;K2aDSdpg{yQS*3bZEbd!Iy-cSOXOeBF(TwT zh2YIDshMWT9NS=DM#@`Xd=Ck(3|@l27`%u&EvsFAl6{^c=UfqszxJv7E@tv%Q8R5T zMg6|95Sfr$N$R<0oNQWjnO$6@G=Dshi_aI4$eGfp`b*M z*_nLWY)2DcJxH^~Ouo$nSZrbPKby^Ebhw)zzW_8 zV$1oHvP*|{jrkW)F3&M1*MJ@C?BCroTKW$hk}ID>C? zz^=ENS=N_@s22N9qoiOt15+|v`^pYuGB#?i=pOTKW$$(ezEuY{={fOCgD;)zvv*v* zulF~uy`9pQ`iXq7w@0>lLKv+hlKA<$c5|ki_|-^XWe2F^BEbvmIcD$3PAggmpH@7L ze3mi3wL^6zI7BE*2C$Xt@}iCZd`MqfQ$%<=89U1(_`Z6~_`}#e-=wfd@z%BN13>mm zDA6Twa7~SDlEH~oBEjf@*z!YoH}M!W%RZ=KLTL$o~EPEbr1$I^sFIOO}T5`xIgx%z$S{C~ODR{C- z{HqyR^p`{O=hy!8vW0Tw_Q-&+Ufg2$i+9G8<-oewZLLtsK2ra5ulwr0o#)dbNBm<< z3$xi_$vINPhfI=!-#$}~mvt}<8*YcnR7B*aV$3L@176pq>Ac#BKO>XGp~8EWl2;IG zW>@Gcf_%r()iKu-X5Ogo=cgNaCf-am?Ui5>dcVxgJ-?E3Cs}Q3Xt3a8zE!heF_z9z zsR^oi)6?HOc(tdtyMKn9u?MhW_iVwTvFx9tzH1;MS_3wY%Xn`LRcw%Szn||! zF_+8Wao)~2pZ1&g0}I8M3)!Of8dnHn@c8;~!=J1W2T;7ys0=)ZClCRDE30T~);(n- zUOLOFmCE{B7vbAp!$Q7MSIUpv=B)FA@8bdAexqkMz{njyi!L(egy499!Cde0p8f?U z-V9gQzK`6vTeaooZkG7hj3go50ocV)g8~LVs+XgxS;(?UDj))N*-?jvueVPnY=RQ= zcl#}U_&?1>f2^O`MRO2)0HEz|yjrlSM-As?LeaZkFEgJ^dj7nxQm>Z;p&J}Wc6^+4 zHEyZ+vLq+{_yM4bB{iTJx6a~A5VZu07C&ZQ?L^!WWQd)XIyV_Jt}_#6$z}DHu7X5J z(Gx`-6)609B1e&rN1ph)K8MTnT9cQ3#HQ|95>NV#bAbQ1eV8Yn7@|;-f`V<0;VD~T z5$fM2f(YU_tJ%)(zxJ)YXuV*ij@&T(nuvCEI=C`lu6Oym%L=Y4nAHZ#s59C$mN)f` zy|EuUB}?A%X~(#gWo=J6dn)HMD@zu)7?w9L8^blZA)%q8-@-0X`^z)f2S6df@pNty zXFOHK$c$}G25UUq*dUiJjHR4U5a^P_kciP~?d#^jlSO!69mnZ6eT@Fo!Jtx2C6&}CJru@WkjXZ zd8avfF>2V4(IH?CrMdv~waFScCQUy^mT}I68;6p+QznbZj8d&a)mBM|yo4TGSs$KB zSsWYiFLpqEzx*!zL3?z?(k5jYCPBrn4j6M=95i$m_5eVdmm{yRs$7(crBUs&^N(^c z_}WSNgn7E&Ea=PC^6tPj*S?NHSFT#l1%aL+hS;oR_MB9AC5KO*dHcspe`O`BD$^>x zyPKk~=Sr;llwdoJqOlv*Tg>gu7Id>PV7or#GL0O07}J}|g~gbiTt ze%w}Lbl4qI{p83;-MwhkMB}ld46lZAn>d(qYE+duK5Ye|V1D|ZiqmX~ zpHU`YT^8u#7^=nBZAXsQo_YJRE2?mAPOm|sXRE44-L5t!YA_w0Ph-Q!0)WT%n$o4dw}&5#z;++(qz+VK(SG`gq9JJ96E%6waR zyMbS2OP7eUjQ#%2ii*K$TYHq822MysZlk7aP15E?KGU2BWc_*Z0Kh++s=DEQ&pBv` zQVD#K1|?X^dP58evonxwZcblO_*-k%JD%Vm*jHbsdzF;FH$;PH>NFLFXX2(VZcOD= z)f7+ib7;JgZzn1k@J!j8?;4Sc#iP$MAKlLquJVGDV2faS0DKRqB|hy@ztf(-W)W!G zFPxW|%SgK+U<^r;F+|hKkNq|QcBp>uFbPkdp_8#|fJhG^SnGZ@n6fXQRy);MQ5)PM zxb|MS_PEfelCAXt!0>GL0WkVfektvsV3vAC#V=yDcIx6FE65lv;x5GPNf`Z?V4szx zH9BGVvX#kvOAZf+Dfqls_KepoP#Y+9QFXQEi|Y3P2rlb?Qwn_7QZr0Pe-TYG?Su3_ zM%P-6fFL|FuxRNjF`pJ|g>wA+ozVPghWLH$svV8j+~gfB@%hNt;NLp%ZykWmR2h~s7ROe_ z_!kR({~4irAhxnN;I9s5IBaF1|N9Sh-48diB>DT>2r!N(rpOuj@-XzuYNKkSt6Y)y z3s1sK$qRZre#07UrM@wNed;8k5e$CM! zVz9ZDFb%(==%r7wg}(-m{O%y#li4pGpJZ@Ifc%O&rwvCQs5;b)Bxzb#hg?Y{E3W*S zo0dQ58J3!BWP=IVthq=-pRQ{FJ5*N_COT7Spazvgb$)ChQD?sm`18Dx6Bfr-g8IMl zkd~F6iIXx3pX^_F&d1CUvizPM2WHnj?#oAqSOTLON#jJ^@?5T4T3W-QT~EoJ{(Qsy z%h!1D5j`F(tG1G*&;rtCvsr<*Pq@k^#cEG#X7we=e`MmE;+Fa74P}TOQS3)kYL6h| zJyT&quP2OJ(bo(rPeE9&^HL<&7RIxJbg{iaIJ*o z3(KORGjNqjiOUCWG``PcoCOa6gUZvHnR+}sY)p8iR;jsgr;X!Mty275uk7Qs{NNff z?r;7-mcJ~#S)t7tN#m*p6VhI0&u(6D)(pwh4P}x3Ku?RZY5VL+$QfEqdR^1_N>^77 zJ*I%pG9Dh+WLw-QKdp$BakkPRW#^v1K2ukDRV2-l(*@cGJ~JHD z{?5#ybHUjJqt2^KU1-`OgBYDcLQz z&8Wgp!3X4!qO7M<{=Ct*rKjwiw-op~KR)EwhX)XRwZ^#Q~qCZL9f_jHdLj%w_7s?6=x0QNFNa;yeLuj z36~Z4otjhjO()!SP?f0Q05S^k%5vlm z(F0DyvK)Z9q695KyVusu9WnEtUAZ{z?;Ck_y5X%{mIW~h!1&@FwizGxo(pxndpd3} zVAIwx$J!FC>fPiz9X`AMGU1&=EApgZgtJAKkP?8<7yFA@o)ro|*PC#v;3EDXW8x9c z*#Q7Y80>2~59LT&Ae4D_=#xK5>(6ItIg#Jo>OUj8`5&jDg4vB-r3CHegRdaiTc9ju z;;rT*qeBfvW1j5`@fiQvk^D3Idbg-D;;?wv7R8Y!{wbLG zUwan)JA(unyj*NRm9li)mYm4Nj_{V2PDYp`{j)XtGgJj!O1tB%Uct_|!r93^3p>ucWCZ}U zuR0;UGomtJL(8w>zxM1Q#?*<0)>CSD@pWHV5rJMA$;QQ#6;01>jN+uFYDBIT(a0PNFYQsf8r_dHHpS|T zD4$5#BN7(p>1*hki|}`yOnRUk>9G%n?>aV8XhiqO_!atdE$1BGPDe$~3xoJL55>c%PK)B^j zv(w`IivPTSAT9U(OR~VaIH0!>z9wOmc6L26tZTC&&Wv6!dx^$~$c=MuKnQVgS;M=1 zS}p4ANP+m4KL`Jjr~l)Y<-f324a8RQ0)vBTB6D-#_+mPxJ2|TXQR$3dXv#Kq{2DLq zug_hsZf9PzLfgB4GH4zLemhE?@S7fNyWrTPHTurNY$SLPQ@dmE0Jv1vYGAV7)7#Q& z_{di@<)(Gj-rb;4njJxWqiiyJSh-9VU4pRf6MQpHjJ9pBGnW+*-RHgKjU#nW>JuR8 zurPyj;LUp2UmN;`pa0!3Paw+uX^ma`?jS$L^Os);#=b^nF z^`6pipgm$vR)L_Q>h}kyJ@ewm?@$QBlSErgcBrL|Fxmc5m5GR_V4jCCjrm z@cJEuh4(JigC}e_jp1Z%5phf9qDpSrmwAbwZXQ{1u$XdUNhtO|by3x=Ud(uMCOLX5 z&k?`BhjVM1NeCN?jODXP+O+=;MQ%j3i2v0)+#EJj<$eHLcQ^N6lyBy&{8M! zB=DMS26bv>)bhE&7&KReVy8ECQ*A^2;d(&Bz?>}Neu%~gg0zrZ?Oow=gh@~BQrDEV z+;K*75Nv`k7MbX1z}`r6O$!W+FYVgb5CJS3gv8nIOn9``J zosDl<(%YNfOcPg0h1Qdwbvhhbu`&q142B`9p5Ef%MDaJ3j}_X|9*+&J&%@>Z*0Xhs zSrUbAdH-gp%K4-))F){n@O-_gU*A+{vo#@57-uCB-<*k5mQPB{v-Jl+GS3ohK=x&{ zoMOcBh_L?)mHTH7wIY~T1v@G01)(}cgif|myZi3Gt(kH2^;dkVmS`F0MOJ0n@z6sp z>A=-(5 zKGv}?ex=l~-qnK^^E{E?4yVA^R$W;J2b zB4C9IR5cYs{n<$Tmz=67l8g>bFoei|97o@#mS@PVvFPPc+rHY2+FJ9IOd*by_KP0TPA`wMR9A015uE4zjmCE3_hjx!P?1!&v;1b46wy9Q>JT3QpA@UmZj9-8K*%sKfX^Y!=vmaF$h+dWCcs5Q`z zfCUq#M$kR&1KCXNn+XSIF%Yk=8GVN8%wZ;4R9nN+aPD7?RFW1+YhLWBnlb{Jc{FilfquM<{6L%}_SMWnB)yW5r z?!*=}@Io~1DOqqlaYU!D-y(Pw=Iuxeb>@#uzAV2yQ5Y$EJ-Y1daXBeq7`uvI1}B%B zUH0uA1*fS~P&(pzy&~_6el1`%oqLm4lk9oNWa2)h$Biw= z?NJH+C*m^JjBcEv9~PVjWPV_VvYgO15c8F6cx5^Q_Zi-L{YFT*JE5R@tfrOM#=rw$ z%C^(FjplwBzj-{GM*qh?)T%f8$kbL+5;M(>>=&<=-S%qd$*+i~J$zeM?3VF#ojIDV zNUW)j{i4Qs9N{w>pgI$85Qv1>u3M+AnUdqeApeKVbC=km*HJmTh^YzFD}}R)C+uTe zi5e40Xos0Xny~IjdGwum@gi=ykytz95%%~8QKd_wHU;a_JkU9TRHIF{5@|XkL;J-V zsW>&f=VM0I2{oUPH(j~TKIWg5%~F0S5Xd5n^S548<%iU7I1-oZ__y4{@g#n*DRq7S z+A`#b{8^X$$B=R9X@{QfCrupl6UbcWx}pTM84aO;vTfHFf^Mm-U6Q+G{x=4vd>Ib-=G@|l~jGp$wCLUIPdxHENm@$b{bg3M9mYoN?AA?pcQL?(r z2$XE5&OHbn>ao4Z&Ge$N6GHrtcXM1ap>txjmO}Cp#xW(dlNX`(J=ETvb~-e(j=zvP zT&<-goLTcDuFU3TaOg!tin&w)>ZDAh{ojPFbRK zoS(x+cmOaZ<6UdaMRhP@{SYTm+BWUVa3YV;J?zE11M4|U4##Uof-`W-HAL9;1@Opb zj_;CQO`Y*Z(pC?7zuatChHBcQq$1Lr6FkW3-jQT^PRp}R!-CL5mNAi7C@jvnB01uV z6-wQW4M-gsmQDfa&Icwc*X1w!LPehDzYW!M#|Oy#T%yp19Z_}YbGE0vA6>1f zOLx+1?xWUQd>c0zC9}fd&1hlI<~Kbs&;AtV*`k7KMne6z>sBZvqB4m!UxvzyQNqH0 z#sEqH?(5gX>PYR5pkF(q^_-|hHS7dl++#Gh*gCa9!~lxDxUZK+?=9aPTwWf)ULaK&_bcQR<67_znR=+Ik9+}ZO|fu z&s(q28w5*$pI)n2)HhW1xabau8uw|i#^F+f z_?;)A+j0|_E|W-)np2UPfb`mK;nRJ>>3KrFuK6^mIpeh-uj_M*k5Qg+m$8Z~>Z>6q zprz4r88jO7Qg}9iN|d_{HSSWXgK#b(jXdbM*J)lq6B^(&hvXu6n;ItN51ELc zaYi`A3Pn!Y4sQpp@SRfhF+xG0B#bj0>a|HDc8zY&6gJ26w9E^5vMUX&!o^+3w%8&x>w@}W z?G&QS>9Pn*=OqnE<@M~;0PB@Q_Tq^K)dp_-&re5ULk&(_WaL!0sZa?p^?F zzr;|J7uTwd%F_4PfZ3yo4^5JAfgTQef}x(mW#W$4-Vu3R??O_L6Z2xWE|huJ^}Z0K1%xlb8augQ6RUQK;H94N}|Jpa(ou{}SAfiDe!p~_sG zaOybU&Qh-hM%zu4?GE%bF6lbyhTui@<*aopJ^=KcQX(78^SV=lBD>2czHI9ABr-o! znI$a1Zn?d^Jr~qza|LmqNX_G>OWAi4?Ja#pME*fGcIJA4hu~V;ZVPYnV_UI+EYHQR zn}E5*jx?K5T)YKm%?H)fx>?2CJ@v;-XGz4YkL~sK>h*GyS)OCtt5-eisi|o{(96ID zB&pC23fYA*9}#wG#(vK&{_}GrlJ1anb`o2>67;IOipo8k|CSV7b|?)%RjQ|zSZ`!i zWfZ2{o79s@dZ1vLkbfiPcqvB^q&C>6_r%Cy(;Zl?e<;|V%+@clZu#9UQ5I}eS59NeMVL2j;efdX?~X@fjsjZ? zHam%MGauSic{(u^df8z|az$1gKqV0WWdjoB!x8In=jQMYK|>ZdRq=n3S%Qv?Cm#5a zs=O!O!aH)F%T@*OW_ukiZL zWsif3U3~z&x`B38S;l)A4I)=5;TK+<>`P}ltnPkS-MfdPRhjl(`|JE{<{^($w_#xs zo@9IN6WaFxdqlld53_4cv)sALK_*k5G*Q~O3z4+(hi^!|6qYJ3)QkcK%PdDq#sM!> zZ3qY(EyJGD%5-N1gxNNovfmPy(3fRVl+?>wwy$^B8PUBv79`j>EEhpxHE=m+WyyiY z0pw+a+Pfbbs$c{=OD~=t*JlU#Wez) zj_jvOfLch z*;FG8pSOBFPKPFP10I%mFI@aaij18H^Eb%au3|@MOPCAtri1k)Za#j*T?MKnj1=1l z3DXbVeVz0WJ#eWxedC6M>Lk*$KnW;%|7|7ezR8LEJUbzJ)Dpw1wLC-naK>^`7yb0oMLnTCEU1o4ZlcY=r(GBm<%@ix7zY{mFq^PrCv!I2pW zkH5bk3qKmGfquy?%@;p)94<5WjkXaMx`z(%48Dcr2+-ZDO zMpQ{Oo%3K0cpLC`);}53wP_V;Pyb)3VBi^}M8; ztp_Pf_piR^GrrwVQfg;qy5SlWurYzJu`H+>-?Q-Drl*N^s`h&A_ z8I7tyF(jR(ntjZoE2%PG$&|JUQ{Lk*d!SyZO8c*Jz>~5yp_rxEDNQ%M)JXr4FSBAZ zX}j)@Bsq}hkZhdz67!7-oQK{XAK*>TZQ_eJ&b=t;i|uW7W+zwqEWY**05jmOVRhb! zH9${C=CRYotMS6#_C+R2d%&$FLQ(HDHTjs!&?ec~+GjWxISVEhT3YQR!l`#1Nxi0e zd#JVR0^c0Q?r3V<(Sy%$S1hMqj%N2RAdpG}HgJucCq?sJbrnWu<&@_17NdAPS)Fc! z7HON)ULClY+7IO6eAF~cpEYkL8!uHVO*_vlob_rpeoAxpqC$i2mURV>Az`(qx(c_I zVTV$X%%EIWU!aT8OOA-ri;eG?@rmV#1;-?(PTmJeO!5vA{#HT<{lE{oy5LmZ_t3Qz z9vU%QQZ!xhr28=}r7AN0n_PD^k}`@*d4#k;3^pL&$gmJ9#A#qdl2Rl{sE=hqfxsOD zf>HJxr_gJ_jDq^4U%^*_v+iv{M7FI7Q8O1d`6@RP#blV6NkU<~TgwsZ>{e$Z^Yj;C z;zM!rkzi-c_XvE}Wa}`IP^EP*L+7yr$r15UUPbz@6O@&BZ0`rx!d`Qf8JLZpY43s1U$<gtQ-=zevZB<8PzHx;^Jf_eb3>Jw$;{s7xvacB&;)BzHR zS%G6`f+58#r94&PWH{N(@oV%yhH`FGn0Qr2E{e&S;Dx0Xau3vA((sOqxD-)ypB2D# zQegkJ8oc~NWyv2av;O%J)PKCk@fY#N->YhYwv+ws5fT+US?PISG#c}~*1S4+HY6QU z2$Sa7xyItyDiC9=0|vxHvJouQP-6)Mu_dyJo8SsPaUScCcE(GUbzQVUdkY#Kmz;8+ zk>r3a>G{IB?HUfjqZ21=@Aqse-o>C|p;0Y8NjqF#`+9=NCJVQOJ-g$k?Ig- zx;LfaX=tNl^OgX~sMB}H#DR5AhPUA!%}za=67Cb}L(id`t%vzkUGjS0edCbqA1b;a zyieHCQH;3Q(sa2_^{^@h&L4N=#=_6jHL3C$EAqRsgPU`?iaqic2wE*23Z`251_`>h z@aZE0-!RQZwUz6%66O_Z9W$}8xKF-VR(v{Tk^cPf{>%9w_UN~fmdt%>sCE=zp0`_i z6QxJoy)%|XLmdozHx=8i-HI}6rAV7Z^P zh+`m5aGv-XH_dykvpaU@3>JU>Fh2u?mVk3sHKO0^8b&)OBj9q1yBC?fcc71kNX4#p zIAL&Nl1Mo%PNd{$U6*~0e;PCR0NLH~63he!Zr#y2|Y0V!I8 zbZRp8^6@2)upK9QAg@f4s;l6#8LYBw#$ueUU}h=Z2$U}qSAkD@LW70|ckcdyF}I+c z%AGA(5;`_Hfbs)G2mV|^@xS?6!W@v(gpFe2i=>CoDh;t@nJPmtU|TR4mH7!OE?dT` zsteR)ly`ifAhyvccHrhze$N)coBdXG327>TN}X10s0B;qzQ!y<56M}<5VSHR5nRA8 zybLLq=tCIKa#xEAddfOetL$hxIgAbka z-C6ey9~xUxuZJ7d?DLEUkE3qGi7I7TQ7Xza?%p$ck!@;V%Gx~jb5rGwcLaxmJQZft zMy>UOJCK0wJ$%d^tqQeCB8%FdEmu~E!A8+7{Sh+wm?O!B=PuN_|C1SUgXeMGDfb=i z6l-gzXLW((Mxly`LPP>dGpo6B;% zgAShWwm|&06-_4WKOTtxqXGLr8_vLd46+V~pAILsV29$)Nii_*Zo>DSr*SJ#c^##M z21jU~ozQ|xfxYbxP?nZs$dc{>Kuc$R`c%)JRJxOBKs;(X#*T;2_0&%!u6JwXqx7;? zxZTeYs^!m#-NU8^vD=#abh}Wv z+bwBBUPCZQf*PglADLeLW7f4mKCJy@=efdnSep9Lq5)fYylq?B^_`Q#t>f>goy9`) z#x16c;Ww(Ko{Gh-*u&y1xK#47l5`%?Q6N5;(F|pU{+%I4?2?Is(FV#Kn~$3s?s=7O zLf3JUX))txp<27`41k4}n)eh(t2^0-v%)|d;;D^Um~JYn=%8@oJiBYo2&eDp$PrIQ z-zY;1)!#D}T-wd-=-$J}(o%wRqe;^dtvisqF*$Js^!v?4Q#8#Aw`xYf6rPuIe6&ntp3~Q(sLtK2l&J9R z+x(vH)8MwJ=jdHgrIG&na|G{+eEH5$UZLxx-siOJW40D}6$)pYLV$zbjceEWW-rcu zwX;dwC0P}7G(XHM|Le(o@IBs$<61y=WkeaNQuew!I?Z)G9%T)lXc;avx(V&qDFvC4 zn5de9=ld;3z3ejxFIi`{8=@P8plRHa(L5r9j>|?Rj^xL8!Ik6tNk+Y5udY_8V4*^{ za;j?lL=I;}A{As`0Tfp>m^|)rFxtN{Y|9my0NKIfXd3i0*w;o%{60KhPB}3F*eZ6A zvNrkNpY>krp)mnGvEidaIBAgZ*L^;NJunN9GOZk_B6SAfOc5_@%9#aE2IF&U5)@Lr_>69A zxdHi+1(B1sl-w9u?D7H{WVq2xXblo#)zOS^(S6Bfa&kz_c#D&zx6DV5EZ?T-_TT36 zF7b1B)1EDAh9`oIGEIrx$IhRB-f9S_zJvU{hjdMhsi8AGmq1rRl5RqOUQ}rzK*Kwt zNW6ISJn68ZK@r#L`zSy9PRdXzCW;{b|KYmg_qD@+J6;~CFBT-Lr7Yf}BR8DFDzgg% z*(IZ{dt$!+Yj=PK@H#eB$I2J781tnj+{|>!_XThD+of|HvGI^A&wNM*#>%nggvX|F z5$=3uS2&(4qg$7n0=~~Nm3H1RCiB0<4~^M~qRFEvl$I_|r{(P6(#7nyqna9vE`s&e zhIkD#*`rgiM?DsJ-;9*2G_x;ghM%RIJtxnTC(aj()5BAK1RCoz()tTP=OccwO&JMO-L7iE3AC~1TEBco4(U!=%SX%p3 z6vQ!At_JsR7@u~Nm>$E~Zc0cT9Mg*9zOR?9&mo|(&u#q7S+s$(3wi$ii_wTUOi?k<8mtpjlfaO5V$$%ol2EZP;+_yJ{liXk z==y6ZW1jsy;*pGNfdp$qu8UG_5}U=<1K_O(%;1{7xQdHd^=rsus2?E@@4RFK#6#Zy zVy#3^f$gi)Gg*amxOm$?k9@+sBURe})&F`_Z_@$%RKWD}n~p}i;K7rt&sUTAnwU%FD82bRRxWhR z{3V687|bH%ey@cp84W%N?dZ1@U`olTN|rt;6j7F*PmG4c{(YnTk4wJ)q3fpl+)Nm@ zDitpp08zZUTUll;@L+dzn+f!Ks z+2;_M0Y^#eL-npya-vL7q|rvdAd!Tzk?~HPX(&xa9KNwx#5==L45qW);ETzIh#@(I%%n06IC(=}d#v8-0nB z6~YCDpvnUDrS&OaAKsSFo1e#g)OvrLQPa9^WfqiXP7DWg3 z<{QFsdy!DC99oE@xaKh3=j4}_`fx%X?%FZ1b~ zC-CpMCu|tal?pEl>WLN3puZuafjXW`nC=j%^dAKH*$eoUhLGwmRLzvNXS{zNEjZZ3 zsxh>G%UhzibGo%~) zOxuFWS0ka8!o3AWP5tyFTe3QM0UT-aKTDZ%nf^L1%g?Pn1i(`DhS!$hSiA&EYOyyG&b7cOptb?!5r0tN9_9hL z4~u7TPK14bWWfyHf!?#MBYduBTyKmy1`ckf_CXp@UJ{7wl|{;(Q)Cv7dI^L74}0$& z6~(vbi?)hjK#-hi1q7iHBxf2#k_ZS0NLE3jP0p!N1eDYQ0t%9IY%)k{B?<^lMsm)n zo7g~i^UB}3@6MT-bJx3fy?OJ_tTlhIs4A+8y=(7p`h1i8$gY@7RN&^BE}^t^{&UMt znEkT+lLtXU-w%5I41;#4p_J|531(}kkhSZy_ii7hJx1=)HutFG4#zi>cS)XceHMu> zJO{nL(4gvuyY!6Kb#9!JYKu>;1={8)$jIcP>3pNrGu7An7h!sGiOzDA&_@_eRpX&N zT%2EFr~K_$Me1)Pk@?qL66xJyrlB$M*U_%rxESrsUN&v<=X@+Qv<#k@nlq%|A@`nKv=4V`=72ISH6yK?%H4MTelp{`S+w;*RXUn|_V1*_VN9Z+ z4LjOzIq9T>!K$L4LZy@ZbTlG|E+t|S!J_9NCc6Tdw;A`lgtt$j&R=b!=jIH1eRrY? zY#EN9*e6sx7>Hqgt)k!gnyQmC>2$CB@Ek#JD+m6>jrP>|lBqOrC1*T55v1X!@H>V4i)ntPL zuCN8hr_3J=^H)n&J6t9#hqT>k6;ik^4zm2s8u;|$)ahoDaZaB0z3Gs~eWhg$4pOT= zXSLOsVZV_dnwKc-czv?}ec&L#a}=)6#DyDc@q!u$dhgI4f)@XO!xCNsnpie_Zt!5j z|G=OZMFFJlQVx*CfX@e%XXCp$&Va)BmuO`4oee!8`14E}X#OnD$O_0Kzwc9gPjFs4 z#JwR-;&;+kYJ_)-KKKpYl#Qs)i+#ChSEoxj>sM-h!FicQj`I5T+|ELMWSpt-JfV$u zvT?e-+Xp|@_E{^tZj*0snJ{rq)H7>8@}%n{>xAEMKL;^97<&>@Jv^e-r30d7PlqCN z9aaIyTl0ViuRB3ydcOYMd`$fr)fT)YAi>uVh_bmff$P!uew9Aw$}a{MpH3){-)ObE z-!2#Iw&LJn!*h)il{Y-vW^7XYDzo)*)*X>I2t5KHQ`Ke(->C-6n(@qrESZra8Tul1?NYOLD4>Xot z%#}RHT2kOQ$S=jnhYZ&S;+dqZSz+?=JH(4)w@2MQAO1|5uBnsTpQ#MC_q492cCKIZ z_`Z)DJkAXw+t2eVIc5Fv2d2$g~=^v#>f$Ei8 zVm99H$(tr0hT|bC?w$TNTz=srRLivtcgl(01uI+f=1boW=x7s#9w>a=Ml*4Iaa$t9 zI#oSaDu0(8O*-Wkw;sMXO{P*ErrX$1t3KGCXI(=I>#F4;r_5mN@beuNEAo?^8jTXP zML#n%U?RVANb8pRt(b{Nk;-g|>idtSF*&hciO2BgCRavAi1TYt3g|b1$q#OTcljgOp(4i8@y5q5#ZWD9}ap=CB~QCUtRdZ ztr>ZNhly;>!Rn@(6zi#1DLcAF`d4DFOF)1ViEFw+^n@^*%U4%*w#}j;(YB#``0NCu zXJ4ygzn0?**Sm`X_xk&a?e6TT4Q3|CZELi@eh$>CQUlb6KM_yaxk3Bjh(N}KGcANs zd6@rE`pM6%#K+EX^1$lFTOZpXPJ1WThsfd-17*H$Cbb`=jn@~*-ehAAH+pj>r{oiz zC$pb7ykpOJ&nlQ3a8h7f)yJpP#CYI*;pbtm1E9nln=@c!d&b?xoYFipKXI{rx) zy0>HVYTgUcPl@RjK|6hpR-aNhT;+&W7(d*2qcQUhe!-z=f%3|;=`Y4ci;6c|*_&VH zPn#)QJYf9fmD7Hyhcb|ru;L{PR7L^PA10PIJB52@f_4P_T+j^|7!HF z`3dz>>N~d%y7;ebnZP#(67>~+mt;F*98)ZSv<17|>^+*EQ@A?ikKx>;I6_3`7w zctA(a8$F)!C%Rt?o4)?%|qTQ>kRoV+5^$J&NyEQuJ4#T z>BAp$H(Mx2Kjn?4d=LOl@kO+z(N5b9bY^d;db~l$2z3d?;kiydR%ha+-Pd0~&6Cb6 z{dyKywFB+Sg+TJ%pFfShBD%3A)yjI1b16wg#V)9h_N!h&mC*I1M|Wk3?xFvamI`Mk z6ydMLa()F`(cF%H$Q9M?Eyp)PdtpDXjq;+zyT`)gOshjVFO5~0_L8CQbM75dVqJE| zZuUtz)v*{y#{64*-e|hRfRSQ`nn4rOhZU{2Ce~VSh3~<%=Tlb0m}hk{OO@stbeMQp zq`f##Z-1(mFsaPS+k|4oyg=@RafFmvM{>Se%XIdHxM`_G!EW3)g~LwvnI6-?ka;H2 z6elCQ8Az1Co{N_g&8=V5vF+uZj6Xra6Acu1AG`o!BWY)`xwma|*z9=!AUOXxJ1Y(7 zeWXCD$qU4X{UfsbC%C4|2vAeb-xCc6)<5)Qi8$W- zJx4AsIrr5%dyvS2gKIy_uB+1MdDpQT;~W{_U*<`SAGd50fHJ4*fAim~Mo3{1t#=QT zv+~!4RUN$EG;`dKCiS(j*G4OMLA#g?)<(6<+NyI~PZf-Rlsoi^N zG~%|K$g%}J&iJ^+c)YycGnPYQvNxK`!b$1tk}sm8;^mX?Han?#O##{qP>SC;pr)NR z>ix~{C$2D-Os8Gmt-zEzWQqsu+NQ$rN?BvLzLn>*C6DW?w5{YF=lTHzWk zkw4W6_S$f8dYI%;6jnt8bH3UqDL;}%TUfNLqC|31k+lq72(0EX_hDFfeJ?vaS0KfB z*z4A}r^)uDirMyP_u_P8{o@2-2CwRXW=q!4=~$sHX+!<*NGL|7(h^WVxR9-vPM=x14CTU+rRe_$!tq<$n z46TEH2xO?kl767A_bAr27ax3EJSCCXw+z+`-<8pQT3$jKA6(iJ-nPAB#OT3Umi~1S z$LM~z(gaz6zG3Fk)P7jW!R_g{q;%}kHwT5j2;XNL56SLR3`jrqGw>8^>PcX3+KF*y zpU^EXrp)Iu>}S7;WevQG!5eNAm*#bUd1iSWM7}tgbkgcVt|l929)XE77q1^a6^YF0 zpLvJ6(oOl=3%#xh5mK-5dydaP%B4)28l#hTfQHiSDdHxki!o&%QCe zR!%PiHbwSOnd~Y3obvdvA=?Ku&Y1&3LRaUY~<~URgBl(GxqsD*tD8 zW&g*Fja$CTa#KSi31lO;?gxY#GU@_*RjE>Q0q{&bGrqbIcQG6!{Qpoa;NROR|JH8# zFLq9IF#!Mqvi@sR?B7%Ge;@z%TK@MQ`0qXN-+SP{_rU+*9^eI1@iYIo(-;1uqoMxy z-j}_r>|zeBZ>$dO*^29>Ux9USbIN+PzcR?H3=Jp zTR>4+Sny+5pp#H-zjTU+e>#Mg^PPi)D)ebPNBe&sli)!n4}eL>vtJJzoWVOcZY$*My;NfR3wRpGh#z2AqY*Mf;OP)d0J zL-;IE1nye<7@onW$U(%s-R&A@(8V$-M!IC+^5PRTKG1}y^0jb5&rbF9tX(Vg!Vjro zpk|i2stMrW&kIAXaiNaa&p|B+++#fS9HdBh4nk=Y>AK)8b;Horz1cm{KS%Jtj+$y9 zCvarr(8jnEo&X>Fxe1*Y!JLB#B!)x+6sNx}V>EOK7uk}y_<~*npKU28jj-QK@l!}v z@U~2KkI5kk3eJU;;+*KGw$1S7 z=~qJ7s6YuWC4g4PWXl*m7R8F~E95|NcZC1zvtZKP@OL!)VbU_}yeJCvTnr<_apt@|@)R9#(tBA5Ep`sT&J6JeLXzue!nik-QcME$e#Z+qZK5*ozVfifi#b-7unr>clgW*`@cG^v# zcgpnf61)ZRcgOXCz(8!sj^wmgRVxO}#R!!P;FGgvJOZclcZY|#bD;gkmTy`g!iv@l z$=6?P=4s~JAyw+c%>9XsoZ`xEn*G5E;DVS?&we)Gl3*DGWnRlNdm7=#D*2VaZ2t*N z=7^{8*xb7+dF$-qBt^?&)bB<45z3}>H?{R|`VEUGrrLcR=zdW!^F9?$d*aZGf06gYrRdIRil}gM zG}l!Q%uHVD1vWxa1}07*Ca1cIm}fm6FwjvEyc|4iBi~dMd_(GiBlYCB&Bet>C5-x^ zi1INat!JUdB(oF#95e}^9ne+ub8uIytqV8x_&v9{hg3=uwD#T+HpxtUzpEJ<;vxO@ zzGe~Ht=)NszIj+sY}3TW2l`2PM`lv|Nt5DA-T_bXdpyt%0n}^k;K{kDc?$ zVdmJ0?G*v71-fqSwTtSJ_oV7$6xaCX00G$(m(Juw59R1 ze&Hp;;l|GL;hkCHh4o>8EAWK-z}wWlHb1IB-?9>|fF^40vy}5fI9s4ELPDGjWh-Lc z)Fv7X4|gOxrZYT1A0i)MBF(i!Ueg{5CE&ZDqm5hgbst2gI?n2@>4>joh93YCD8xJP z&Ze-lZF~WW{2Vm00e^;y(`)_*7Y)CnfcgQYo%QFSCBuD!)@k@%p}kS>7tF@ldN z;iD8WL%a5HwR6ysecC^~x^H+kd-(epx&l0}t?<`Y5edfuT;*1S#pxFGfX3(?bOXM4 z`~>(vcKBXff8bpo&5%I1zSFD4=b%s&_ADPhCQ7t!TqPd)0nf#l91^sZffo(P3PG+L zVU-iYTv!=2L_{O%d>lTKx|jfd!Id;B)Y3h>PJB7E?_0Up9WB-Y8CSe4Wb{dD$1a}V z>=wL-1$j0Hj{;-L-4Q2?9zd}fN^B`msfwz0tg53vVdL4CSG?5w6dMuJYx#awu;25g>g<4?-Yjl~b*j<#nOM_cHW|H}827y@7&w zaAh#&jRR`GgYpay89X?vZ5m2%h@GDE(ewJW8V@+?&q2m>c*TV#`yTVXk-%QIhF32o zU)(*G4?8#=FbKKkqU$w$AGZbbsZ$wKro<^!51oVfp99)OX1x=cx9>SfDRs|e_=Eo9 z1y6ZxlBAgdu?92OHdB+7V#x#!AC&{>qLIAVD5}!E5=QfKVF|{#)r3AMq2qg+SREm? z9Nzvi=g_VMAAP;eH|;3*n~QVavBDODFaW3&+2vUJf;^LZ)hV;_*J2;M)9czCHc*#j z$MyP!e4?+Vzpr`ZjrhV`JH^)S+Mg#~0ekf809(EO;>9)BFyZ~Z6ZSuJ})#@c@DZw3XI;4NVl=lH%1cSU3U(8ZWy(>12#w91?h-@$ZElZg-z0*N_J~( zf=j~M#hYRc%q^DeCU7Xz*gX=zBfi1AoIP6a5cY>LMyIdd-CwT>?^kV>{76$~bL(1v zyVv}B_w^aCZwrf%p`trm^F59&1Il#$E+IfX-9M32^slj8RlG%E+W|?zI@XB>+J z`-0)rPIz6Xf)sRBtczNmYbmZvYIqbPCDPA%}K=7AbSvFdbO=%huoS52sG8yD$5Xm>q$+f+5&pVuaR9Xn;x zD;qgT!tp@pr+n~4wLCMtvnGpp3gyCWFrW%mp9 zbJVHz*_&jY>JJPXXxutk=7fF*kzvF0C_bD`1MWB+$cMS4xX z1OD9v^p~%uRtu-B;I=f}5x~|0Q1Nc^U@ZR(yUCjy^%|@3eG4i=X;`Gz$N|ee7>7pL zl8U-n3BtY`U@?~-61M;bgBiPFDrczQM^+x%F`pIs6vBNDLh6wb>6Lf!I6xH*0AsTL z!DVhuH&MkMMUc_UXKlgnxQ+vgw)k@sVG9dQ(iP|xcaA6OmT zOSg8BRw7q&YsXycMUB<%zYVGc%INLc`LL$ePhmdWwWMi(A`9dA=2a;Rx`s?%tG9uL z%`I&SlAM67sU|G;AAQy?baQRinuS^nHvs&b?txotq*=O;-n2df!Fp5Ei1qGehx+C&?;i#%tN@9r z3TQ0Q(l-Iv0v`Ys!E=a6NWS!EWvgj`>ha|Cpd#g?YlNJD|7)pwlE>-RG2zrMZKrwFXC{VbkaniRgo zm1Y)2q$_265Np@Vid)xj>s8mYpV7ug4JgXm$nmk5yf59(O1(-Fqt*}~v;kpgFW;zG z=dZUG30`?~91ird+BFkNh>N59OW zt|yN(=rth-Y!ok|!cJQ^0>U<*%0lK7Mg;-~+;jL}K&51zIB}}ACBE#*rFLGmy3dRr z7x#lM=C{NU(fMqUw~=z$gdB@lxv%>AVc8g5U~;7ewirr<3uN={A-n3v$jvJ>G`Z&B zTIR30KsA46Yi3E^!#Q`G%fG1)F4)&l>M1@4P8Cr2&YkG1V8Qm86t=H*<*$E*)Z~JaE@%|$Ej+dj}?vQ+1 zl&7b$S!x=LuDbC`d7Epa)ei9}AYk)tD!ZNms-geL0kCN7{c8#wM7r=wnQYW1YK+S> zph5P@3O9sf{CX-~-I-x1KUbex#Ea8{NdhDVhj6H$=v!Bvdijn3R0xNPE}9eWAi*ZM}+=Q5he$=8Xa#4Bdi3zQ+w~#*^Om?_^j< zhhh8+zav2k)3VpH!95m5E=2A+?Th1tqPBsbt-Ov=x6w#Nz`%MWWKOXCF1$Pa!rs89 zL`Ru!EtTh# zFL$#VFp%ZKUt|xBMYQ*>zc|c;s6mG7ex|=9lzstRzYr{|!5blETo^_Ly^ z4L}-EX&b(P-e?{(AHTCIJ{sFwD{oAHNzPfsR4WC0(QjI1ba;I&(~j;cHEU#Q6Qp7U zy2RiCP-F55Gq#tEsB%Ms*vtv6SBUe1eV-UdCfejeXpTXMdM}_5IAuH42Y#xGI@TU+ z__CDMc1V;}1gt;L(h9-Q9xh=0C$b$ui-4`PRTPIM!$&fQ5xne={G4!y@8M;r?#5y< z#;2dY#Tf}Ej%n6O11&_jAvYWTaCWD)6- zfcXHQZWcl8{)XPRpat+Cm=YkBFj;4Y?a=*6K(L@}@O)Bb0A2L@2%+{*p^!Ri*>eY@f-U${cA7Bc`(Ek+e#sCa)8zW>mh#LU-X+ z&9mKgV7d)polmC_ERJtkI~nSB;9yMg*``bhgHG&(3a_ZYqsqpW!(+P2xfBjIZKHi2 z_r2|)xE{2CpUWDwr7lM|$3fQyNiN39BhD?bG2!30*upF{DEg3^TY!f{U2#K>Yz|uD zE?01O)4Yl!gO0BMq%i)DuA}Sp7xp_teK{FsCiy+vQ)xOsk)oK?0z%xv6)v~;IfUUa zTa@tc3r+7L-%>t+bT%3SWpB*B=(~_~{z43;W?`U%bwV!Au|+h$yO){EQkerh#hP!~ z7rEuDzF<0mb*w{YPbbml1Ar;4VF<4?jBrvqe3Sg&t@Roq0`zQO%H``4g;Qk0Eub&C&t_;`GB2_ zkF+G#A@{|maoR*>m<;h7+<0CV4v-KtAW-Ot)|Dj|Z7)ZK>bM_Xn zJuh;f`SONZ^TXf%qJSC=QzaY_RSLhxn<`ImSMw-c=*~{o_SgWF)eske6QGx8S5Wf+ zI1?p8gbSR506uzcYhT%uV4P^#bxPzp83%~3ZV0-%v%7Brm>+Z%O=YwBgIu`21$OOM-+ZCG0C?yx~|v^SM$D_+@E@+rnAxc z#`JUbl^jO)QbWGVPaDGxgJU+;b{{GqRa)${1Ru~jexbgS0kFop47-u_JFhHPCmX&k z{Us7gch^uq4Kuuz(yrP450Q|HnYV1Iax&bQ*C;PsXemII29Ma0meV6DdYPfzXMc`G+d}uiL-5T7=&D)oEW*7g zLMr3gj*>JTlj2l>LufyI={fM%73c~ij7qYv*whSda!DnJUrb-eWccV*1tEcsAr)hlD_Ih9OCQpS>XQ>IU3CnCRCDC_LBXN67HU2;wQ-avJ8% z=Ink8_zZs+u%s!?0kG~SAcp3R3D&jvv-_u^3`96`bH!J@$c4sE;!)*oc|G?RYYyFb zUSQ}dDZtR-8AY?eO91;zhmt^~%gFGSc~(9K6DZ~Q@bbf6o7&Y5$knq)@ti#L#U6Uk z{N>rRId#Mb*2R|O0aP$1_b;Wxv(c-AMiu&@bcTl_3luNKbg{M*e|>nlD{*%TkI=-R z@e$Ha1ZU&XZ6kRm;3-SZMdGu24xXj;@QVks-Lq|pMbzH_#RC`3c5TS^_Rf;UG%(Lf zu+!>Of2BnYn3VZMdxBr{J46H1@uB=ki_xWwUR(vOL6zd;rP+vSYb zpYNR^Vebd8W0@>pz!&Zr#maVxzH5w?P-f4=^G4T)zA5ss71*A<6%ClGH+*NJGAGtN zIcBC_A%xWe_tW%V}%06ajJ`Qp)y7$@XE8-KS)2@{#QehOYWRsylzT52j zO)YU(0##NTE;gChtEa&pD0r1|^hx>&a==~CUOK8K(fZg>_jQ!_X_}(Ba)|?jG%?ru z)ot}ja;22?)YO&aA_sBVqm&+j)rNuFT zylCmQYa~Fgt_|gB3*>F{IYB5=h~k+wiC%t^Ag%Eifl<+&{bvJOuC3a! zewXc^{Ic^iHr8}RA-^9rA29zMkX5oQZLPm5{)^O{6e~mjF!WdCz0GMwKn>uPd9XneuaWoR*RLG^3N4{DC*O4xvoX>VY_}~PcbJsg`FnO6_$t*vl2_*ujk(?Et`kX| zI&0lT__Mj-b(|qcR8zm+u2z*eF6QUyH+WboT_>h%IwmJ8Zl_17J)WQI-6+pAn&(`Q zi;g2;t0?;#D9Lf$8Z1?);czBPnXC|s2Wh$i4s}_ydOunycAvCaS8uAuJ!}jC{3vDn z6sks;2M)~HwIzWAOdu8#g+-ueg%Xi28-73sKf^$Ag*$tG*hiF>rn}eG8>&1zh1y#9 zgc2gU;4wb{&Ri8ie2Y8>1(M+g0M<1qczO**09+Vm+qBIEswZ$^RJ9NsjT79H6vP?C z!29*jLA&4oa(5U5>aU_9p#EOMJ2Yh?3U!DUzo7rCn~mE4&CS~K4TV$C{!(drp}$_nqQe=E%XtDHa;SEYiF}(r(n>MX67G7 zalSi59rhdl#p{(l*e0XTN zq3vb}N?)nW3X8gvyQ(6{qDM{GW_SUVE?krR)oVK&CEz*4q&yf|x{Mxnn`sQ4KtCC+ zrlD2mIu>wkZ}5xtHLN|#e^LE*I>+|5+JwO@CtuLB(=_AdlF*|HtyA_;8g@*7td3@L zE@hbP9jDB@sixY#s=}7sc`~ux91`}kI%`-OvTy5*$W z)+qS1ih;|uAFPKoQuzB~(2%F4COCs%V zIDF0T8-5v3i8iA!yJ7yS3t+~L8Fp>DnO5$U701s#oMhv-v`BZPSAyif6m}4M)k$|i zic3tO!X(lZ`MC7o>&FsKUSf?L5_PVwOI}N9s}?dip6^HI5?HMUn2(2xV@=;6Z)}KAp#! zpa6)n|4AlZ65ISObA02YcX76a4u3wB9b@6?rivdxO|pxmo1?1JB&%Lp6D@zf0*jv6gDy3{|Ngi z;lwa1yllyxGY8}$YSZ<)!ai*DOfy7?t@Qfz9n0wmX5-4Acc#O@Mq0AS!7 zH^5#4`Q^~Z%IyO>GG)Jai3}0=8iI#bTO%s^drF6Ru z3?C?x*{v!2tbR9paQviZ?8fqPdtc|f+errg&IFO6bbS7$aiI9^Uo~<6)$hVYjD}0z zR4aL^X~n~zh*y^)KAE#!u?d=eR|akQ7)#tGe6YeJsVD0Bsfj<`6MXGRSer zM{yJW9f%cEOt&eSociH)u@@%l_Hfoy?Xz%N2CJbI5_kL%Gg@nxV=t^#E1`yDQadia zPRGdpkWZ)E|$9`v~4*e0N>5Ejol%i zwm4GwitJ5QXH|PFf*#L5EDH?)ViXvHR#KU=?xgmK%OsBV5HUbN!YX$CG3D#ba#=y~ zN|*T_-s&}uFkSaw<(;W=!ZPC02;<~7Y4-_K81mrEA+)wGm0m5^08)$lJ-W1d;a`vEy2)_qtsT*S-{QaV=$7E%wxX6rXV}@USGAK?1iHfPd zD7Kfq=yR1w8|u5{brYS&ZM)QI7D>U2@PpBOie2D(F=DK61<6iG4LYM}hOQSwyEfCt zWF=q=XoPWejE8JlyWlT#en*xUy3&!Jxtwd1+t&q8@U?&{T%@PU@#xK>hlkv?ui+R3 zgFn-ttjxTbs$sBRbArjU)=bIAG#{nDqkVVf$VBiuld*5|>hwc<=ik|ApYE{{%S zwz?lVF29fVM`MfZ2D@BxjCvuCv1(Mm7n$xTirISnsSW?9?}xH8iVANh5e9oPbdU$0 zWU!(=QSz}%YYP6sg%R0Ze;P>Zoo9pTlV(Tz?{b7xnMI%*mASG(TTfG!nDlo8;v313P9H*9AznNN!Bsl zyB7E7$RS?HyiGuHAoC73@0Zmn)kt4M7WP6aetw{7Zl_R_GzRUU<|%MZKvb3ol1Kss zg0qy8*b)T+;;(Z5QKSqo8v!8puU7^>yt)fRPc80@0pRd;$bu^2iU@p;>+n0!4-P@3 z?;0ZwS~Ue`<`sK}8s0ub`R(9`F90zhEo@jgF?7=ce+5pIFcplfLej3A^Y&+9wF=nq zeH;B%jwUa6naKE>HUYFSBp%C2e8-EzQ2WmNTQ1!2v%ngJ37|6N<3+WKm_@achwG$T zbuX=-V&G90dr$CEfY&PP95j&Iw9t5IG22yh5k@U(VTF86$09k=p%mPHUyj&VyEuz~ zZ-6?YLI80Iv1OF4&~Q0C@GavZX+-YAS^RBWaN+k`!?M~iuPd+xnTYkJC(|NtQ+%K) zAm7^7*%O{5GJ=mPzFzbzQGw8KPQGxa-pCCR^O18LWIIa z3||6jj`Dp1J9t>&5KK?V>}F(E&mi{K!PZFCCVGN9OF$lJAo!A6^Es$27`|3jTC`w; zj|=c3HX-)uy72%Y5yNptY`Jj`${egmen{KXJe)l}g8q8)eYKec;3#tNQ(_z-uO1yt zoMe4ZrNEXp%&#=`%c>I6UaeLz29Aqb+`q(g<;p>68j&uo{Q?>wfD=6czgw`qSo9}ORXot|V_K92upTy8C1RKbz5rNR ztmp+&;9K*^P!c>M0DQmQuibxWo*<$>&@8on#5}S)>2YOQkDutEj=I0(|4Z2U3eY5L zaQH$WZlCz@2AtUV3UI+``XILervp2H&lB8(Pvm?J5zr-tv2)O7hXsLTEQyE)W7q$D zVl`-7KUslyT$?95-N5pQ7>~yr#=BIdE7o!#c2tg!4Bis!Sj4G5KYNu4mWWGYa-@(` ze|*8bp?KZc9BEZ z%Ba+A!1ESSX^x|ugN^EWcOqleIBD9{p86knI6k#$_C9qp|FQtt>=kSsJ3gXcckzHx z#ou;S2xyGytZ`U{hBtHf%j%aJ+cV!y{Ppay7X|k9lwx<9u8P`fzu_P=ZLimSiLhPt zYEmB$@TsZpUhM_B%-(3`L`Aa`XEsJ5CdtoBBUq@}DD}`8ovY>_EF|oL@KU(hBVGRn zCLSSI@4Z)NO>z{oXDdx-<|7&XRV>@O{;#V;vVRac$Sz@l{sThnenog)RK&t9OerFF z(v(QJC8D-<#qGF`Eivuq*sBGg!qa*)`JnPq#)?o(jS{UxLQMHkKc(*;{4KNWMD6=( zpcMG>gt(Qe)JaZUA~XnCrJ~=}O%N57n!sT`Y$>$;SN<3eRYr%fq9lHue9r*)W6?}O ztPe4NbJX%<$BbQ>+_dSh~&S-gW2gUO2dpHXWimgwb{5 z_BPu*1KIZdg)SuP?0vsm)DPdN>~5YqZG)+G<;kx5X$xwb!0z@Gdc%x zp#l0R1=Nd-YHwszwZ;3fRCe1&?U()QGxo77B-&{yjfjp?ZyO_A4s5=G5oj^DG(<%? zK4rpbWpylj*n3Mp3Mq-rYe|MPIcyeo6=~Wp&_%p_!04=w)&W9<=!)=<8nLoBak1?S zL)WBYdh59&PR&uQ|Hn{g-N#Ikg2#JT3RM(zwRXqBGc&@ zcu&|2J|_vVo1)>0R4`^#-7ZnPa<$xrIq*y8rfJxZvu%f`7|YpBL%pfSF|FsZ)?GqD zWbBi{fz@ji5q!eZp(>1gDvD(J9G}r_Kz#YTA?VsAFTemz{NqFU`%}m;5Q7aKK&?@| zI0prF5ouAsnI#Ap`JoslXv;^iNY7%AT_6M*Dc^p#mKs7xbDnKelqNU~K<6_=(Wjtk zz!3w8#%n+7%d0FV6koR&U!3eyTzcQp2m7%e>34v1C4L`(|Ie4V`8+42AGEj-g8w!c z$nPINNxXpYwyhg1#5eR{9C_Nj)<{XIpt~QC_#W)!BG(wBEF4Sj*!`eJm6K!@lWr63 z?+Gw>L@N{nCES2at^htAp0nI$b6oIz6kK7r>a$qdO^t#cmxiubbuKuB9NHM5ZNF=U zd$BlsFT$I+7L|8v3XLYpshOz|(QxeYA5Litj~kYT z7ZCV3O*dj4CE_vKyi zElZNQ{c!Ct^XpjoybQaEoUm~2sv-Qstff)*rDb?>kV_vg`RT>*yScZO@7>DD<&@Cv zcQ1MAXIBkjbWEM}`ozr=?_~+)og)kkdm``Q=4qWf=hqgL)rL(k>obA^H`9?;NOs&N z&^wg!%o~6fo~E|SX{!Nw2;6BvN*e%buW^)y=VZfwQfdQ~Ym_~+G zIYen;%UK~U!9BIF*q26AyjLpSB-+kFY^qsCYOP8`lkS5B^BCt}l&EQow$uI}Wbdj> zkT*-q7Xx5k>ioJ9lE0nhbtTA;!0rK84{~q}=OCeT&>64`CgL}kLmI_XS?X(Qqbx%q zs%itieD7{u+oybEu@626)xrq{k0#!1T`B=)T}UuNS6b{h%c-h z@rv!zOj#(>zzeU8LsFB!L{AZJ-2QAqwUl7-F7>KU<%*9zMF5Ec%P0eqJmWGqn^xB84eCgXP*~iKJqXErHmnVsn=yZW;SWnF23Ck zk_HLGFDS6;?!18|8}6YctiZC**7*q=tluGX(AYTJM zotBv}tXbw-B%_ zdxPV9*LJMJaE6lW>*h+>-Y8^b)%9_zNZu<_Y)4(OWOJ&k3hc+qE?9ppjMV^F+T>AB z+loXi|88tIbIoCYQ4)e<|vYit3Z@?{ABRugJMVJ_9Q0mpSw)Bk&eantZO@GB&Zh3aX&H6`dY@4%$F+;hGi+WQLe3Ulm0Dy{UaI1Wy9Rh;rA` zVyxDEDA0sTAbBB$at>&PH;-x2s?XMn`xV2N@Oyszjlq1GnfGd5_3aeHwoP0WaEI}) zSV{KRq$DJS6;}p!g8Jz-Kzp>=l;Z3^jOftiNS)_Y+R#ylo-&piJ6*?X;zGPgjOUWB z+fK^t#J`A=Q&R?&+?+)hgz9$l*U+H4Zpnn^M|KwCAa`zuSd0rV)NMS@+bmR zC<#T6bsOb8apv-T$snH&Ll2)Bx@6-2FZSLttd4bA7sY}Hg1eIt+}$BqaCe7b!CeQ0 z0Ko$UNN^HdgAUx?-Q6X~KnFT+Cv(|cYn^+}v+us^+0VW6N5bf@t-8MI>h8DRsvP6(A>*J4%v1gM1+hc z(mQSX+yf91bLr-U>14irpmf5L*cTPPyQ8zr!eN*!4bWKrEnyj+s@ z8l|~`H9E;!9JE?L;#hE-=i-Tzp^nFxL53yujet1=#rh!LDGGM6(XXQUr@Wz97RtWI zScgUoL4!8GmE1F*K9s28H%tZcn()Z6ljUF?Lq$V;S;H@M?xogCCFK$*_Sq8E_cjwn zgJJAC?{QASUBw&CJc_=G)^3BmoMdO;-ih}aAPVaf-CM)YH9c8hUynt!xpjT97@c9? zTNqdGIGDc<+;UcGEfCDENM%&!{?HMA$`AB>#vBURM~8uJnAw-y7tCJQ-O8apN%ckL zAGpxvylb?qd5_Grk1KI{oY!VWDH}YQ7NS}36(=e1Kn(gu{r&~BzUNzXx%i);YG-ND43z~c$i*y(@ zqZ|={?7# zXQFQVtYh^dU!*1S1~MJgyI02Dy72mVKfvoKC;7RLDfL-v2IGode*hX(;83;#sPL6f zlDm_Y^{Nu{vsBvFT}AsX?4ePO7`Yu}^iggSG`nSd$8CSl1boF?Wdkox_<#pqs1N}# zgnj|X+xti$@w>D&B=bz+LiBb6fZ0`$j8#U3JZjHIxgtqb={-I8P_rx&)U|{CUO`HC zqq)8AiPkN>H{%r(`qI3IFqJKgaxyT4ktp%FE|WJ_yWh0Lk?y5f-$N1;y}bHeKAD^BWv6^NJ^XB6)2oZWG4|(%$cEwK%jKAM- zM&KlrhM`Xf72G}FX=*i^atvVeT3MBx(4Ca1vLEGHVZT?Sa2=OveVgW5yq1XrodNiq z`V-a27lU%`?t%Q_v?F|qy=ie7gPBT<1yB$K{`IMF{pn-lSH)a|;(*(2l?spjWc72m z{is;6msm#&?jbE-^7ZEyq^ZH+C>dhdF^t5Xd&$$}5BJI}84LK;T-#u56!8q%>x3x( z_7OIp+gbG-Xqe2bIkt^YV(A-G(&H?IJ!o#Eyjy*kO^auvbhJB@{ zZ^5_qBr^M!2$MqZVp(~EB82O@xu$-`BxYl-+C@Ur5nUjVeRN&c3h7Q5AUhdNr_TgX~O{F)s3e4qdQW&q68U9$0Pl8S&P1}E9v`-k!kawlRg-qIPDh1 zxlv#5a<8v@WY;b?uUB%?Pi_*MV-Yq0283j`_NYA-)JSb3lp3CjsH_Tj9bbXkw93hChC znrga}1j1ZUF#BLeHVKP{3(@f;;Pr)iB1={X>V66iJqZ>gRue93{AQ4w_=BU<8K;Xb zq$>gi!76x!^U%l;`1E6Z%oDwj`wiwOaSoW%5Fq*%nd&=Ou#@8L*YQ1Gm5seej9(af zQPvaP2JszzXKYbf2jFP$!$1ina^N32BZ$tAmGzErP=ZGsV=z$Qb?^ts0tgjuhka=c zIJpXC2Va(dWnHr?6*yQ%#$-~8(9e^lN!fTL@uE{Z^}NcwdVdxFP&_by<@u&(`35LS zxPn`PWQiOpKe3^^D;2STrxlN*W}<1-uw9QpcoF={*&HZHlbYu z*R|DXv_i$iniNxhm)5fp@tV;V6vu()34Uzr37vE!Uf9G>TWrF}+#}G=_9Sw)@3Ibr z?XLI=ybSg98CfxFx7z8wr^P)gi#6{>sDrq!M7iW$+0+luo^&$@=5L^=_W+>N5z`T= zqK(5oirW^hgq~z(p=rjwcKs~NuE&K6lJExW6DX+LrJbe5{3!1?sm(u;xl0C4G&+?KU7bFp9v1L~B z2k7&O5ooP7*vBE5LNc_nsQWLAmL+yL2uk!1os5ZA!UHwc+6^T|+&r0K!iFTVEft+shc`sMF$(qz7 zqlp2&lXBVf9r%P;Wra(=1D~00T-a@q=TN>DTF9%0Pbo)J9xnGiNQhcm$G8Jiu%+ez8W<~CI#IMZO* zkhaxT#c*o`2`?2H&XqrH2o{jz^Nqm62?TI&wVsaZfgaEaM4=e1n?|F6{*BO}`rn{D z7pvV|v&r4BnI_f{)MssD9`9#;0rhM8e#YOKu_pX3r}7Q=EgQl!Ctq@(>4=x`QKxi) zOih@0r|&%KlMW1S%Cb0mM1a>3p^UAI+^I)GsEp2388_N8-B3e1`1Qty=90e?A?lBU zEDT8T1$a}&O`dlDo5Hmz<%Afq$miK63LBZ)CgCd#+(K}S)_9OXj> zXU61>_1*4VfFeZObSzDtLxc~zKfdOo+=Vd5PBcrt8vk+elz3MDyR^KW#X?Yw+*Z7D zJ#E6}k>2mK4PTIhm;|)@rpJ5@qeov|57e(p;IdGGn^2V#Lrc-K#|{12Xd{nG@j0&A z|JbC%|Bhrx^+uqm6rcl1d0Cqeum9uxtN(0O8UVEfI5hwCFDX6(#$e+A2W4FUI}ybH z%CXcR`zKp>2Pp2=R38%ZL8G5UuiJ`0X}HK#7@Qsd`<1Hw3#SgV!iZt#A>7Tn6fBI8Rf11QU-By2^#GfYdr%C+Je{cYKv7ab@J_iH1 zTx7nx4%W-s=y_2_@`)qy&%G>ex*b=J7Y@!a1X%G!wEu5>P5*h!f5VRcFQ5{C>hVuK z{%He$+Q6SS@E^8;eJM$2j>UGSvN;X4u8cNUx$2~EuaEl@_;$gitj4+CF<&;0P2(a_%u5W#6h?Mfy?)_pB+3xXzp9rN@<#igx zrK4d_CSLdKJ7%-mv`%-Hlt|pwC0Bg-0Hl<;9bW4)y7kepd%5)oo?{BEgG~D*U9g1E zPVykx0>%lm#`1_j zlnPvtTzS>ZYMu=w%Bny6`VY0(ano+ zd8N*Cvgc~_SC5G?K#|(AZ|{G%`XrrQ0ko%`5uX3t{52cj~A})I$ZSk zuZ=C6lSbbHaVh7BQ_$^0G0pl;*ajx%9=5($nA)9vr;-u-4p)ZWnseQmcYl_LAo0tu zL4Plrsm6|)lkqMPvy`X|2={iU#N-x|33h%wVQ`u>A-#(_MXE1oT_=YGKVBTH$;~>Z z%{?G8rXKPJZ0QTO{K1p7dp&+LlF7!;976auh7D9$ z_6W4L0Vp&Ora0En`+71?q56fn(WNX*i{EGATr`G52!uKP%|rU`tzTj9n=DL)ul1{F zC6iEiS)CpBky{IKOB!b__9z~~8PU#i44@-5hr;%QjGee$E&y;SQ)$;7@&8IQ> zx?oHuWWI$!wOLsFzE>W2ZeP?NoC|*|zajS74U+-~m5Lb{=mpW-Zr3~R-fAnaN6X)7 zi`Ay5aul2a3{EfS+sk{q#8hoaNEfywOB{*xS~2F!GvwauB2iewLCa!88v+J%8^qJn zDS=ChWYG|Fr5tljm%T5OdQrQf4)(kR{=`1QLnpDTXV2$AbK;;2{aHtQu8)2qq1K;s z;`U$fbyi?mP>j^Y{^J&Q6!!@LdFQ*#<9OMXHxlcqmC>}2_WF+hMEK~oxV{6CwA&CbP*{cYoy@_6KIXFY^K+gHLMio3C>}dm zZxpkiUD3hE{S~stV8zL%!1Gn!RTiD-$f+n8Ov1yNI#KL7ueNdX_s$1HQ&TP>t6Ok`++ z!l~iPw=%DZoqNWfC!ZxM>Y0$P8(jiUY$b&gI&YhNu7XU9x^lG+o+MaZ%aO*mZE26L zVmLOva&#}n>7*z11Wy#p`LZ(F6XPRnBGD!RMO-@P9|=JHTw_3>7>HPnE!2qywrDR3 zDPQ}Y@UWIG_20e;dI~`bd!&YnbYC7S6ByWrI|vPaFm^_59_gXQBfBNP+{HMz3UN|ztC+|q^y#Lt0BY#h#|_<@i_^!9%F4xd`vfX6C!&at-iLz$a1~q+oDImgKn{TbyK)gvQul>0(+F0R|Zmh{p=wIJv<) zXKAPcm4!2V5guV)AXvzG@M3KNRUB(x9HP@ zTXa2zl-BTL_i!uEg#@@_e4ecWCV!tG%^b;q`~ zN4zb7*xqE|C%UGLrE`E}-}8QyTKeXZDY_0|HvmZvs~WHc8PqdPl91AiXkL{RG71HX zebpYsS^Dd$OBaH2as2>>O2`bjv^g{!;Irn)U?(*R^)qJ38dyX*Pg1ClS>EG}ZPlJ` z&osMndKN1f`(k*|3q3OFJZegx^pG<`lA})UMb}9pfkwhzmBZfDp>GyE68ePCqtLHM zvk;X(Y(jQBSt}2by3Xx*p0dU09Lr-RBvgko2ReBpMO>wGJPND7u9~7Ej5)ylA}$OM zBeOZ|W!zPc0u7cuja|GfCp5J6u78Ceg4&yqkl8$CKG7W3)_O(>1wFQ(g7)BoNG^9p z7?_K_CVJ&v{61>xz#q6MZ!wM+j|i!!)Ad~Gr3KzFY4*c^=M&9!*?7cTxXlPPyqDAW z74*pnC$&MhBeliDeWFPQLss-U2A~DZ$j))FJ<)MNE@Uhu_XS8(1kjYl4iVD&=`w9^ z1twMaVA`;+yWlc^*m9oni_8x#!c~S2yIm(=b(btl*J{!89lZF|_^z4c?u%xRU|s+u z3S)W3Oj;u{=zgPV@8hu1mn<&k^<{Q|!#+hK8|C$Q+#Wj-lp(u93juXfJeyze%77l+ zJ|FK`B26|5{HV-N_Bt(A$-7(=g4k-azKUUJr;SdIOtX@WdE4QH3qKhzHtY>BW@ZAt zc-r;Jom&t^4G{buNlUe%8CAWf%F;4Rr2Ze}p|4ysdvUu|$BqK1{A8kQ@*%hl0E^4-I;|E+JaU=B8i0;@w(f`1R}*#br2H*!os-oV<54E{`)1fL0xF zV*#VFtUuV@9rdVjkK~qnF`sbD*E^IwS^7YmP&QT^bepm|3))Hp`jVg|{{|yPyww5_ zb^1jT9$Rw&x2N`z(C8&3GrYPrio-@vqtZ|slH+m3Bo@^NLC5)lvgA$1;P}i-enTqe zi%4w?SJK8Yf=zDq`)X^Lt@mV@L~8}Z+-;)OKfI3+WoCc$G<&MS>?fN<#Xdg=A|)v> zTaQFuEsjK98iqy)ckWH0C<9SQlyD?um6+mtcAp^HQ9?2sq9k$vwG>H#xhbl2c~>IG z|Jcc+D1(EaF6Lr&{`^r)Z}h>R?lD-S1+_Ah7+Np_nnW2Lo+USTeDE0KlsFNGhOITy z%`o39+-|94`=-iAcYW#|QmGIYfW)L@zHUA>?h*iha7$M+)VtNMr>@hKe8G`b{ucGZ zP<4X&#hDy3RtYvfT^5Ow-2ljH^ajpO^ztn879|VQyv(!5GumpsF@faS@hg*<`NLan zDTCc;d*Z`RZ4%t1sBlpj?*~SP>no)msFJZGhJD$GCbax+tMxet7;^h@4#5Cg*c*_* zLc{c1CQGbkBXRT)7kn9*IGq;6)|ivdIQ>F$%L?|odpV@DN9mG7a=gjQ)WrIR^H&k6 z(!Fhz765Vj15Y9An`nlDCWXW|GbQ6S(@c(Oy*AzmBZ#kiDQPkF&OyXpkvfwtEKu{; zRjo-RXxRjrsGD{>T1cWQjFRoOkOi+<)!Gkaanc9%!oj7kty|%*vbEp4 zZ}g}qGRcZwO&6P|%|cT*46URg;Lx`hM-uwJzRAV{Ts;&Vup=_Lo&gMy=im?_VfW%? zdiVFHG)P}aPm7@$`QH&{Kl_;h6v~iw%Hj*fB(E>&SZ67|cBCd^>>?-faAW-8Xv3EznnDV~HNL{#l&7zM z0rbo0TNFo%$ts`CxF^1a?^w~kr`8jV0}tHc3OY}cJn^>b?WZ~Sz}Osv&H2{G<0C)mH03BMFQQlwC^uP|(pr=msQI`5Gdr#0CDiwOKo&@{e%h`ix8 zu?3;{S(f%YxEte3dN8lLnQ6L$`9?&kfVxFwpskGZhk)+;l#gV_Azu>KuVM~6S&~tZ zCa)#xR^L5RP2Y>>+fE$JPAoTZe(jksXF%rPH;fGhZKd0YUZDbHRVmc-QQ#I6C*FPV zlK~QMQxGYz{VdC%o*POD_W8;}Nu!P=skD?ROO1g7o@<)XGVQI{Cnc*Ij)Yu#f$Egs zYne$UW9yLl=)g4Pt}}&Rhe(^dA+YT?lt4ahxzVgxEH6}kQ~v~S?^^4&5VOGMxZupb zJx-sG7GtdtiF{hUSA=V37otFpej3_6wMA4{?|5T|Jc5LP&Psbe1v_kZkG$W%?5 zN3UAwuTQq#IzpE`{Rxw?~%C5quhFjQl+Lu`HMwJQpJ~Q#T(#;U|?s z5MfM)6g~Bg(UI~Un~c&%gJYNxO8u!ruy@|dvZUwkh-H_p#9Dy?>BCF-;IR`SJBZfK z?bU3TV)lORnYRsIt*1p)%zK<34!K*5={<7v@h1JRhKBlf3lp|8Phi2Hc89b^DR(nI zA1FE}8>2TVuQN;g%eVPdVAT)mDzss9UVGo@6Rp*AYkp!wp=e-(sV6UQKh$!j$RN^}{*bNpEb}S8E32{9qS3Tm4k7NzuM|UnQJK z)&}+?E6ss#3}emhc!ax6QTPj7yMd_GSVK$^D0n?xzuSnQx@v;?{VdjUNl7$y{$NEA zzZs&7l-ptcQtXpxz8;Y&E}*XA8=2KJNYusQO6^{8Mfk2U@;z%YRJ#*go6`LKowq|% zw<>k(V2!j(PE6LU=%#U41`R-GubzG@I$nz4{G;VugD^*vmaKmHx?)Vba$EtEs(wa< z_2y|FONfO7^5R1_35)0ok$=_$P(UAOUDE`!f>~YHJVi%9Jbc30harK!YfTsL+Aq?o zkntD+1BrI*Fk%C!_9UaYP_-37sH1L*4pFG|1{%FVRCL=j@!P2Y+S8LRQSv>t{-Q*w* z2ZT6J?|NHEnp$S6r$bk6p2IRfPb6a5pgl$iU5jEha6jx7xsihL%B!%4NcMhzN_`>^ z9>2IUy$Xll^g0|Jl7lCMxlOr{r$C(3@gllsMB|;$SYG#*CHay@T7TU?A{4 zR~ui18A2E{@yG=RYdx6@M4E-!dJxdI$4GX>h~fI4|Km~h{q08?y2D}P!_gY*+Zm0$ z0-h7k9XGA6fg;sPIX9V5>s!n&*3gCMqq6Y4#e-Y>!BA;hlg)4DP7HHvR|QRmYnbF- zC3?0C?<9G$Ml|Ln8E8G?oP*+*;$72l#$0@Tr|~L=@YaFpGTnG8-vdt8BbDV^R-tNi zqcQm-YZ9m+O=7wDq}mSGd|bmM;9Zl)Nk;(ZDQ|RnO)IKy zcfs5$Cuc{Or6kYMdu&XkY@|HlZeM#`*bQ=q)BG+9GV4{_Y5Od25)r|V6a#FAn1G9z z?uMX1MA!cP)#)Z?bCECW!qh$cJO+!p-MEDxo!Z4{DA&&Qxm4i%?HCQlnJ))%5N@=P z*!z$e{EPKI`c4fnydJ7kQi=a$m?Ct%stgGW)9AFU-I=5KWWjbH-gnQ?ksn=SAj2qQ ziN-tBX`4C8d|%wO@eYwO-86pk%i`2w691q!@@jKyj+xiDPLBYl(}$h}??gH2tvG1~ z2LZNwUms^9^IZLV)l)L?m15o(yHcAz<@r?)4-B9Phl%h9k?A81ZPgC1hw^t#nwzm$ zdIu{4&lcwN$j0`^*#zrHFi_g~h-edf_>T4-X~&qG6xR=j$@dat*4aqWKbg3ch=k!) z0R|fyW(SdMW3P?1%+)`*^)fRtTPw1-5)4vZMT$x|(G)T~z3vTQ2z_d-`fRjFDC9{L z$5$CUf~hoYRUcwK>zQ6ni8TOOEQhkW0UU+k^UY-Cx-t}DB%=KaZ zVb2`hWzBIy!tfwl3@~_8{DA!UigiU+^(3mp02=IG*K)ho@?Y z+7)(&UgmJVI0dpoVp4@OYHaMXtT%Y<>S_3sEa9JvF?GSmG@<)k6tr4Ri_Qsjns4*I zhCR9l-EI^ef}Z)wZAOb2;&hS|HHRHZ4y5cD+!y#ntugH8$pOWq2|@_kSMb4;=L>MtcyZdew*7^Q7J4Wu0G!gB7g?7T7tK+`q1m4k@DtJnww-Yv*i9hP+u6EJ z^Uae^7r(B)+45!vKHs|29hPj=_mg~doM=+j9LTh@Enz<_$BNHR7Jxd+;SY_T&DIoh zlQ^=>SfV)6r9>GZ@zK)vZ6ek9bmL{mZLeZAQI$f2dql{lxE7^S7KT5K^k^E^eI$Rs zbtk;*c2^}~+? zrsCgnFx+SHDCq<(X?p^ zC3+>;${buKuggEeQgkT#SmMI&h%lVS)D+(90vS3swv{$BkA zE_Gt$hNqn!#H*n|+b*2rW6w>8E}C(r4sv zpTE0#k8Xj}%h-13|MxY)5b!y$-(>m$>fF(!9j<3xw)P>QAX-UIwH zKao?G@_es%fYlg1kH;x8LWD#9KrP&bPaDJ81Ju`P_HoBW{!hFo{dtc6a0u>Cy2QU0 z*!xo_f9m8<8~D=({bz+EceBZ_Wnzb&U~t%^B->)}V#c1aX=5=Z(d zj#LVx`?Y=%6JOufh^B7*sDvAl=P4RyHX;nZ;daI?FO5 z!!2foH{X`B15M}FVi*nu3RQ*>H-i$UZdLGU|3>-vYJ-Iu=B{>B<09677~}9I@br3pT^+fbdltP> zg~ni=zEP}T#qXcgOSxX}xnmN}K~rXXlE$o)3h+1P3`{vk#CIKqtUPHbUY{!yLWAXN zAS;FVx$8SI*2jc0pnL=2njX<6pJEC>)DK>n3Ql0-)8qqG^+2$mg1S+`h3@(4RGh32 z`I4W$$yrBjg~5^xQzT*C9QAvPO{y&hlJZweif?T7sAlfjK8_X$*QVoMs~J&O7z<1G zhrB_ea((AKg?jdsrmI15G@^Q{Vw|N$ezqZ8>Z-zR78}OE;=?Q|@>Wb;x$mUN$O5ON zQ~&lLcL$z}h>3+?YyhVixp?lp4cZ2gbKXPc4DwO@4n@_oiZ0Kmb4iAqOauN-A}2nV zXr1e;h)_eW#rYI69*I+0`xY7h?QkhnH1hm>ah>9IetRK(;gz%*PD!EfA}r<4&0(UR z^u~+mVh)}q(b(K-dw`z_K6??Bco<4*0Scl>hw<7;bj@1yg0!@+0~xO`I-X}L>kGnF z%GcT3&~o6th3|+-SEj?;*L#e1EnDaqJ4i`?*AumaCik9|^BGUE2#S%6qj9cy*8VKd zLg`~g$%<}&fl*~AwtmIgMu_}d@eFxvolL*YT$zo`r=lM$G!wcSV$y93D@u4^CI2!-4(vrt)jaiI6DM#KrdKz7uHMyLf zBs}=mQV?R)gAHU}CR;3mK`j=Yle@c0nD)2m zv#vRMx_b=%webJ&_aOsX zGiXJ7ULA3&G}cPeaCfPWw7eQ|!Px3SGBEYH0Ai%?l@&l(FG`sp%f?J2=iF`DFF25O zra-8LXtCL&bX>5>8_UW%kzFkmlSkF?!VR{_V@v%W!NaCF$FSa}HS%P=cP$%@e3y*; zHH^^$j3IW77TPIP*B^O25-z)4u3IRup_RpD4FH|Cl)@Tjp#u{8#2C z9lK?Gn<7)8&M7R7**3R+@p}snsZUCrt7ERtd%oFT(r(gG0Mrgeyy|9xySM$+N^er= zYMyT@v+X+b^~3PB_i*;(&6lNH5>hc8jo~qP^dO2KV3IL7V>1*Xh!wHHkizZ(h)!ws zGBL9);|ErE!m{C|G4FCXymQkhTW0KIQERYLG2!cxhwd#kQ;xaeE`0?;O`l!~MWM%| zUaoC9#~H=UK=@1`^yqT@@M%#Lld-uRXtK;^F@%&cd>bUHSYoKcFh5?RW%vX5aWk4` z#F@X@)%4J#wJwfNAGHe!A(C|;3{c{6S({tl5#u1>MLfY2%hzcK)a<~nA&~<}&8I+* zcdoOor;z1OS_fH=xQzrJN8k7lBj9$wELgV?K@bx37Y@}M3oLh-LsSE$6yipQdkYz4 zG`X#GrtbMc`EyOlVEZSjB)a4zZ{zMN@9K$E?-B;ir##Cg&Jyk-XN85&?{5lBD{E`t zi#s~vI>RY-iyG3CZFMS=P}y8jm(J?hCjXQGw-Z ztTN8L-w)+26H!=Wee`O(%X049Jw1A_dh%wjF_k2SpKo7otGC{GKyg4$z1jk2Dz+3K zEyMJAa&Y(8Rmp;7S`+5-e)}d$!A@#a9l9)vTX-GDd_BMmh9Q+Y;A$!G-R? z=AqWN%1di3>rF2OJz_uM#FoE~3n0_&iuis&AYZ}Lqr=EXv`ZTWMk37+xGd|Ej*Oy? za@))m39Rqq?R=pBkyjI!Lr)_sj#|`5noXs|cbD!jCwyopGtTU$lP7OU^d&LJKg)p% zPY~3YS;$>1#cuE?!1Uku8ux#v%irdoqFOpwxVpJmn%e*R>1byA6qTKml8y4$FCigT z4R0q)RuxmLpZ~d7I=E4C04vp4)ht~d-CfKrU4c!cyxe4fPHb+Lz;79KUP|7dTZo9T zN;^8Z0YlPUl#7Y`-J?+23RrR4av1FJL#CEs86RB>@MSGRQ2XH|J6 z&8lJP<;E&!4~QV~`ya{Q|Hv6q0w)o7aBy^U)u-g(|GBRrC99&Pg^j6%qnAFg%tp!0 z&&9&eL&?VlaFDowwUU5Lz*V?XvUC01o>j)h(cS5F4E|d$UTa zv%a$Quraq(laT<B1`g7EsSC zOLIpHOI8I-2P-#gN-j=5&Y!#eRr_8*Uq}7O7rgQQj(fkI3Tv_Qm41}qwMO?Qn~5?N zx)S-~&(;~0+4IB8H2LA0BwIs^{wz{MYk17fo&WqLZFhL8#0~U{HMZQi^}+PVRv))L=>b4(n73UeY}}n1^G7JKU_OLZsxO^X@JP5=eb_3#@r4LZptGqp){Ss zkVd_aaz=)wz%u=CCp17>bkDXc?IF#-GI-HsvwFA9IAYpaH%#CNvqkj+f|%7^oS#k- ze_n%=(*pz+T%OP+!L)3_TTtuv1}2SGA=Y?!Yogb~V9eay%_@ev{VV3!k+v z<`a*?YsBlAM%H+Epcs2>_57xb&_D;ro1;HP_IiOB$iZ<+HR7bTOF8vetC`1;E)NSe__;|%M#U&yF(jOyyripMSF$+%Woo2W0ns|C94O}3g_1GR&R-B~t4b}G)89OPU_e?M|f<9CB!g^ehhG{oaLVN6UViIt7&e&yVL65=ooR^Jf`A0PTliCgT-k@qDSERJ8eB|S*l2F++J5533j#4kqoR7-Cz(C*wA)Q^BQdj`rS*0w zs+%e`PmQ{84S5fzzZcITF0wF5rSpD?ntkblfUQ-B^BUtjABK+h`zL9b=KYAhI@P#5 z1opfZSH!Ux6f3APEsf$vo8#-9-EdoF-Pa6uMgHF@%%iod5(n%T7m#^dawoo*1cAe6 zD_LkmmSEq-TC&@C&)PE=r!~G9D7ILH_h)Z1Y@jOArni)!YMrBw3F^Dv5{BgQ>r_i& zeb-?|<$aTU8$wbpkPif=Bw`)h$&)(KE-0sMVL3akOd6f0c#~KZRM4Ju+oO^uO9!7f zcvNsjXtvU~4Xp6d3f6qWGcf4k!k#vpB}7QnB(L9ietLrLp<;tKVWM1Y)x22J`B=j3 zz%FC>wtXQh)&;Gx!9j&gBPou~Ln-T6mEz08<3Qmy%*K!0CMI!o(>$@HZu~X%}Sy37R;Gc;3wKACpUca zdIjZkE{UhbWt&wjl78B?3a8xys4JM6Fc)=MYJeElmTDVet1-8_q{p_+QiNX+wPoGG zBc%+Uxa_N3_8hCF2(gaI(=Yt5M3bo*OXl+`NKm(n$D${g+0mhexT;sBu?@C7`k2Y%Xe;?z~6J!8kNp9BDhR*M4db@K5-9BADGcN zZ6=#Uknh%$uvpe;s7@^k$$W7R{y;*P-0Py@&K9OB?eJ!cHO1p^g}Z7P z{Ni}lV5=|Er8(vdi>zB`NwqY%wg@l1P5K@Q7kZhI=Be^)b-(C<9TSIEz=vLJ@uap?v9f3v(|L~1WguA{ZMJ`S!xKTF!t55J$ zUe-x=(8At#{R{jW@)(3NGX>rc#CS!9vroF$`m!z-Uy0fKuBE`+ELG)vbYY=$HtbV! zS8?E+mF9ddx5nnMX=sCf(G%KCwO%6X|7NfBX%O7n%Qz+rtNW=F(tUSsKZWwk48(*v zj>A;+Z|pCiBsw(?6`2Sx4?d)_6VAiM??%_?49xEsx+70{g!xdOHeD_|kWR<`NHXRi z-APpLxw9^!K(P|2Z2{%(bzL3>lrM_w&In%4kdS-up0QtQ-H{fN#L0T=J51{zxz8>4 z^;YZKyUi^lU%%b%AzN<1t{GoHg@KD!2G?Q-b7(tZTv>n9zw+@u&U6i4xlvKH5S+3P zi(9$-p4lo(i}_fa{MylJPFWct#qi~xMat3>Ia|k0QgRM$p_vA&y` zh;xyrOL_g8K?$fr^{52hKVI1Ro#l%#goF>}ZD;(!U$5^~&!@5uVquwL7=%#T1T!Mdeih4q73dbWzkGCo=;f~~4YY7%S0iMnvFxr0e5cBLrh=h(OuD&TL7*zZG}nF;4q-s6=PuZwq#W^I+m=rwCc z7KaBI23(ow5u0k@D05r`gAAXk+Bu$3?zTdVyf|mhh-Qw*J8#JiKjXeVGf87gwl#B+ z=}_m_sM63e+1D2Cl+Ul&)lc(qf3v+DG53u5 zk-oCS=lJ-Nv|(*$Hj=5lC@dhdMl|+tczZo6O!Fny^CUrbk42C1^9U-IF0`$kY{{9j z(q|mb=f3StUX~~61mzm8-Gz9qa;bRrw-p3L`DQIIWm#3dmx5h67)6HP7T9N)X$N=7 zdJ2cUdX+EX!G4y>h2BM>Fz|z( zuHD6Rq{iXu2md@no0+v0%Y}8}FCLY$i;jq(Q&GNM+4DN!$yL_v*6e~G?QZ)8%8OE8l$C6o?5;kmMkagLSsC*)*YLl zr!6ijyY$<5lOBpfIG^#Upx%#q{z_z5|EGir6iqs`&clftHRUqTTy|0D*}R@qd~~d) zj(A#)nc-dK8CHC_#b9(E&Ulhpk$%N~Z&6)~rOy$WkDuAg( zMaH&v249DZwMv=}2kHi9ho~%V71MdIVi#G7yn1s1_ZVumWJ@`AbdYVzcc7iO?3cKZdL6O$ArDxm3Ox{$DbK1C<0|-v8^ET#A zFL-BKOOOtlQDnb}&Qi90@YwdzCQvxy3F(Xb`e`WIb5=#X8OwCs^{{9vH2_Rh;7MIF zI;yr@ja@)M{nm|C5G>ZK%>iA+_gsYua$4P06gQ}di>7XA9VY4U&*ar6{hHQUh+o=f zYZL~*^Le{y#t3rl1}Xs4LJ9BR%hiWBH_jp)JV!Y;kYIdLtta-|B+D3PDTHqF`#JPnk?7M)E+j*lD>U>olrpVyy^0}o;Qw90R^mp z2&~&K#xx_&kNhY+JIIwL`A(;1nSukoe3koc%h2Li@;)$W`NZOrG24P+uMWzMA2%n6qJeHJl_UKH62gb zO#L?G`h|wEAk6$WSues@@^SB`mg7_7ZQfj}Kb0Vrb`OwKzCXHt+xh(IRM@@y#q6r+ z;j6e|FAC=sNXv1ROx%%Q3ZtHkbFi!EF~QQP+pTI^08T$_*!v^47qJ*`e=~Q^G8S71 zW`&hLP~)6XV@l`fZ@MPx9v|P^uRX=Ds^!%5t=}>W@{dOLqyBUfo950x_aVl;-r4-C zgl7TdH+ViS0{wrFRQ<%2{tC7HLaKgZPOR$gW^O-WtDhrmtP-ZKmcNF@B_v--Nips> z#%(nxuu3@qXpD`66|0;D00`N*c{9rbK#!|A0En47xc%G#z;$?jfkYaPnhrKUu^UQG zcCKGTf53Zwj{FirpOXC-lx0Zy4`|TOk-v)gzYyiG@=D6R`gz`;Vsrcsy#10J7}ju< zk$a_R>hw2ZSzl>Wva_+Vu>vdAyg=hY|Gp&` zuu|tQdvS2H0T`U=&$$OsKz0Div$WR&cK@}##4qHKnVsX;xhVO6Vu#%P0C4zoCIB$c zlH;cmf6ekgUivRs^ItCg?}q5&Zuv_gzph@I`+sYb+BOd24z4zTv(cYt|GOe7IoJVB ztFcPkxVXAWTARB3RGxzAUw#8u@=FPTO4+%7&HNuE{kiMENc#6<|9^Rsej?8Qa_N7c zq<`xzw#y>_PrU`lPuyIM^&h3^cKHH{wm@>dyBt_^55qz zIQ|05|C4e3?(}|o3+`VS{U5~tN00me$Xjsy&29b_e*dG9|BE61T(v5(0NlS_}2-01Gba$78q_mRKEg&TwN_T&ojRE(_x#zw^?;Yd& z!y&NuZ?Co2GiyC_t~Hr~5`1l{SG~iJss4?*`2Pr{tM2T((n%PZ8UsAy6YIwWOw8;w zY|I4o^h`7?Oa#mfbTo{B-*aW? zm#?v2KF$hM5CS%!q7txOzJ=}bHMYyg*)B(5zkCb(35OXJBYiBcv+nfK7)P-`P+@gWIv@M9;8^b&6?m zx<8(WhsWvdsPM|dc45z>L8x-*xxkWgh2p~#+Vd1Mp2~$UE{REkRAMZt3TKO{ z%;jch2OOs(E_A2cNp()nM_YuDRiVqBtmlT@=evi?E4NsOgQ7B_i&M#|D!Rnm3T#L_ zS30T}ocJEm*r%>m7i=0m9sSfoF+jQ6{CN6S@TSXdD{arC`m<$)>4AEevsDE_lh}4m z_2@yu(Xxs7P^?*B{VM&wNH3h*@b+&0ctVzdl1b(hkmECDD z`~`tKKcHMZyQFHOwBMuFY@Xaf>DkhO><89>2<_6rK#Vq9_CqB}iW7KDqw>%ANW~)e z(AyN>5($Lz(g*fZX9y6QxPQtt&M9~-$}jUJWJoAg-W?{V%Afh{Kg?6-npRR7wl|y#MnpDeK2QVk(cj%Le?^rqa|pb{&Sl*1kmqGm9)3$0P3a^?J)C zmBkl7m|R#vrM?PYCRuJMhsE#|2T{Yr6J5OJ6@kCJtqxy9zd{v`jJ2V(+9y>z43j5> zqtBeo=;Vi~M|CF6$z`eCMPq$<<)t{q2#xRwLgIu~Bv8a!iDU>?#H@vfNICKRQq_?C zbSpheh+#aE$AnBEm#|U1R{1_X|1@DL7!}rA0BMp|%>0-@W{COSk(wACFDex7nQfp8_@#5=UO(g;R!2wHlE7uSg@rC8M^jNsapb?6K`mNVJ5rQcz zQsjopwN!qu;QU`YkoJ+1hIU~bL<%ZQfGu`X`SB~L6NVHxvJ?_iW+o7aw7n{UC6;Zk z;6fGjwj;0~a76ZC^H60BZB3z*^Smd=PNYGP*2G)kpA!RHCO zfM&}qoY6EM`Qg@~#^npzHI{+dtD|{Rireone2~O?a9(MWJtL8C(!vtAeu9{vR0w614lkbR<`hdU&|kMB30ZwSU?n!_ zmXH+z0Qc=mx^&s@0~Dc3LX_QklfVl5>IO3yXDIv znVM_tx?2g5nF(kwyvTL&R3eX=l|(VLR>535YU6t{3Wao$#rh~-x$1`DE35 zK1bwxiKW~}#|$7h8=@4*iGJ}ZJKWY3J)2lP4=zspc6o{-C3vp0>tIUGosbzBigpZH zXyuAxd6=|!)ew~xij+cW8$z?pTcgC&fu1sHQ)W+5pVN|{IeY0O#^c^mnB_s9ukUD~YwAPVsyn^CYV7p_24k}tZgGN?#4q%u7liV2bl-FoNlrgQ7+2jU^(aF(e z1+&opicVB_UEFnCEp#zph$x6MLOp$WHhd!;7B>S*yyUjm=)(-q04P0(G8iOwer_kL zbXEjz*AyzZU6r>~$=w5x2*Rz*H(i4#BiRyJi=^b?^Qv4_4&GmO19U+nw)Be#0g{lF z6K?B^p3M}yjUFe?)w713TZ{qckT}KIjge?efXC=4EVJ>3oFj%4nt&EzdI(o+?y>i3>@JT_r(au0lIp9Pe`_%ix?sR-y=w?&7lcKUUq=# zr5K6WgGGbvcJ?D)l%7Lu1K6B!p3yvs3_6$^<9FJcMuJ|*8^kgw8!`9I@I<=0ciuX1 ze5`j+8|5F7FGivbpXk8*iuc&h)=>T#WsDytz;38kA(e=Sj? zvtTp>TCE&wr>ojJ`8FcdfDBrJ-RHh&TS-{cDF4h5-a#1(ZGiiFEal=Ne+^y3K97?p zeJ@TA>}G?0)=Ok!bbDB}g+NX4nV~YIef34aN;(v=KO>n;k%=+mG;`-yS+iFL*s8{Q z#Tl>SVnck$AR^DC5a(vcOS(f`*grgIz+_o+&z1eUEBc(hMa4^l4$8t8Oje9;q#kr29^B!kK zmJ7?;EktK@%kmu>9+46b$^`8y?vWC<%HdSFPaYT|zL^(j%(^c| zraksR3qG$3qMY%*{KQMha{8~m$r75%xTv|7CfYW&4K`k0xgbJ_u?_w&vc%+Vq({UF zr^Ne7WrXJRN_$kO?r$G%OpX2R8 zJgZ|yuxH~OT3`kIP2P>paIsrWpl3}l{AG9J{3n)?FfNc zauO_(-zU?At_A(G6IDK$H9(tQvbjW##C5ewEwM3C7Fdi1-ikGmChwf6%>9b9)~!IC z=TG*$bvhLFmcW9xJK+Y6mPohwX9j%M)S&0=ca7kpvr9)KOnP4yFU zw`u~KavAHwEWc(Ukpv^=^DlM84F1%q>NxBZyUp(v8Yvy3Ee41Vsin6ZWLQAop^oO} zFT4!)*yS4R)1pb`d2A7C9+R>LN6${p=s65(CeVVdW2veSrLg~zY8H+@HWC{F-v3K< zi=MZj1bLSe&9Dvp{u|s}N~POP9*}|t!5ag$ri0wk|vSP`s&rxYKg z>2W&kItpX42h8^{mZz|W2s#h|CWrc|XzGxyZfmgzi6 z#DKvij?8_*6}VOsi0V%Ud%Azv!NW7&qWyTRb$HrrXS0iEOUZDNfZK~F)?#L@^yyhO z{LvEzQ?)aDhr6;LHf_A6%4Tek1ro}V&ea}HE##(dH)%AVFdj*L4UP8lw`@bEYo}PvY z!0766B@&fQPJ>X{`$?^N?oqt$_%?@N$eitEf zof*6MX5ZKO7h=Z31Q0V8b~+kB;rKV4fuR3GoEZT#|5uz@uF2U2H}r4EXJMxS6rx{_ ze>s^O$G=>vZ*sV7}s z0Hp<(-B#DIX_h~cH{`Gz4Q8^A&RD~9aX zFub7RzmNal!tkmQ16ae$;)y>5-ETEq*ZdD4eEEY*cU9=GsKSqk;=d^V-)bR#lL@=# zLxCx$@8Wl(_|q}du>3G#KwY>|{4e4DF8&vZ;eQo>`fC+6z+$8C6Zo&l|DTcbZ)g2} z6Gqp~{M9(?w=dDZm-9az+k7i3`AtiA-Man{Lc|}j%^wo9O9%6NR*UJnE?$iUZ!!kV zY)mxFKYSt3GTdYgE^BMPZyXtawQ;1scCHjyOZ3N#!LKC!4`Fna4S$y}xVCo$mL&dL9burS0T8C&bOfj{H|Yq=HTvt9c@Fw( zCXMBS{r^6J|B^}jDgS>6uixssuk9m&>-)!+>xZqs9xvVyQU0(fe`pnds~^3#r36;s zeqY63cm}46aV#bmOsiEUWM@r*L8zU`{6MD z5RAXm!82dm>jEpN{{}{2@{EamKeSwzr4PSpxvp(PfM-hnt*Fy8(*TaQd=qt`+}|YXtd~>$ zTT#E}Zdv~*-*tmq`_p*+&gg{sT1X6B-|u++cBB69mg~=xf|qBbewS6h7FPk!O?_Xw zn*uvVz-5bobm=!y2g?18qJAwC{7b9Oa4jTZ{iD>@4|jWGe10|D`@`}1<)Pu<1>?22 z3V81AZ(sxlc8siyfXgz!!FY-3KZEg?fgQuO=m2=)?%z&;nVtr4;OF`W>VHmWTTe)ZMY^r~iDp=ho+30AU2fS?K-@?lPxY_@^FJ=H@eG|Ow*CI`Zi(mf&@3k-r z_`vrG{FlP09~S2y+LJ#}Nq%{8%5T~{uw097fETBHU(sKLSC{^mj`3$DIRg;i8zH@n z1pkgIW4IRIUFh_m3$Lzd?2kC=DwXg*#Pqj%;A8-=^J2`xo z>#q4Ke|Mvfu+aZ3!@g9Q8+GJb==W%R1IztKK^_Fl!ohqC_;d;=-g7vjRmpVt=dJaxb z^{FY%y5sqktfA)R!_}A2VKKX!7q8qqWj@0+$WCdvhnLLoTrWMXUT}VHWo>0SEa`k_r)_H1 zC2yi*b;VjAnx$fYzXE?zN!!K2fkzb_E5tawFhIkNfWh~3#OnafgnEGnC8Fnd6Z;B^ z>&^pw5#Np55mAldI9ph9aP}+kBYNjnreNgKd9E1vd}=>_-`kgJ9iWj=M~G2B?yO$e zw5&;MHd!cDV47I(wEbFdrclHp4EdaQ4o#lnbv!H+G(WM@qV3yUMUH&M^QY+>qgJ*I z!A)qEShFP*j@)R>9IeL+agzDHs$;l?0S$||`0m|* zaWFpGx9GEIGj9#Um%cfypX_HY{XEt}MD+YHYX}};wX3O=ar3R2lSooz1THGuv>R8! zs+Ofu{KOXGkWdkNug9kuo&6|{JCL!FcrGMo{XyApGi=YAt%41tIoopU3$AwF|@3sXk|$0T00-UJm)81DC;!Io%YPBgVvnuf?=Vq4~wEEWf! zKal{Lm<6$}5CtyjbWkcBe;A*ZE_9QH9t7hv==))$8^thB{g|N5Oo$|l)L-67pIqT0 zk^x~PRFFDos&wV;Zk}yNvu;;XRbd~3ju?*EBAVz6j7GiVrKoP;xw7GimW$>sm2Dp_ zoo%T5p&c#T6b0dxrAc2ca*g!z>?goAEJ(_v^8I6m=p9}#PjhfmK7GCY>^*N6Y(xxl zu4O|je+KcI_r?hSkdaeHn!%V6C1ON*BgwjOCv;$l;k1eHQq>}kWW>8(Xa(FH#MoRw z=RV5eu7;5`Esvp67xtX3)@E!83kWmS+)}tXOibGDzFQ~`^Sz8a@w{HxZj@Os?n~cE zvJSfcs95eKbvJde@1_6r%N(?|okYw*xL$>gE)d7QR!|N320Vylb=FyA$f#AiQmfI< z4@knHyv(peL`H@|)jf;kgn7wTJ#Cb4+1;x`Ajj5Gc7nN)=pOYf z*&WF7F?9C`3Po$MPXYdN5&Cp1MW6g1^qLYU7k^|woCWRI5rV}Qu}77hs{z>r@f$b- z367_VP&rYtyBmdgi!jRPNv7Kq%s2czRZqk9Nvlv22USRGRvT)ZIR*lTe~1t zbp0Op)kz?_U-4H9dt0SUyhgHpGf@+6izMp%P^Vfr09J={S@^xWfPjiM*(sCg(L z54!!n_m(qJGRtwphsR>1TQmfgDE4WbaU{jMVN}d8WpR>;uw@4O=9tDk#%>5;CR?G( z)uVHcuk&}vKbne?!59;3Vgx%JPBwEZ=!A&&>3C@elCc`g@$oRWS^dJBr^6a!5(xcJpQ{Y7%KegZj=3V z?F*&dVx)15d68At-8DL22m}|?2@(c4$Q(IDI%GJq%5DiA;^t>ohU^`ZKqnyV*1SQj zUX?mfH5^%G8cg1E$hS7ZPRrU&>&@!JV%%w?Ud%S!7A?K)jY0k1y_8ub$O+dTEw>$) zUEZ3159?JtJ9C|DxmbT!^nDYdvGJ_XXI&a*VbA&>$r@r6dT?j<+RF&@I6Q}HD;X_e zS2JnP#hCC@uxykLf!#&o{QWhUdpsiDDjAcJ=iiKM;G^gW~Z>+t~4}Jd$z!isv211cAxiUu|Iz}B!xmP2(i@YV~BJH`@G(l zeCK+BXRyQmPe{3V8Qieq3khDb8ZANiF7#(WPH+0{g1}85LBShh-|;+mYS11Wu))7a z@4ur7@ldBP%MOkcTZBSBA*q|ul;e5PSOXTMQ5q0`D?u58Fz#W<2`rF}KCaOJd$ zZi$8xQBWfEB=sZ4S~AuN3qJ`p}NaRz|L((8HJEF_uicvOQK2>HG z7RT=7yJIz4giqnVH~Pe8_0UwCHz9#{_sI&W4Aisz4<*bt3sBk3FL7C7dMB}1q@AjK zz})UgTW0kZ$EljKa=^-GxN>lTS7{9EW3QNdf;O7f0@i5U~a&%tDo_#{x8x|Vct%cLh5(qB-5Lr3dIw-Bj z??r39Dug!|O)mtdNw8XUx$wfSfAolay?BxTAt{qugv+x8T9P7 z&6k*OZBl0Os}mb;2S7m?A-!jTzm%Mr-w0yWsj zfUu2lC)p`>c$o6=DTYPFgiO0C2feJ6o|bL6<DJD=!Lv0v*(&=M{Z1h;~bq6Re5= z0*b;cM#mMpmj&Vj*u%pHB*zCyj3AkTA;<7gC6gV2n@Dqt>*@SHc3JMXN1nwG_TS@w z#OT^Zlk8DPwis{U-Nr5=f=y?+i*oKQ9fyF?#lb!0^2);2=#h~ZqQweSK_=#(vA(UutumMsDsVN zHfCNV?=&esRD&Q1P;N{lB!;)?eJr0NYl_xF|G`YHvrDQVVh9Y4dK{LlyHtJMV)|*5 zTSz*rh4#kj$O8NfoLb3;okS>Op_jSVwLDOwR9+Jeby~w9o}zRq^EC+W&lJhRoeQPO zIq#H>Rg+5xY`D7C?6^jhCLDbbPlxo>ZT3keF}VdHv?>delKM897d{(?C`{NlbG@GX z{&TV0;QWDP?3m2(OW2AVG(6I})`743f^^NDYdV+Z1~uU{>DQo~tOMMIZNlJ~=~`9< zVnzbwAadu~1y;F;h>>@=-b|NILs~Ad1o`&KqC8+vd5AR=h71OAcZClZ`o2gy%%Cs( z{D<|54H_C6JG-u>FVL)SHp5-f9=Y(Jh^U`kd#?(^K-zPJf^ss}NOsjAH%4KBtv<$0}`;hz5YLnut zY$1^`A0Y`z6H&i6wOFnyxn=Xjpxg5H7&LU7I$wm9q$HJhixHg4vb;_Q2D{xDvwSc`>4A{?e zPdN*5(=r}0o>tJPC3nV=9ZO_*c>6JZU9NCe`!b1perP!&QEzhVm#M)(_7KIIB=JR! z4BT}b`K{Y$M+@ADNfwWoIX*qDd`3@S`n>o~UPZzOQd!=N^2ki7jC%xcF!0je)flJ5 zaOrvCzt_`$E$5sK%B4RknsVAIKL0*b{DW|_X-CG()X{jG$MHd@g^bNF%97c5T;=!) zo}hD1%|ktpeJN*I>!+~J@+LeAY*N+Ts?T6~`oW81zs?3SCRiogov}S>#sgQ{$NmUB z13rgssx9EW0(#nPDz#(lS_Tzw#!cc$qT*55 z%U18x$fL27lW3h%2%*K>7#*UrupjKy=*TJpVwq8$==8k(4~Li-S*92^VxEO%-cfU2 zv|#*9q-a79u?-zLyhvVrIL{>Em|J^S()F=eSbIRlo55Qn@63#d)E_BqTh9DB)7Yhu|Ki zyT(}S@9FnsDf!^(VcBM6ag-=E-#42Xg zl>1J4)EQbcG$9Iwb-VGPp*3HeokN#%zT>dEyWaU2wRlcW5|^;>NC1xnv)zA$yTp^V z&6>6fvHgKB;C4o#0qx{_p@~73iL~zz!zoNAhAApECv4FDgVJ->#9+*}RTO((lo%p; z#V_u}Q*Y>%$#HaiYH&)QE2Aw4+cOW^J!{S0pSnl1F1}m0=${cd$ul!Y{4tAD;7+Z} z?Gkw%C%&@Mp<^dvK(s~YnHO1Z?*b|kI@Ax?*1HmcU;O`wZENCTctAZ(|FK&KXoY=! zJng-CY+L*Dply;3ZNsd6GDr5Q(C`|<+DDY<3MrF_YKEUyPQh!$yfgDBT|mqZMoI^c zxFKc? z99i9)^(fvdBz-hWO3EWFc>+2`)gdST9xc2~N!|&I?oCow034k|n(ddsa$}+$=T=r% zu@KmCuMB%*4_GBVSeVv0f`?TQ8Cv2^$&O4rT*M?}d@yxk;l5GUp2rVaoyE#w^EUM% znmVV(f?YL(+06`RYEE+e88O3PuAR$rzGra3{2W?SLzew$+aQ;te(^BOe{UIwZ(NrpL&2b} z>}lVrq_y_2F3SsUlH)Qo@ib_cfKBiORq>f1TPtm?^!IP_<$i`^v0M_CV~3RGTJ zWewFl=*~gX3UV#QP>78-d88^|@-7wObNK5-iUA9eqClapm0aC8rD%z8DF?h&2E(zD z5qAXJn6KwgdpZ$}Y~G7JtYv-0k@X~n+Ad97^Gp7qWZu!{N!FyP%>xhd*^}o22_I!S z#CUSXIG$@5V{#S^A)F%HPknv9gQ`^$$ir%UH))`~sea%2ZW7Tfc9Ie@f(?6XPqXuw zwKePdEpW-(wk4CT`|P)%#_9VOj-MO_P=rkuTcyfkTVmA}frf*}w10%yTgJv0FnS3_ zJ3OVg-&}fV@>MRoeV4whn%d!+h*aEYrd97In=#^6b6cEp*Vs&l=GJFyYKXz~6rUn& zcQOr;)I_{&W?PW(mBpz#A^Y>^iI6?v1if#iVRS&M1#o$V>2{A9B=GQbT7gZs#>=T-Qja7bD68KT1RP4 z8LY%Ah?HB-Jjb$X%2&4ph{DgdWs#tl_O|qENDzh-Md)z$5S+vHAmJTjQW=M zzRDWX(7Vy3W19rqg?X1&f4#pm7aDCeaC-^**>eh}lC;1kb8RTpW}P&Ok<1dzw|Bt( zP!pa~E`@syaoHnM!9qC62%YKq&?XY%&OFo-$1~a8ERF`vT)PdOfY&A6&_+8;6upq$;Yulv_ADGM6ipp4 z#C+?n`G9<1ah8k2Iu`WkMe>*XlP9c51M=8AA@ZM`Tqe9A#&Ad=y^;c_|4^ z-7D!RzntvqRZzO*Bp~H3tKW*3+%hIpZ=U>AcDL>07kp`H8u!f(urkEKZQrp1pDzRyx?)j66Hp{|b>%FTIvs z_@u$swNB9fK5=?$E2mb5jm&Jy##|j`QzS*uMl_~&+fIU6V8HD%HKCyZd;^2+U@?{8 z6cMOhKD9+I)(&0dvAi4(*H%@W+@6*h%!AbDd#SO5xO6iqY_oJetUYMbNFK%ZPTPsj zr6{XXlP=Dgq8~~=V8au6q^(*--=jD;9WM6&*sWm{_JH-7Xb)bxZrumQMN;Lxs<@9c zY3JHb9MXHRVDsG3V8(0h2sL;qG}ZM&Qk=-B5i~;JJ9scFgwM&QbRBRrp2$OE@l73n z%zD6q`WAKzt!Dy>9SlNMo6b|i5l1aJs{ieZ$zfH_q(5V>QfnOg@)l;#=F1+?&{ZY6 z(5SVVPm2$FNOPaiNZ`Hfi`ghp#A2PxS)6f5^LaJq)Cp;vy@I74;f% z@RfWd;llTZX+xjfCcpZ15E|yO9t7O3v+QDKEB8CD!;O4quJn;KMImGNi7Inc-BKny zkuxt#`|VCZ)>=>RTEo%W{%9G26P4YO0i7J zNQjvTCddz587&xNT(h)T*!4pNh&6pio$9##QMe!|JAvq2uWB;oHpWU94qTcNn-+d~ zDedL0r{9yPz^wE|)0>-`E3y5hxsrgkiH@a__I-Y{XSzR|1pb>AOZ0S?hq8X%kqywD z?MFwp%Nob4m)u=G`B(Q~djWSnPmqa2KC+(MgY*fq~&l_dHfsHrgNE^H{ES z&tqU>V*s|_`qn*<^v z;i6L+U>J2kcd4s*{G~?q?^Fjc=bs(wF244Epe>h0DgOYAZxxnTC;68eg}=pu_BX)* zXcYOQyCee(3-eVNZt5=i69%ShFaSSw3MW0VE$>rE^nf^E;v_{@;_|uFZJUuGyHpe=;;9R|I>iotS8KjSM-FQo&IkD z`quK3@hU1zfa}bGr~rDT0_@j?asXfcVZVUqkgtF3tEB)p=dhVrSb=p@S8~{D6K1#- z$S$m(&!>B{7#$q~K6AXP%uLCfdb4bl$RIU6Uqs4E|YLN@t1 z^(X8*<07z62_#lLIzFx#W<4HiZhlccr8#x7xb}Sa=wxMc)@)I!-lc}~eDi4Hv~ljaz1+C^YWP^7z^pRok4lVYzXM0B-yDBI7(O{sN zZ)Of3VSd7dWWYM?}bgf+TZxVriT!E~Ol zy3e}kfFb8O!nWq;)*qdTM?BF%3^n3-q<%S7;p0?Xc~^`%MxGS=s! z_s^WSCF^xSr_71*=(fs)54I;Z8rPLa?!ccamT0IfJm)ed5rqIdf}f3( zYVbDB%xGlYV~}JsCFl!b(e{|Jq^_E) zBjDY3o-Z0JQdi2fyK%osGTl32+e@h*;(W{9tu)%Vx#)2C5P zN_n$(N`)&A+K4B24`8TJSjC6alQq5o8}`M2H~hhs<9zLCdg7_m{>DmFQV)u*F-tJD z5Yg9c*gFg7VJ&v~pJTl~f|m!A4i#Y;Nb^@V57`hN!HGLKl-$aAtj5)qY!K!8urprT zxr4I2F+)$HjB}C#R_&|wG9sZkqGhd3Ck!K&GG z9i$s>7WHZR2XDaS+2p)%1=r}k+L0pMKAVVG=~g4C6ph;HuL?dwD^j#+BDO!84;Y6IY((^|VqK@3JVIj$) zS2$W65)@K`RdKDU;^CovF?^U$-f_Silreg+^5f88i z;akeW6V@B55ro6O;W7;qQaAiS(g#*c#|Y_MTjv}^x^ML7U~ZGl+7YIijAk>AL7_9uFn-!TJ=GeuTB4z~2fIsv z&L7r|85N&KkyI+K?O~;u2v(5cVGc7D_HYZ@wk^xp@rA7qZGETy{Kur?M3^SV$8eMs z+%tW1b{e;~LLoi7=|_-IZGzCM6Lc{qF4NK~L{tIf5=$rB!vfY-$!X7xwQ$+B z&uR097SLzr)D+W5NUl|&3ETq$w46&E0=KrPo26UkA&~CVy9P(%2H1IzN|Iy9$fVC0 zDhPDOP7uQQYlY&M7JQc3#J^*@l+-5o!kP6jaH&f1fGqVj4v65a{wpDb zhezouW=j?D*bBymCRUH7(MloMgjmjBY}{+=?iS5>j>1UaUCLOZ%;7~sz=x`9A$t!b zDxUusB+JC1*i}o<`IR1#)TD$jH}#-?vF+OjJKWXWunu)hWni^dP+9LC!UDai;Mf>M zASE=W2-<=W-=>+ik*+R-Pc3+>M1K{PO)!CrFRdhgbU$5iizq&}i$u;3f+D>_lk6D{ zg&dnKIRE4N3f=dTk#nlxuwl#$#b1dZ8WT3fiF}Gdte6`&crWSfh0Z1(V=RsZe48&MJq?YmmV6!ne2gF?a?OSA|0zv%L<|JW|U#n2rd$Q3Wi`n^nHooECu_z zqhdk00Ht8ZIGq`FQd>^@J;`FI^g)rZ!T{5NMita4klIfIpBxTPBSlUBnVsQd9 zTyBjT3VA7RW-l`0+NOvCFY)eqi z^W1`qk3j_qRTJtx8+w%{g8zy}x3HL|T0cg?UC)0^sI*4IW$c_Nj)!K%Ou7acbOwB7 zybkT@QaC*1pdIqZQsWY33Y>22G(lO>Xn|Ew40On-UK1xjqr`Rc{KCjAI)~2Bw>wRH z2JKxSmIM-NQNAGV8|XM~55#DF;XGMgoruxkNl-aoaKn3B*|=!CzjJoFN{ip4;if#L zaqN%ayc4coAry_b$Rz)Sa1V{iom-43*#a8US0EB*H#uwtRNx+k;u8NoXAR92@p)H{ zmeUZ>gl-D=#rHFwjE%mD_a7pBn!C*%!t7?)IsE0=hif%of#s30xK5!Yk$_f`SbJz< ztg)N-wb~sP$M1ty=jY|*M5igd5AVqqdLSJ*VED@a z$sDM-F$J4MR`~&RPCiKX*UvHhTCRHuUqG0rKL-jG-7g6%W4+~z=Wb<n@gkRtjWrf_enQS@*D$H!5d@A`$m^!&smN#Hk{OM{IOuS9(1j|M zO;i15j^(qxoL(CSBNx7Bxf<@f5mhHG}hTKze@{BnGkM!&PWeT?_+{StK zdGd8%_*jE42rMM)vTv~Rl!Y|Y2XtIsiUc#qHKBKIl!~RNA|w~$OuV%ebNIwngIc~5_w?`NB^pLT6tKxSiu;@k~I5Gxp!7D zN}kwusSH0SR9cz!zCeHW{ioDe#Y(`8d~sx+ws2`*69aK;l*%}Hra zS6jF`>1_oTAE*b|htuX#UXAk+&Bgl^-QiUb;}!+Dau_Fu+$`;`P}bVyS!cLmKN5P+DWyl)FQmi*K`xU^RL%Lb0DBLqU+=*GvhMomuch zqKz&cjGa8TLY|e?RUyJi= zXgYW?_^1*g{WxjpS&f?Z{<)`#yJV1Trk0~m>n$=2DNH~T-eklx4DLu_Pt~#G+nug5 zo4lqSboFpC!+#lFf4j&{oN-P0%5#Y;;%dj9Q$O6dsAwiM;4Wno*>T*6kF=-fjBU&u1jEpq6j zvN$vG>@|s}wj#~o(@HoJKl6)&3#O3CLru-Ik`=d``ts}8$38e%%P(|f;Ug{?79b=^ zCTMRIg1whK5!sGQKv{$TCEHDpW$UYaLsaYWS1|@nzgXaBEQqnih5QGzFF_?Dg3@5$ zln1|pNf04(k`P+rZ{o^dwNL{sJ9lEY*87VywoomQIm7 z)InWo7_aD4d_KKpU+FD*VeSeS3jUS9;bYW#L<0z_z2A!2myQ#xFJcq0@?HmFGC@vf z_e&{Sm)wo3Q1qn+?EIP^GvtRirVlW@Xx4$DucE_>_wi7}EjC1ZC;Pd*GObMx*5eU> zK-gX@8r-@QkH?I+QEf_cWV9TzZk$ALm&F>SJ(zG5n64ZL6T6xbJ$|w{jO1#LST2u; zgo6e-$*$iJNH!rMlT9nBK@lTl5=v%?-ra7ufc%GN`YVi@6t7BoGDTNSaan?sft`4Bsqj5qa65 zb~t`^YIfjwV2kScgF&(JqnEb3IcU~V2J-iodn!|(nzG#+4|_2~wZZ25Nis zPl=BjubH8&BoNN8cTjOnXbv`pTkf`K_Y*ML3{V&)3m;a;<5F=D9Z0AqM^`yMA$@B} z&hy;0$6Li^+ih=C1V&?fSTuYbY87#>nUR;dYPT6t2=nAYRzM8V3kCJQ+=!z(HkvH! z_u)@VbMmTjL64vtHQJQIy$A%c49l3TR(U^JLT<&Yh&r*ZJ{A-3V6J)+_(P+aeTy;AllQyQ%R z@%Vcg7B9Fsd2_fV#j99X(L!PSA^e+teO8iK`K;DINvs5O zAyebs)P6}Jqt(j)JPMOg>`NgBmslU~`(s7%qc|y|1nCh>FJ6Z#?%ilSw#?&{0+dy| zv;(E*Vs_0Lx@iv?$jDy6!|OfGZtjfRJRJ(hbWWZu*`G8Tl{2_=*0q>SQ_l3FMRSx}<8U-w3# zyOM#+ODk`tU~2TlOjnP9k^b^}$7@_A@Lj+a2lPw?T=oILPMzQ?B3Dx!KY9JX&guJ^ zD*-XViwRta83XN4VFUbG-b_SHP*U5R_WP`91(g7e;Q&b_z>9J(tN@t_F;jgrz%(xJ z`?&a+pU?yTe3>0ky7(f$5S^<@ibsGWK9)wN2ISv|q9CA^)iW>xP^~Y>`E<>6^Z2jcdB!zr`U5CFHM|!$zi8lZpeI>uc#(YEKjUGT114-v^`JI1F zEc`0*#sIj}3&2|gE}NwXoN;DlWBwQ3`m%4@by>KYV7QsLW@cps9-+R%TdSLe;Z>Np z6k)$%< z9szdQUu?|^lblZ$Ue)d5)0)*g9i9z5vh^9Tu(jJe=-U-qZ&(|(4Z?pf^|`(%tOf4l z*9RjLCF}S+DAh_k^8f~qtG-XfpcE6ObbP=i>C9;VYe{QRw=bhk8w%AD-vceK`lGLV ztz%^Kk0MaW%{`P5d2DV04qahh9J+dCNnb#L9MFzb@VqVAC8&O;h6(Nf8IcaFnz6s{ z-0ywc#Bo#*zSDkQYtR|h*{1{-mvQyNrQ?mYwY7=;FO>B)4z^0!xG)xhTONI!NW>7( zHDMl`)dsZE8|o}4r>PcySSXJ8j%&a*dFfRU-s$cWXUY%s8bT)Ji7gsquy!DukPr8Sj}lF zHs)!ML@b^12ZM4YgHn-ubA3G}M`%s(cGOLi4zUR+vW6$QG>}XipPQT6n09!1i2fgC z?;Kr86!z)HM#r{o+qP}nX2-T|+qO<@r{kn!JGnjI%$jfJt~NE4m?_1tRe&$6hJ7sIX z6fJKae#XKV#hs^F8Z!n_i@rLc&%CaM2RqOz zAE+7ni%>51oa?ZCseRYl8yTX#Ads6TNWt* z|Ij(RTp3Kz3R7EiX0|!11yw&#v#UugcdR*~pg^JN!RJ0IgBfe3;Xw>a0|BccMsggW z;y94TT8>>)(9$Px^0&?On+5Fv-&lkB)D(`WKn#on`TP zvAoI=vW17sxZ+68I13A=9udxiIoW1F4iQI}C8WS-o`C9NDl{4Fs8#B;eF+kZpza|!#0l_Gq_C!D9XfHAL2uZ=w@R_oBJtYZWt-8XQC?Frg!gr3< zI*{XuuUg^*OXJ?iSDDC+GWtsfVWHOW54Q59X5CGrTk`S^$E_Gzud-wt3P`W`8q!G9 zL1)J@iY3aS@^56-G7g%~%sV2OGY?;HXfIfVS0=oKc`keySVyEOjF^kap5C84ccSh~_<MHzKh@d}<)?lig2&Yb$N=yv3CqgDvbsl?s*Q?wuQ&@fKNdX4^F z;p|#U99#b2d1uex?F~@sw6K-j?C8y?&MR~_*xF3jrw!t+*j}3!hvtJ)3uIEi;sI#^ zx_~tvt?{D~9%d`+PO{jGntxZ)M$#6LrV*A=az!Z>5SC%oYJSc|(tfcOu?R_nf%YK^ zg(YV@J|WVbAshf$c2F^nLk0;%&1eBw3=5^_N`qlDNyH4I3*=@Q81TfUK6Gm6W7U8nUyci<2xc0D zTXds)&|YUlk`;i7*pylrVbn3ShG0^Xg)rGcMKy!XyS8#Oo?jb}Lkkb12Z}7HY+WE#aaqBGIi&0?sfEwPr!Bsb6W; zhOjK589N7-T^kA)?`r}tOKh+WvNEN`im@AZEvKcToa32KZ??^JG+0cEw%Uh-#Od;a zwC*!I$wkdP#@lcrWVw^C`W-Lu>&7?Q+|OtHKJMr_DYaa&R1w-_tEns7b_BVSB?D+O z*T!4d(AoQ}e&p+=XIHZF>K<{ec%UuS0Ggrd~YQSVf^z*^q&&e!IH)En$c$ysuzKy4{l&h5(l~FfRIWO@^chR^5Yd?(M)k&0mnA-K zW3065%atfHJAkHOW>OImrwf32EQssOnK?|LX7E-sgxh~zYQUN&YPLc)OHiOZGj|IHMcov|D$(D+P?5Hoe@GEgzqN9NI;iUf?;1zX`2^quT{mU%S3~K*wELrS~0{*?a_kx`&@>g>KGD5?cX6+z4M-d{lWdGcf_N-5bB`pc?Tw*D+~NkP_hC5WHCqI)|}~ z!{yT&dEi=DM~Y{&kWHAYvYDIMZO;io+3PkS?qL+P;_WD6==iXx2M&TAF~ zTWyWXfG;3xyH<@_HI2%G{vEyFWb1G$TX)%3grQb>3PF09Yt-eAiLeCvp{#omOiaqeh=b)Qv0<4!1oDaZ7p@lAGM$ z@otw_Gp6HfHlTGrcIu=4V-qtW$;y`0%R96$#)pnl;XsMzDo2*gA^wIX=DgV{S;nO^ z$}g%Mf|c>2TLHGwGpp>1D~Z}scsHg>lj2AAu44DjK+r;QIsEnzqde*}3m@0QR;fBy zB4>P?3@l<}o~=OKF4I^s58}F|ArNdC4YP(eyAw9o_<+kS)c4N5FFptBjDP^k0ieDP z7q<0W7=Oj$BhKa**BRvIgNBd2n%?1+HI2#nC#(%;K|KUZMU5sGQ8>~jbA%@*JRT{? zyF9TQ*8GO0=_`ShLnF_M`#WXd6JQAS(50usG1qyL1-f^I%h>0k`5QrvsFrTW%^rpy z%{B59JXe$&HedGfcg|{$j#VBGN>)Z5`(IAAINsOp3x1urZfi?jM2x5`8@3=k`qx=E zX5#=o?c5NTg8l8di)u2;rQ8EwFcQQePm@4x0w|GGH@l-zXkRjuh?nOFCsL6#TdSyl z&kHW|4H}NjKBbv}=O=759Q25H-}wqd@y->LmxdI?K>MKXJFtdwD#1U|y%pfGs<6jx zCg%gB15?zS!ZU)$Qr1uuBt6>zNAv^J1Qfx^W{^VoZa*4ulmQzFDj9am_>bh3qVx@K z#%k6>2*mFhLcA|NHxSR)eg-e*R6uaXU$JRZR_&N{6$J_4&YGyo5+WuI$=hfMPz{9} z%AUgDdd=^rDn2~=DUL=3MQ%KKAv|FI6S6v>FC4X)&u#I#X9A+oVRGP3adQdlb#%!=^cVwh61eT>!ynX^7%@Kjv>gW8d`T52**rcX9(*jbTx2twL&oW_g zX_i1hBgwTuyOGZ(jMQfEREzAWMEi~7d4LE!`EG0YA8Vi-O-X%GhyZg{1G?1_H5+Rf ztNH|Mr&u^a4o~cuTL!!ND#(!)9c*9{oP0tWqe_R710y7j9ec zLDZ*%9cI~-mXTx?4veOY(26)gGi*{CND#i=R2D_{GidvOs>06-W~qH(D$gj@|D?TJt2`K{!lI!nL7!h$HyW z9{Ms#tTRM{PMRKN^{)h%WksUlsl{s;`QA-gB`mYB2ac*ITOPYQBS;9<#DOJ9)nAwW z%)MsZ^u~)}4b?Tl1d}2?Zxd=Qlh$al%S?JR>es`w8*2)iU?c7;1E?z^Vkh|M%9s4} zDL{Zfk}PVy0C*~gu%-RU4(i-UCX{uvsV%tDYCc||)W|`XIB#nW*(o!ecxhNzN~ii= zMX>ueJU^N`chm>PLl-ZmKdl)lY|Y%AwuefU_bn|txs#vWp4GATEv?6^+imsBs50ix zHpd7pp+FM48`Jy6HLLrQ6c`n%ZG(2Q6V_aDC2ts(%bZQIsxBeYgor7U<^hqlB;{>M zx`@n`Z$(OCbaJwHBb#%Pjm*afewn(?UG=pqQe0k9;fl+gDpfLjw69&9c2A4yo6Uw( zUaVthgA5i-GVqQFj?2T|$^oHRs zk>)5qtlo4jA<^gs>%D1anF@nU8Bw0e3WejNjw^i4*0Q|6ahH+;Oj(pWX)h~90}i!i z%qCQd^~Vmq;{#!q9ci&59|FTTJm;BQ0fG)&1{428#cAFi1fG9eWO0DRWbdx|8|VK3^P`HAkqerBsC zf<8MrRXeLJ(>(LNjSJ>h4r#>S;%+QxS$U3`?teTWB!)8-`w3Km<*?Bn?p1*a&xDh zjY8oUXWupV605E1czZuw^x#GiFF!~ws@+{ca^yHHH?Oi>K<2g*)-3sXFNy8~U!)DC z8BLquVeImh0o?>?+A=&;kNAI}gcr=#$t~Ts)*_Ho((AlWq)x@DA+wZ;Ie!E93yfDP zMAz{Nx{Z6-M&!xu@jz6VZ>fkpqDG`nXvnRcg^IdP*=$RB(lqhpQ&IepwLduT<(P;I zZ{DVnN%YeR&WbG7KG&sFdH;W- zX7Xnq4N!b}+1@o#PlQj_;iU&}5x6;pg?)G!I}IrNDzki(1Hb3laxDvk)*&8!s5a@U zysC?Hg8e(}O3=S?Ci6ZTdcj=ga_rb*ooRLZ0#ugf#8t=3_K!z0JIyAZPP&tqfT&90 z9U)hWtAh-GU`=)g*?D}%-K;RZv(r));ugQg| zgU3-*I9aj`w-$20<^i>)To#e)HQM3|J&zz2*}EXYS48en9v_^d( z6p(hczx%>`=Vad^#~;jJz~lt>kMAyExR~)fo2EYs?&O2=i$WUd6&NHmM|H%N?D(B2 z;9+1UYVYxl>j`}4#@?YxogbUue3mENw%0Q@SNM-8Pbph{YrcGHH15&*;V~z25!<8g zizh=X0vv{{Y)9xuk+n&X__zW!Tgwn7tOtVfH5#4i>=n;4=AE)prEhT( zmitw!ea9Gf6G%Mep5z@mN%Tgaq?Gj+G6mTMA6~WSX!_EW4?$S4EWI1v*0x@*fHOoO zOT_-tW)#h>@y|qV87blUCAZwSU}V#L?S6=R_-qw=T`v<&0N;-Q9;(a^K9U3N9r zB<9mJ*R8C}qc7Cy7RJTC{p%&KGIq|EdLGL)ZqKaK z+Resj$C(Tw*tz(adc~>}@eeJjPAtd9HH2iE!O3R0zhs<2*+C~{Zd!NV^lav)qEx!% zj=vg)jxfh=tpbq4m}tDtC?1-tec?XAyr2JPn6>oz`%HA%kuGBOxv=;T=rUhuL(sF3 z@-*U$!$FJ#q)q%l9~i(6HB;a;T|J#RfMd_D%WgHe!c0!DvmDRHh9d&t05BQDkJmz( zF85FYy8J^YNchqMJ{f~jjsd^vJRYQvW=Ot?lV zSF%~_5BxjN>QL#y5)qLL1IpyGPF8!EW7?MtE!#;}KuGBOt4qc`NXs&2tjR~!>aDO` zc3LG;JC1%p36UvA|G)dU{uyxpvp?m3lUx4_-~UVA{Xgc`b`H24N#FJZKZQ;N==VlD z#%Oy^Q9)ii0Asu(W1R5c%>?98wy=(*5=khu=r4QqQA>Q*O&g}|P3h|-Hp>qUnl&0t zn%o9Ee7XUgy8Nsd7^l_Y;lsz})%n>krw$JNpI@I>0)4vwLf}_VKNruAzFw|?ho^E# z{|kY71ObDcUhkEf_H6U7=eOpEUTfcn=ivv7d8l^+?By=DE+@mR_I};~@~rsJ`}EVf zU4x3)l__YjP@3r-=yHrPZxe0S}ro4#Kx>vNLO=zt0-b_RQ4>~(1yUar zNO3_~8gmL62;ldFff72k`l}7BA@M&~^Wg@o(ieK_6|JjMt`Y}z|B`k9x0j2Q*C}6$ zR#}BWe3pA~5L-@k zj#vcctFf!K+^kKqZp!PECO7YF+Z?-Zybt zbU@3g8CCCK6|CtPuj0ADyC(63SDta}d?0$#E$1Ovlx5+(@Zsj*{o`~QKBr!c=}iht z*!VZsdsrj}J4qoiW@4>+rK6~Toq8aW>3bh_c~Nf=k3X2V^W!;I=B`%`5@am* zgG&9&?846eTA+`TzL9R6s=_y#NelyoL;h+Nrp+1=rle_vUVNXOuaO60!uxysMxfl84-8Fx34_?K$bMcq9{c+ zm3SKL7sp~DwQ?MmPC*R;uypGdESZKiC{HU1`(POyFN~mZQ7djN(|8S_%I?yRi%IMx zFno@Cr|VHmOBW`We$=vXQZ5oKSwjRPQ!P>{P?$|Rm*SKaqQ;WGAwCY60mxEmazk0S z1KnM&lC#%HgK*g$eu~rV#YbK!#6YTX1)(K*XyfYEr9&&d)HrO6gsvdw)Y}Wdejld# zoky|^I=+NB`nHy`CuHNt8?ZeTcTyrXwZ?X?p1Lhu=x`PMq^y`=D*DXtqUb!e4S4+} zhNg66i$!3lZl zh{#wyR7-R2!?68e!J|CXde4ibtFP5y>P{65g7v7@at&&0M!aTZfe^D9$(kSr*bB@!TR)Pn&U3Of{RmtMTts*dwjQp| zZ9y?(U2(uNKy0dEh4ToylsCzDM@A|WxA#-o)N6D}W~!+*P76O~+r2lecCA@#L-nNa zRT41zkdfGu5jB1-CLX=&cA8xKEGbiQ=CeR|b(?$GMNL}Vm)N~I80f^6NDTO!ER-IN z3jRYMu^??Wp1=SJ67YbU&X;W>P7CB z8Zuu(-~fRVvR(FEmi=8I;DE&o_4&sZ7u<0g8ZV#(Q~2ddQA4=IG0HomevUy38${n6 z$88}B6S3DKwnyv?v|f~T*XQF4g?vDH9J>YDL{1AozTJs`FJJ>Ap(+&++28f1t!kGJ z_$zc#o|GqZGf6*Q(l6lS;?~jMyRi}hIA;#FHv`cqlR8#ZY+*mKu!&=~iF^38KvJ<_ zl7n?d*duox$jm`lPe^#-F6a<7RR-6+KB~v?wRxMr5XDG7GW!7;QeV$_nctT*hmxZY z6iblBmnuHvbG<)N;@tE78E)VED&@3PZ%!$F;Z z?-y7^996o{GSkr@{~FDYuJ8hPIj5dh)69oImO4^~V9;Fgs5iLrr$oqFku>U9-9N9+ zQj-c?jTHn`M~y1Ux})Nt{`~t)MMrdM;dn`%t-N{yE6(?CRo!l{-^+XVLBZR)CM3_t zvRbJjZMgKtOve7P86AFB;Di;8c2iD(sh-f$GFq-2hVk`K@I;qrJPG-n!N$+`b9cL$ zA9$jQCVy^-KtThcGqy}6AH`I3TAQaj5OH~vvT}q9`L|djX~?G;f;tvGSY~4j3K0-g zdNi=7z~o#E@ITpRBO*x55p_X11Tc`+-i8>k$fZ`|)6SDa`27iu;BG8_t{Zcv7B-j` z;^y0NDN9EmRo-u)l}XZ*%B=;Pu*zeah}-^=n85bH9=6d`_bA=;mqvn#bIO{$MpWQ0B$U^aVM__7BP}J$%lS2qhW9ASW)8 za6}UJCo`TG0n#Rn*;3Lu`BETh%2M}vt*XpFbHpM|SY>c-Aqx>MN7wsHPkvr4HLKPu z#=P1^Ja$N`Cn_=mSwqFFYa@y$txAvqPf%KPZL@zfe-~IRxU$_8(cBE_7v6}}9qo`L z_fsKeu83L$lb>WNWs0o6Dc#bzPDfEIG*?kyr}1fL&wngHj*y6ZdaO&ySB zUZ_%tS;d%J7Vk?RLSEdH+%`6<(!2cFfO>GBX>7Fqg3-_{8K z-nKtRB%m6WZG`N=-NEjBzari5yf*dA*nev4j;>3gw|r6xO4>gc(U)NWo}2 zv+01^oTE`Xt#QlB8Oeay(@`Hm2+=4~n_m-UK)r!6hf)-pE+yN^!3}}5F@SWG^7Vnx zWjj_)3Qz(grqQOBVWu$xrPW*+fK*g6Py>@Fic^ryS1JYDVk$5KiDch;0I`qO%0LhW zZorOcPv_u<-ywRXbqyH*HdY0i0HGwowJl{xHgMnoN|}6kN3?K!%B;S=vC-ffRMW*S zvTlt%CAOMWXoMSQ0`*YOllh7xNcW}ArgtKHbU}+vL4EL&ByW6(i>4~wEv2Ji&imB5S3lVT_O1C~dJd(Jr8e@LaQ&O+W(ft3X2?oDb>vJb zJN}Rhg|Lv2Jf=qX>f!wbVoyfGM#A+-9DW-jm(FtyMxc!5Xd^OMEcwJdCq=qw>N2}X zm4wzRG!aF1lu|iGc4-wFg`vZ(o4*00pl@JFP5c`bYudBqR2_9?6Gex*<~&kB(@7L; zC#h%!t(7?S+cZ@t+GV<_1X--jKrL46&Qasr%l5xKlQ+Rzwtxi#lTgX(I zrqPC`VH0hPh^C(rI!5Sc@+rs#tA1xPv~K>Ch_ZUQ6K9 z?90Te9nk^MFMqH|4ndz6R4=E*STaN_H;;j=M{ta)T zlY&^PR*9Fw5phi zQv6J1I{tO*YCzOIwJ~O;x&mnvxrzg%aJniHm;!zthw}6S>RM5RYq(V6G_H`HFLEh5 zTV%#EUiw)&x95)URj~TXy2sg7Vb5|GypyJ_MHwJVhq4LH1oqJP&BUHM26hJ|J>z-M z#ciFgc*nQu<1lzU#Q3Jw8Kt0!pfD+4s6?+oJk6Z8EdCDJDFFMj#U-MxsG@q2VzW|w zC#>*$=W>BZo$ZJwi?t5qiF2~RRn=Syxk0!A@w8P4A8K1_Y{QJ$eyOe@vx{pSFJm{0 zspMdkNN(eo#iTq8?F{O39~$FFZ#Y-h+0$j_KDu=^tr-i(%%>m;50M`=h>h5~p;j^Z zwaT$hKf4NFiAxfZ)E@n0(8URdWA#~;KGt;@L~m44QVB13Omi@!_|a>NPwyJ<8<&73 z#>(xv8n-Pdr9L2mQ{!2ttth3|X6si@mOYia|4fa+RWy0KO^b*99)qtAADY&yT5qGV zTH9VT=_^?4se|Idoivkn;ijR|mKyA!h<28@>w&S6_V0qrQ8Bpg&ISsPC@RxgVaBJ@ zA!u}oGe5T;->`qcBmFq+5inRBqZrEEpBqQx-2(6vRt@8pOj6v3w-0HKqs7{?W6o<4C+6~I=8T`-Cnv& zk(Akb{WDBuZsYG=&WX$R992%Q(Y(4tPf2q8$cOJ^T8WQ?g1 z&{2s@fWDwHyY)(QYQIl&>Heu^ZGezIbCjn>57dy>EsiKzwLD=|dj_Sq{!Ty)5v^RZ zA^-dvFxdk&u6ZZzrZHwMqiQ!2QfTkhBiedBZ%N^@1>L&xtGU;9Vq8qhN?_>L=eM(L zrzUFXmQ+6fT{bxBQToBwmAzV?@+cQgruOe~sz!EIwn6%Z0JA_qOg_09+9(C=Bo#oV z#5$7MrthvOO>(7UkxD4Uu}?FVdR>umAgr2G)77T}fSQ9SAwPES=1Pd-%dLfMn$fY7 zrs3PXc5x^th7h>$OrXMzxx7CP(y(9N7M^8{U zg|oc^Qgav;i|O2Dfs}jVGCaVnkcNq!wCs7A=+f8q>HBru*F&XWJA2S2h7-FO{m#z1 zb@QTZQR}tw9<$n$d@0a%vc~bE;ej~wl|FpD;`gxs97k5*nTVk`>g%?ts`Hv<;S_C) z)&3DPuDhOFpM<>%JI)O{z~w~WGApRwy_pxaxpjH-0-Nvy`R}&7`@cvJ|L<%KEF7Hw zG2X*}riZ<{I{$19X#N;~ei(0;swyOSdr*|%F2%B0EdyEIgFuw=O?8q&r;=#ule@&e zeGs{Lx=F*^-7eeWTGBJ)!;d z>G`_`xu33HE z^GZbCRNU}QK5y5PNayyK@i%$6x%u~{$hs&nIkQ&*a{=f2-?v9+a83}k*d|!J$$YDC zm*D^dhWNv7V`$6@{pK&da>5K!mm)zMLTf3C!zfIl1|XwVuixK*FPDdd`9m`70*odU z)eVmljTWCCZ)ex9atet6m!Ga?g7h6J^IiUgf6{$n>V|Qe5j8 zteYuLh26>dm&iXIpMWZo!LEHtR!z=QA1SYLxC<=2rohu@}$e?@x>J zWmGs4<3>VPGQQEQ2#_F3R&?;dV%kE=g{QfP*ij@7ll|)=?FsC<-t(sFAES-1oZawpPOsE%zb|!a0(;tphaEO z$p6I;fd6wN(AU-Nw}Xen{?Q-=$=UPywErCIb9a6J{EfMOY5Z*b;OP^P?1ZceN3&0o z|C7|c?wxXWkkeiQ<6Smn!$9D`uZzob_q~N@Vwl;vHV9zFHo+v7^*@SX0iCFIx)cSw z5po!2I4^BPfPMWl@rmf4$C%IKc6Gg z!#HM3B`E&8+~jNoGo>O=q@#xp##|dMW-2d^-pUc;_gcs2k$=F2Kfj-ko5#!TBhCd& znYtFETvW>heWHN$?^o7~Z{xcleB&%M*rIt;p%13S-!i1k4XSV-?pgo#+HK0R1} zc7uT^tRXBbH6!V0YTLCXZ&QU3LyS$<*rt>+sY7E`D7`)E-4356O{Mb^6+XW%P2ccGkz4f<$ml4`hZjj%@| zbN2PoO=CG!AgYo&UZgY@7KVnAWd%?guze-ItdXY!@+#&*xH31_O3}o7MdXc2aPxo) z#a}kbV$$+a(W8veXvDfT2ilEIutu_vDP*fKJ7f0O9SCBH0Qz7nWJD&Cf32P%E7yHS zv<Sj#)-nrSLdW-b(K)n*5i%F9rj=|7;b2%v5>@P?H&VK@zMso{OF zpK3S5s?{$_rZ;I0^*h|Uo(L`%Obl@z^ei$(6b#|{SoKpa@SKCUbPtOwUPeter=e71 zvwHonk47#}bpfv(=X)e58{0bun#9gdpK@$t;LL~ehUCbC?GxKmnWW`Dy`Nu%3xVCr z%7OEQ*!A~?cIRTsxw{lU+B3IVldWoOlhc;njY-2(b$F5rnuNO+4Fa<& zjVLop8HTN}Pfgop>@HMC)`r%*aD7=E58)1(>9&vN!T@~H!EC;-tNpv{nI#cEY?7u^ z+7O4tZniF{62}RwzLZ3@*8@Y--3&>}1lbYsHjawYJYNTR7%tO=jDsRw--h7GVCa#I zf7Q_))av4gAf8~Avh{MTEHr#wkX^sJM!_RG8t_n*?Kd8(nB3I> zU^Xkvx_7l3qqAlaMec8iEzhglijT;?z?cP-MhNWw>A*_p+jb)3*;>V;J)E>@%B28R?NX4FT z1xW#}X)29|Qa_SZiY1FW*4sqE{ULPkggQ1>AWNsYZl8I_K4*amgAf?)l1j%uTPAQq zXZkx2%D+5t(R86KXhy z)_%|Cd2aqVmF7@S^m%%tlTQeeOVnaQ3wT z8LrlMYR}o3g=Rbn4

d5gH5+6&FnN{NGnEOh!c$P8CP}ZQ67=oKrG!n8G=`t z?SQQ%sWaaoTDnd^<}*fA+_&ypO!=)lBZO`4Wf0XYks^mil}*GpMedCpw}z}D>syd= z-T9zco)U{mXFCK|up`7qdlF(If85tD!C_0(p;>JluF_^kCN@aP(PqPzLG%7x7>WF4 z!G$<=#W;+t?B% ztDy>7YW@m`hA&`aeMKCO)q16%wm&jet6yS1H6a!e=}uw|J&(v@aH%*ODfJ2nXzqn@oB@VV(@XmB9PuV^HrbYJ4My|Kw63KYp?ARM|esajz=mX()Pxq?F$1R@5 zi*2+;RM?-p)14>D`0_ALWn4>p6+$`0PZfe7EY*noo3t7Z-%<~Y@S1m$au^t;u64vA zbr1su-J)Yl)G=zT_tz$Rk?mT$N?ak<@dKLa;uZE<8Wc4YK!It?Ca37GIQSPvO!g{` z*uDh#ozav(7CC!{+AqWwAt7tC>e^sj#(imC0<;kf7uAHK3f4@V=1AZ2;;ht#B(5e! z6xJB~(G$Lc$!!gJ3#|*=-=^slXM=;_%(ZXZP8Km4>}-_9Ws*$J@z8%KdH|MG%>^0f zwHInAWaenk5~VyEbMiFRs87Y{k=CV>R@vd+4O&YLyu-X>P1F`T-nR9j7wE zh1z8*H5ZMKkw%_uEP55Rlw z&(a)I^95CL8pgfzd~QYSz?Ce9m<_g!ZZNtw@gJyPOik8XGwm5`nJ15qA$u#9XFW!e z?_GYH2F@wx>@r{!ZQ?^f7)0L``CV(wN=#kLS%X?ACEW93F5$=}Nw&mJf?}f6HnJrf z^~wMJeUwhr#c4jPoS8MIqprzURyiHlwY0LcYAYX0;u@hm2)yU}0zH2j_WJv~0$iAU z-t)K0F~&dIfBJgIUp6Q#wG=|*efcjw_&wMbeereiT*;0pOw6zfU`txu1rBmgLzV4X%~D0$rt|O4eBhQdh!S$U1jI! zw(V<#cvzeU2E_<|Z(ibVN@nmeu!Gj|P5is$IuyTr?+A9d*EETDh~n(uL-3zDa0GJZ zSQ`;re=vU&sM26ondSe5_dO(4M0&;W!9+Jj3Al%_J5p$h6nN`U`zFnPRUx#74ZwaV zx6TwKk0u_*s^Iv0JG1KU?tW-td#sc+icD6lg#G;8LRABB?vMSl5#FC*8%~)N2gX4s zL`-Ij{=2`wN?Wtxms-&WX%F@HdvusGoDW#I(+^}s!b-bj)nYS2ID(1&7~cfo-ZnqN83+#3l8*vv4$g-Ei#Ou`f4n&B zfAZr0jfWL+uy=8=HFEh+Q2GDl$z}e(r^f$-QU0H})&JP|{|~SG-@o7g9@gbxW%(Z* zlWZLSBdhyw(SMiZvi)Ov|GNbApYN9I|IF&L{Z|j=e-p6&SI6YPVdL33I0*mk_5X$H z{@3xB|L33lKO*G+>yjr{cCPZ*w!zsSBpB;dGV*yI0vd;zroyq`P1&HPguem&h??|)U0y zw_E*lOFC^##%}d|J>G-cVtymy)Tx%Pj0NxZeBB+xwsn>=1bDw4e5Gzl%zy8^eKROhuilY5cQHvi*l;^03)Y4NfQ$ zXvm#qXl!nji^Xu99 zPD+IdiUirym&Y>v$x9|eB*0wa$B}!Q9~w05*iF3ymj7Dzc4nih`(5} zI!=6{Z{Jaab)cm-=B?)Q%$Cn_uw@w+oUCjnwZc?8D-k^=Zp|jrOy)Vkcxy2?v=^k` z@+*fA$}+>&aRHdc*oRjtXzoBH(k)Iz{tQfl+Lb)T@nSo}GUY z6Pflo^YWZaa)hg94(;9_9C}j^{v^1_4SQC*JWfJWAASQNBSF37!yx6QpvH#Nt3^{Z zQN%syB_Eeexggh6cfhhg<@+#yDeRRUy}aK&LpvK+JoKFCj&6y=h-~R5dLBoxK(jj= z5zj+WAd4-q0o4j#A;*Y%)M{?>vV^*)1{z+lIiEX+k+-apl4Lo|qa`g|d%WH`etz#C z2A_knuFz`V?zo>DwJb4vujfDBaA3&461=@0f=66kEhRafoPl*lwe`L_PZm1b(ZKH? z(qKYbWLeHA^>&gkEo3iAR_#z%*CZ4o<)E2&+NP6So`i8xWHL5q^fqTtAqp1-N#_)T*Q+ z6HP04U>HnXSqyQ(?i?)~=cql?x>|@^htZ#=2_FKT03-GG3!M~EF>Q7Gd^%_EjG@u? zghq8L(vx1k2zP$b7@h%Kb7O%t6XAgCoat9Bmx&k0JRG`l-*I8ebUUIlL(&2gL#I#> zq$Ks6P@K2S3nQ4%Ee*t6gAL4$W>x8nG&-jSu#Cqntl1@yhwU*2wta&jMeFXuWEEDg zZ-v2#6k|iP3FcD~xKML`F=fQMD=D`8!ReHVI5;!QAU@>w#G=xU3SYeyOG!oU zc+fX*8hv))a)*)}9THZX4O8rqdw&fy@E1cDFf+OjUSi&a_E6F?bZJWCDpHcq>i z4OxrBjL5$w<~G18BejxQ1GRp-Pf6=zKpvjknE|U~)le(97wW(m1A^}FunSpSs{B+US zn_qJLFZ9Jh7!5V;<;8#xNv(r(=P z)2D>UzZU4c2W;s zA(Pg>Q^97SW)lKn1#8vzV?bm{_yxYaf6Nqhv=8%42a(|yLd9nce~v-4r60<+MBSQ% z$Vj8=Re3Z)N{B#0WNHPm=i}*V8!anzJ47eaV7m=ALs>X#g5Rqv@^jQK#KKHP>j6bYgH`m*N2!*Nhftg z1LLwe%EwDH`RHG|)7+e;FiV0qk`}n>>71C9Q_>!_BI`MoNqZX5BWrC5nagWLI0$X{ zqjrbhg~aAD)MR7M+H{reaz^5&E@g_=(j9_JE68Mmfre%Y2to{bT^oE@KV+An&ErC0 zr3Ph?my3=zHDWZ8XI|K%NVLPVtN>hzI>ceA3JsJ10p$23ZUhnjFK1l81< z%w0+?wH5wki)P}1Y;I#j83;hBzpd1uQo{_rLEP|(U3TC9a;gFR%sck3{2m|#h_DE% zk4rvFXc{OEz)0$ki^GV52j-q%+wlX^`6^Xo$a0>?S;Wvev#2_gWiwaTlAHxq?9i#q z&<1meb3@Z%R7kb9x#FaeSYQvhs%gbjf#9-*=WVuIs73KabW2HLFh9rq=+(|L;P~o+ z(wh&F-NZpdNu!SPWlGLfH96nQ#)iL)3I%{MIjC^zwEvnV?1x&03wXXZv8IW;~7Bf;bTP5c3QCK`uIcy$}g=k9q!W77ZpTG?VvdBTvCM zQ2~li_x34$Ib3^tNYdJ$uBdq=uPBjVo#gzZA~T;~K=;Mj_4E6#fl?}i!T12VMprc7 zx@m!)FFbA2-td=qM*+YE3XkvvJo=2*DWd-Pmv>__dJh->?#>>&K)}1J=c6gF-|e1v z)-_`PbH>4(!&&*{(wKvyJ2m6;ERglx^KGO1-3?@WndBHde)=*7T)`{U)4 z{4GjRqB?0ikJ)qngkd9~_$-c}^b|(8$skch%q4L+sIp3EJu%B9Ad~A^RX)i2J(a-FU5jw3sACSrc zFW*!&5O$|BJeQ4=S2llwCN>P9$mhWw%ajSa$__#9j7dUFTM8LtrHy+gBeep7#Y@GS zEn7v-PSy6*jmiuYG2#}bP6o^T-hCV7r&+6a_3t2H42&XN-Lka~##516W4&`ej}5XdzT%#_nA z*?bRQ9Y7_D4Vq%544CpP!0Hb-sNzNpY)ble4BOe$G<{(vRBwOjUl0rC5h~>2yN%jFon$f9)3ly9eEONA_B~Tqh zUD15AIKvuu zA+vzhb(Hi)#!g;@M4VjS(T-kA8M81M5lLsA!gb;l_E_a7b=df<6zZR`$6AQXWw|po z3zWUF=x?%!VPaGd0jIWi)^=w^xF|CAwtGLO-FPc(pOY|k7|<70LuqxA1%c8M9ob{i zKnVlGlB>gS1`)&qKjpV~LtZdZr!Wmt;n~mYziWFamHQ)2Kyc?8PT0DUb=9FZ?xD&+ zWKk}Pugrbg{asIN4_}Z51D+=`LgiGu46Hl zjFnZDkG@dl9y@P?Y^NLJ3IL?2Em|a3+#SKZ;N)ZG48o2Dj1o8x)C~}}Z#J8)_t-#S z1xZJA#@Yf0Lpgw3oYa^QG7p-|$T~TDa-pQeCop-G;cD+B32oGAyvoI5G?3<8=>9a&l! zHY~VgzI8E1Nes}aA_4?sQ~=)`8w5?fYZ{Cc@N)_s*8$!tVSK&Eor_s2=LD7C{lnhb zxU)dJeGJ3Q>`JqR{Q5p%Jh@gBU2YHec!l*84h_d${K9reiu)2jbDuY1l5!{MR8C7y z*~9B-^|m#cpY3UPCL!8ixa8F>m-i?;uNXqt_h3Y}T!@C`$B-#yXLvY8m7r2%dh78M z6cZ|hV1>1mJMQkjcW#%~6(@cj(qH+P&rp~OAw9{PjVkiN5QWBfAq&Y8emzCRL-@wK zN*YzssdYOIQn^?77{!$SrEx5K+%a5?Ay%7dv)LLt$AD8bA@jLj|TP(u6hUKZQDV?M0JCuISWxSH-QPL~~YA#HS>}U+4 z?w>2Hmh1Et)*ESLDRs&a)fMj?Cn2}U2%r+!inq3uuqwo^#$Fw9jZU}9jMBge%9DEj z1e1OR*-55jTAgr#o#^>a2Xt13VhWWATdb2rYdxWXU_3QHv_$HejkH4zstwjEb9oSf z?j^!jA!T#=+fTgf_bmu&SbyAm5hE{9i@9G+&E~@BklMIv(Z9`Udc0}+X;0X<)mB3U zeCfrDmqdC--!zJUZsxgK{R{--oGS^1$D5(a8hNo3n6*=uf!K=~wKKsrbI@E;VdC6N z8|t#rOAaFw`|i65#@BTmPn)ZeF0 zCvM^#U0yi>o^K$HOKD0}w8NRj(!qfw>K_b}U~$UKAx<h;oQV>85_=uw)5|Vnb z2!CMVNU>R$*&+#eec7k8Y|m*W`sfw1ssy15D@+ifhK789#JZ(caKz>LLRC~(PH&Es z33rq$nE=4rSp=PDCCfr|nM9M705g6?n}6tki9&*^OT@a9r^abD3m*DU6v9AG_2E-t z=uR+A#{p)e&Xv;X3B=Azqn`PG3)+YSpI;DiD@8;J_=Q2;3qeQcV*5}aoWft6)}#zS zqdn?W&!dN(e*}y1Mh!~-v>7`wb;)w@@r*j~4Pg@Gx{Zp;w1%-aY9}_Ir|4pgw|!)K zDX?;_#@VstC6wQQ^srdD4zOl9k2qRBvGX8b%nM-*sECV7S5J;Ejy?uNHx5 zc*&!XO*9gV{uz0bNgw4#0W;^?pQQrT@2JO>C}CWnX6K!kTs8sW&a2F{(4RRhgQH9#n`I^ z2xjQ~40O3o_W`nzLPa>BzL?7{5S|6rVYaAc%*zkz=0{Q9SkxfPqK-PA6L1;Hj*7We zmoe-W9(uW6i?H1?-?#XScUK+{uD>~TqweLNcxQO#($r0qB?Or&jR*Kt?~OTa;B&zY z=)WqSyX8-Al*1pF8W~92fxCE=tTGh>V8)=843LbIhf)TF8sO<%BHTGdAG*Gbwp&3l z?v%wGH(BQ)naId;${Xrg;NHGH&#lw#@qB*vD!F@roJ2GoQ21`JiRL0JCANk+ThO<^ zQ@V`Uzcdq}r~*h^o}kPA5yhuxtnSjoh#U3Izs>Qd{r{aH{?k4Ee=hR=Tc@T)=dUvk zyVGwS)i=DhWT{nZRG3A#;sIsL<9c+ePKici-}q}_nx!UP_LJn($xWX;yO|kh#z4^@ z^_N`PiQ~ovi>ISC`2EYfi-V(A_FH_nwKetd@bK(c{%yZDZ};bypLBlxp6^fkHuZXY zKA-L`&o>W$_vh0!d;D|lUr+DHo$GRRdN

>&-U#d>!0px~pBk-zUf`kRN=v#f;Ic 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