From e790ae0b05fc750fbbfa296a8ba194e82a194b3b Mon Sep 17 00:00:00 2001 From: xiongxiaoyang Date: Wed, 3 Jun 2020 00:49:46 +0800 Subject: [PATCH] =?UTF-8?q?=E9=9B=86=E6=88=90=E9=98=BF=E9=87=8C=E4=BA=91OS?= =?UTF-8?q?S=EF=BC=8C=E5=A2=9E=E5=8A=A0=E6=96=87=E4=BB=B6=E5=BE=AE?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + ...=> nacos_config_export_20200603004804.zip} | Bin 6345 -> 7016 bytes .../com/java2nb/novel/book/api/BookApi.java | 19 ++++ .../novel/book/controller/api/BookApi.java | 23 +++++ .../book/listener/BookVisitAddListener.java | 5 +- .../novel/book/service/BookService.java | 17 ++++ .../book/service/impl/BookServiceImpl.java | 87 ++++++++++++------ .../novel/common/config/RabbitConfig.java | 5 +- .../java2nb/novel/common/utils/FileUtil.java | 27 ++++-- novel-file/pom.xml | 82 +++++++++++++++++ .../com/java2nb/novel/FileApplication.java | 22 +++++ .../java2nb/novel/config/OssProperties.java | 32 +++++++ .../java2nb/novel/feign/BookFeignClient.java | 17 ++++ .../novel/schedule/CrawlPicTransSchedule.java | 77 ++++++++++++++++ .../java2nb/novel/service/FileService.java | 21 +++++ .../novel/service/impl/FileServiceImpl.java | 70 ++++++++++++++ novel-file/src/main/resources/application.yml | 4 + novel-file/src/main/resources/bootstrap.yml | 13 +++ .../src/main/resources/logback-boot.xml | 64 +++++++++++++ .../search/listener/BookVisitAddListener.java | 5 +- .../novel/search/service/SearchService.java | 1 - pom.xml | 1 + .../02/4e9ae3c8fe054aefaff9f2f8d5488695.jpg | Bin 0 -> 23531 bytes 23 files changed, 551 insertions(+), 42 deletions(-) rename doc/yml/{nacos_config_export_20200602214520.zip => nacos_config_export_20200603004804.zip} (66%) create mode 100644 novel-file/pom.xml create mode 100644 novel-file/src/main/java/com/java2nb/novel/FileApplication.java create mode 100644 novel-file/src/main/java/com/java2nb/novel/config/OssProperties.java create mode 100644 novel-file/src/main/java/com/java2nb/novel/feign/BookFeignClient.java create mode 100644 novel-file/src/main/java/com/java2nb/novel/schedule/CrawlPicTransSchedule.java create mode 100644 novel-file/src/main/java/com/java2nb/novel/service/FileService.java create mode 100644 novel-file/src/main/java/com/java2nb/novel/service/impl/FileServiceImpl.java create mode 100644 novel-file/src/main/resources/application.yml create mode 100644 novel-file/src/main/resources/bootstrap.yml create mode 100644 novel-file/src/main/resources/logback-boot.xml create mode 100644 temp/localPic/2020/06/02/4e9ae3c8fe054aefaff9f2f8d5488695.jpg diff --git a/README.md b/README.md index d4cb028..c4e5528 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ novel-cloud ├── novel-gateway -- 基于Spring Cloud Gateway构建的网关服务 ├── novel-monitor -- 基于Spring Boot Admin构建的监控中心 ├── novel-search -- 基于Elastic Search构建的搜索微服务 +├── novel-file -- 基于阿里云OSS构建的文件微服务 ├── novel-home -- 门户首页微服务 ├── novel-news -- 新闻中心微服务 ├── novel-user -- 用户中心微服务 diff --git a/doc/yml/nacos_config_export_20200602214520.zip b/doc/yml/nacos_config_export_20200603004804.zip similarity index 66% rename from doc/yml/nacos_config_export_20200602214520.zip rename to doc/yml/nacos_config_export_20200603004804.zip index 036063cb611b9fec50d4a61e715ea87871713afd..db4edce9316c4b71a2f3cd66a8975775427484dd 100644 GIT binary patch delta 912 zcmX?U_`-}gz?+#xgn@&DgMo?d@J3#3CT1YL*_!DbBbdR%ask4)&&mjqddz+vEHe2% ztN7+KoM8}YRqneG1}kp|MBN1bvk-L&f?vT#MF`D8Xyg%5W#mS(dGZ8)@%psPoK*e1 z{Ib*>T@X{RGB>9-=%C+i2Z6o6wcSoJuDm3rZn*Jv^lxU{C){SQRn4}Q>#o16GG|2# z%c@BNdJ0^j-BlS!8DFR_AVd_=FjZBTNg7m@a0ZrFTLEm^Muf@ zF2)+SLuZ!q<=EV=2#R8|={kHRy0P`R^@*jL-<%&Z@LBIrH}Spj#MGC|=Zx^u^F}?Nntw+|n&N`!)BW(A4+qr%mEYyzVPwpu0EA#M`gZp^*hW;*F% zeMgss@Y3D?_9P0d?h0Jo#%UD&&1{qBU5B+|+g8S!zP_QbAaiQ?*=5H<4sUp|{cU@{ z+!}T^Jr*S%&i+lYTT5kr?d=gQP1`cH38zyH0qvQYc}`>EmH z+Zw)2cyINf%G%n~O*-uGCY{^ca=zvqUO8!joQGU}-_wh^c5_ZFo?JR{fyzR!7VQ>k z7Eg!6N$ri9dAT$HYv-k^EYQ^ADNL}G5Bb!-`R3iwx&l@8_@@td&Z*N6{Cs(%l!M&= z35y$p_Ak>m+?+4}>GZx+=a#@748f zn^Z~;cwdgNnC(9MZr=I+TfY`>luLUX+n@elBKpkMl%Pu@&UJr3y#BJwBuqPFYZBuh z#sF`24!7#{s|A@D7?_zE7y`T*nM4@isd%!Ts0S#;P40x!SD>`Km={<)M+_pr9m;1B z_W_Fsi@Sm8IpQv0`mwkxnAVnX2h*hz4$&w!1t7aZk^vd$i!v}Ek}_JV4)A7W11aVO M!qY$xw2Omy0BUDphyVZp delta 255 zcmaE1cG8eHz?+#xgn@&DgJIR$LmPRunV5m}W^1N%j9>;2%LNGIJ}VM{Fyu*l^5 ztm2!`aE3voRk`m%7_7V<5Oovy&qCBC2z~_{6(KYWp^-;Km64Z`NrVAz_vC5f9w2p- zA3|vz2?)Pj!V4^Z1j^@^gz#e|eZb listUserCommentByPage(@RequestParam("userId") Long userId,@RequestParam("page") int page, @RequestParam("pageSize") int pageSize); + + /** + * 查询网络图片的小说 + * + * @param localPicPrefix + * @param limit 查询条数 + * @return 返回小说集合 + * */ + @GetMapping("api/book/queryNetworkPicBooks") + List queryNetworkPicBooks(@RequestParam("localPicPrefix") String localPicPrefix,@RequestParam("limit") int limit); + + + /** + * 更新图片路径 + * @param picUrl 图片路径 + * @param bookId 小说ID + */ + @PostMapping("api/book/updateBookPic") + void updateBookPic(@RequestParam("picUrl") String picUrl,@RequestParam("bookId") Long bookId); } diff --git a/novel-book/book-service/src/main/java/com/java2nb/novel/book/controller/api/BookApi.java b/novel-book/book-service/src/main/java/com/java2nb/novel/book/controller/api/BookApi.java index 08e18c7..fbd54be 100644 --- a/novel-book/book-service/src/main/java/com/java2nb/novel/book/controller/api/BookApi.java +++ b/novel-book/book-service/src/main/java/com/java2nb/novel/book/controller/api/BookApi.java @@ -91,4 +91,27 @@ public class BookApi { List listUserCommentByPage(@RequestParam("userId") Long userId,@RequestParam("page") int page, @RequestParam("pageSize") int pageSize){ return bookService.listUserCommentByPage(userId,page,pageSize); } + + /** + * 查询网络图片的小说 + * + * @param localPicPrefix + * @param limit 查询条数 + * @return 返回小说集合 + * */ + @GetMapping("queryNetworkPicBooks") + List queryNetworkPicBooks(@RequestParam("localPicPrefix") String localPicPrefix,@RequestParam("limit") int limit){ + return bookService.queryNetworkPicBooks(localPicPrefix,limit); + } + + /** + * 更新图片路径 + * @param picUrl 图片路径 + * @param bookId 小说ID + */ + @PostMapping("updateBookPic") + void updateBookPic(@RequestParam("picUrl") String picUrl,@RequestParam("bookId") Long bookId){ + bookService.updateBookPic(picUrl,bookId); + } + } diff --git a/novel-book/book-service/src/main/java/com/java2nb/novel/book/listener/BookVisitAddListener.java b/novel-book/book-service/src/main/java/com/java2nb/novel/book/listener/BookVisitAddListener.java index 7a0fb8a..9a7b1c9 100644 --- a/novel-book/book-service/src/main/java/com/java2nb/novel/book/listener/BookVisitAddListener.java +++ b/novel-book/book-service/src/main/java/com/java2nb/novel/book/listener/BookVisitAddListener.java @@ -15,7 +15,10 @@ import org.springframework.stereotype.Component; /** - * @author 11797 + * 消息监听器 + * @author xiongxiaoyang + * @version 1.0 + * @since 2020/6/2 */ @Component @Slf4j diff --git a/novel-book/book-service/src/main/java/com/java2nb/novel/book/service/BookService.java b/novel-book/book-service/src/main/java/com/java2nb/novel/book/service/BookService.java index 1350e02..6381b92 100644 --- a/novel-book/book-service/src/main/java/com/java2nb/novel/book/service/BookService.java +++ b/novel-book/book-service/src/main/java/com/java2nb/novel/book/service/BookService.java @@ -136,4 +136,21 @@ public interface BookService { * @return 评论数据 * */ List listUserCommentByPage(Long userId, int page, int pageSize); + + /** + * 查询网络图片的小说 + * + * @param localPicPrefix + * @param limit 查询条数 + * @return 返回小说集合 + * */ + List queryNetworkPicBooks(String localPicPrefix, Integer limit); + + + /** + * 更新图片路径 + * @param picUrl 图片路径 + * @param bookId 小说ID + */ + void updateBookPic(String picUrl, Long bookId); } diff --git a/novel-book/book-service/src/main/java/com/java2nb/novel/book/service/impl/BookServiceImpl.java b/novel-book/book-service/src/main/java/com/java2nb/novel/book/service/impl/BookServiceImpl.java index 8efe3e1..2585c0e 100644 --- a/novel-book/book-service/src/main/java/com/java2nb/novel/book/service/impl/BookServiceImpl.java +++ b/novel-book/book-service/src/main/java/com/java2nb/novel/book/service/impl/BookServiceImpl.java @@ -30,6 +30,7 @@ import static org.mybatis.dynamic.sql.select.SelectDSL.select; /** * 小说服务接口实现 + * * @author xiongxiaoyang * @version 1.0 * @since 2020/5/28 @@ -63,14 +64,14 @@ public class BookServiceImpl implements BookService { @Override public List queryBookByIds(List ids) { - return bookMapper.selectMany(select(BookDynamicSqlSupport.id,BookDynamicSqlSupport.catId,BookDynamicSqlSupport.catName, - BookDynamicSqlSupport.bookName,BookDynamicSqlSupport.authorName, - BookDynamicSqlSupport.lastIndexId,BookDynamicSqlSupport.lastIndexName,BookDynamicSqlSupport.lastIndexUpdateTime, - BookDynamicSqlSupport.picUrl,BookDynamicSqlSupport.bookDesc,BookDynamicSqlSupport.score) + return bookMapper.selectMany(select(BookDynamicSqlSupport.id, BookDynamicSqlSupport.catId, BookDynamicSqlSupport.catName, + BookDynamicSqlSupport.bookName, BookDynamicSqlSupport.authorName, + BookDynamicSqlSupport.lastIndexId, BookDynamicSqlSupport.lastIndexName, BookDynamicSqlSupport.lastIndexUpdateTime, + BookDynamicSqlSupport.picUrl, BookDynamicSqlSupport.bookDesc, BookDynamicSqlSupport.score) .from(book) - .where(BookDynamicSqlSupport.id,isIn(ids)) - .build() - .render(RenderingStrategies.MYBATIS3) + .where(BookDynamicSqlSupport.id, isIn(ids)) + .build() + .render(RenderingStrategies.MYBATIS3) ); } @@ -104,12 +105,12 @@ public class BookServiceImpl implements BookService { BookDynamicSqlSupport.authorId, BookDynamicSqlSupport.authorName, BookDynamicSqlSupport.picUrl, BookDynamicSqlSupport.bookDesc, BookDynamicSqlSupport.wordCount, BookDynamicSqlSupport.lastIndexUpdateTime) - .from(book) - .where(BookDynamicSqlSupport.wordCount, isGreaterThan(0)) - .orderBy(sortSpecification) - .limit(limit) - .build() - .render(RenderingStrategies.MYBATIS3); + .from(book) + .where(BookDynamicSqlSupport.wordCount, isGreaterThan(0)) + .orderBy(sortSpecification) + .limit(limit) + .build() + .render(RenderingStrategies.MYBATIS3); return bookMapper.selectMany(selectStatement); } @@ -132,12 +133,12 @@ public class BookServiceImpl implements BookService { .build() .render(RenderingStrategies.MYBATIS3); List books = bookMapper.selectMany(selectStatement); - return books.size() >0 ? books.get(0) : null; + return books.size() > 0 ? books.get(0) : null; } @Override public void addVisitCount(Long bookId, int addCount) { - bookMapper.addVisitCount(bookId,addCount); + bookMapper.addVisitCount(bookId, addCount); } @@ -173,12 +174,12 @@ public class BookServiceImpl implements BookService { //分页查询小说评论数据 PageHelper.startPage(page, pageSize); List bookCommentList = bookCommentMapper.selectMany( - select(BookCommentDynamicSqlSupport.id,BookCommentDynamicSqlSupport.bookId, + select(BookCommentDynamicSqlSupport.id, BookCommentDynamicSqlSupport.bookId, BookCommentDynamicSqlSupport.createUserId, - BookCommentDynamicSqlSupport.commentContent,BookCommentDynamicSqlSupport.replyCount, + BookCommentDynamicSqlSupport.commentContent, BookCommentDynamicSqlSupport.replyCount, BookCommentDynamicSqlSupport.createTime) .from(BookCommentDynamicSqlSupport.bookComment) - .where(BookCommentDynamicSqlSupport.bookId,isEqualTo(bookId)) + .where(BookCommentDynamicSqlSupport.bookId, isEqualTo(bookId)) .orderBy(BookCommentDynamicSqlSupport.createTime.descending()) .build() .render(RenderingStrategies.MYBATIS3)); @@ -190,11 +191,11 @@ public class BookServiceImpl implements BookService { //将评论数据和评论人数据关联起来 List resultList = new ArrayList<>(bookCommentList.size()); - for(BookComment bookComment : bookCommentList){ + for (BookComment bookComment : bookCommentList) { BookCommentVO bookCommentVO = new BookCommentVO(); - BeanUtils.copyProperties(bookComment,bookCommentVO); + BeanUtils.copyProperties(bookComment, bookCommentVO); User user = userMap.get(bookComment.getCreateUserId()); - if(user != null){ + if (user != null) { bookCommentVO.setCreateUserName(user.getUsername()); bookCommentVO.setCreateUserPhoto(user.getUserPhoto()); } @@ -260,9 +261,9 @@ public class BookServiceImpl implements BookService { .render(RenderingStrategies.MYBATIS3); List list = bookIndexMapper.selectMany(selectStatement); if (list.size() == 0) { - result.put("preBookIndexId",0L); + result.put("preBookIndexId", 0L); } else { - result.put("preBookIndexId",list.get(0).getId()); + result.put("preBookIndexId", list.get(0).getId()); } selectStatement = select(BookIndexDynamicSqlSupport.id) @@ -275,9 +276,9 @@ public class BookServiceImpl implements BookService { .render(RenderingStrategies.MYBATIS3); list = bookIndexMapper.selectMany(selectStatement); if (list.size() == 0) { - result.put("nextBookIndexId",0L); + result.put("nextBookIndexId", 0L); } else { - result.put("nextBookIndexId",list.get(0).getId()); + result.put("nextBookIndexId", list.get(0).getId()); } @@ -310,14 +311,44 @@ public class BookServiceImpl implements BookService { public List listUserCommentByPage(Long userId, int page, int pageSize) { PageHelper.startPage(page, pageSize); return bookCommentMapper.selectMany( - select(BookCommentDynamicSqlSupport.id,BookCommentDynamicSqlSupport.bookId, + select(BookCommentDynamicSqlSupport.id, BookCommentDynamicSqlSupport.bookId, BookCommentDynamicSqlSupport.createUserId, - BookCommentDynamicSqlSupport.commentContent,BookCommentDynamicSqlSupport.replyCount, + BookCommentDynamicSqlSupport.commentContent, BookCommentDynamicSqlSupport.replyCount, BookCommentDynamicSqlSupport.createTime) .from(BookCommentDynamicSqlSupport.bookComment) - .where(BookCommentDynamicSqlSupport.createUserId,isEqualTo(userId)) + .where(BookCommentDynamicSqlSupport.createUserId, isEqualTo(userId)) .orderBy(BookCommentDynamicSqlSupport.createTime.descending()) .build() .render(RenderingStrategies.MYBATIS3)); } + + @Override + public List queryNetworkPicBooks(String localPicPrefix, Integer limit) { + + + return bookMapper.selectMany( + select(BookDynamicSqlSupport.id, BookDynamicSqlSupport.picUrl) + .from(book) + .where(BookDynamicSqlSupport.picUrl, isLike("http%")) + .and(BookDynamicSqlSupport.picUrl, isNotLike(localPicPrefix)) + .limit(limit) + .build() + .render(RenderingStrategies.MYBATIS3)); + } + + @Override + public void updateBookPic(String picUrl, Long bookId) { + + bookMapper.update(update(book) + .set(BookDynamicSqlSupport.picUrl) + .equalTo(picUrl) + .set(BookDynamicSqlSupport.updateTime) + .equalTo(new Date()) + .where(BookDynamicSqlSupport.id, isEqualTo(bookId)) + .build() + .render(RenderingStrategies.MYBATIS3)); + + } + + } diff --git a/novel-common/src/main/java/com/java2nb/novel/common/config/RabbitConfig.java b/novel-common/src/main/java/com/java2nb/novel/common/config/RabbitConfig.java index d66decd..5acb1a9 100644 --- a/novel-common/src/main/java/com/java2nb/novel/common/config/RabbitConfig.java +++ b/novel-common/src/main/java/com/java2nb/novel/common/config/RabbitConfig.java @@ -9,7 +9,10 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** - * @author 11797 + * rabbitmq配置类 + * @author xiongxiaoyang + * @version 1.0 + * @since 2020/6/2 */ @Configuration @ConditionalOnProperty(prefix = "spring.rabbitmq", name = "host", matchIfMissing = false) diff --git a/novel-common/src/main/java/com/java2nb/novel/common/utils/FileUtil.java b/novel-common/src/main/java/com/java2nb/novel/common/utils/FileUtil.java index a0a57d3..091adec 100644 --- a/novel-common/src/main/java/com/java2nb/novel/common/utils/FileUtil.java +++ b/novel-common/src/main/java/com/java2nb/novel/common/utils/FileUtil.java @@ -9,6 +9,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; +import javax.imageio.ImageIO; import java.io.*; import java.util.Date; import java.util.Objects; @@ -23,23 +24,20 @@ import java.util.Objects; public class FileUtil { /** - * 网络图片转本地 + * 网络图片转临时文件 * */ - public static String network2Local(String picSrc,String picSavePath,String visitPrefix) { + public static File networkPic2Temp(String picSrc) { + File picFile = null; InputStream input = null; OutputStream out = null; try { - //本地图片保存 HttpHeaders headers = new HttpHeaders(); HttpEntity requestEntity = new HttpEntity<>(null, headers); ResponseEntity resEntity = RestTemplateUtil.getInstance(Charsets.ISO_8859_1.name()).exchange(picSrc, HttpMethod.GET, requestEntity, Resource.class); input = Objects.requireNonNull(resEntity.getBody()).getInputStream(); - Date currentDate = new Date(); - picSrc = visitPrefix + DateUtils.formatDate(currentDate, "yyyy") + "/" + DateUtils.formatDate(currentDate, "MM") + "/" + DateUtils.formatDate(currentDate, "dd") + "/" - + UUIDUtil.getUUID32() - + picSrc.substring(picSrc.lastIndexOf(".")); - File picFile = new File(picSavePath + picSrc); + picFile = File.createTempFile("temp",picSrc.substring(picSrc.lastIndexOf("."))); File parentFile = picFile.getParentFile(); + if (!parentFile.exists()) { parentFile.mkdirs(); } @@ -49,10 +47,15 @@ public class FileUtil { out.write(b, 0, n); } + out.flush(); + if( ImageIO.read(picFile) == null){ + return null; + } + }catch (Exception e){ log.error(e.getMessage(),e); - picSrc = "/images/default.gif"; + return null; }finally { if(input != null){ try { @@ -69,11 +72,15 @@ public class FileUtil { } } } + if(picFile != null) { + //程序退出时删除临时文件 + picFile.deleteOnExit(); + } } - return picSrc; + return picFile; } diff --git a/novel-file/pom.xml b/novel-file/pom.xml new file mode 100644 index 0000000..0ae59af --- /dev/null +++ b/novel-file/pom.xml @@ -0,0 +1,82 @@ + + + + novel-cloud + com.java2nb.novel + 1.0.0 + + 4.0.0 + + novel-file + + + + com.java2nb.novel + book-api + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + + + mysql + mysql-connector-java + + + org.mybatis.dynamic-sql + mybatis-dynamic-sql + + + + + + + org.springframework.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + org.springframework.cloud + spring-cloud-starter-alibaba-nacos-config + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + org.springframework.cloud + spring-cloud-netflix-hystrix + + + + + com.aliyun.oss + aliyun-sdk-oss + ${aliyun-sdk-oss.version} + + + commons-fileupload + commons-fileupload + ${commons-fileupload.version} + + + + + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + \ No newline at end of file diff --git a/novel-file/src/main/java/com/java2nb/novel/FileApplication.java b/novel-file/src/main/java/com/java2nb/novel/FileApplication.java new file mode 100644 index 0000000..30f3404 --- /dev/null +++ b/novel-file/src/main/java/com/java2nb/novel/FileApplication.java @@ -0,0 +1,22 @@ +package com.java2nb.novel; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * 文件微服务启动器 + * @author xiongxiaoyang + * @version 1.0 + * @since 2020/6/2 + */ +@SpringBootApplication +@EnableFeignClients +@EnableScheduling +public class FileApplication { + + public static void main(String[] args) { + SpringApplication.run(FileApplication.class); + } +} diff --git a/novel-file/src/main/java/com/java2nb/novel/config/OssProperties.java b/novel-file/src/main/java/com/java2nb/novel/config/OssProperties.java new file mode 100644 index 0000000..024ddbc --- /dev/null +++ b/novel-file/src/main/java/com/java2nb/novel/config/OssProperties.java @@ -0,0 +1,32 @@ +package com.java2nb.novel.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * OSS配置 + * + * @author xiongxiaoyang + * @version 1.0 + * @since 2020/6/2 + */ +@Data +@Component +@ConfigurationProperties(prefix="novel.file") +public class OssProperties { + + private String endpoint; + + private String keyId; + + private String keySecret; + + private String fileHost; + + private String bucketName; + + private String webUrl; + + +} diff --git a/novel-file/src/main/java/com/java2nb/novel/feign/BookFeignClient.java b/novel-file/src/main/java/com/java2nb/novel/feign/BookFeignClient.java new file mode 100644 index 0000000..169aeb3 --- /dev/null +++ b/novel-file/src/main/java/com/java2nb/novel/feign/BookFeignClient.java @@ -0,0 +1,17 @@ +package com.java2nb.novel.feign; + + +import com.java2nb.novel.book.api.BookApi; +import org.springframework.cloud.openfeign.FeignClient; + +/** + * 小说服务Feign客户端 + * @author xiongxiaoyang + * @version 1.0 + * @since 2020/5/28 + */ +@FeignClient(value = "book-service") +public interface BookFeignClient extends BookApi { + + +} diff --git a/novel-file/src/main/java/com/java2nb/novel/schedule/CrawlPicTransSchedule.java b/novel-file/src/main/java/com/java2nb/novel/schedule/CrawlPicTransSchedule.java new file mode 100644 index 0000000..04f79e2 --- /dev/null +++ b/novel-file/src/main/java/com/java2nb/novel/schedule/CrawlPicTransSchedule.java @@ -0,0 +1,77 @@ +package com.java2nb.novel.schedule; + +import com.java2nb.novel.book.entity.Book; +import com.java2nb.novel.common.utils.Constants; +import com.java2nb.novel.common.utils.FileUtil; +import com.java2nb.novel.feign.BookFeignClient; +import com.java2nb.novel.service.FileService; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.util.List; + + +/** + * 将爬取的网络图片转存到OSS任务 + * + * @author xiongxiaoyang + * @version 1.0 + * @since 2020/6/2 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class CrawlPicTransSchedule { + + private final BookFeignClient bookFeignClient; + + private final RedissonClient redissonClient; + + private final FileService fileService; + + + /** + * 10分钟转一次 + */ + @Scheduled(fixedRate = 1000 * 60 * 10) + @SneakyThrows + public void trans() { + + RLock lock = redissonClient.getLock("crawlPicTrans"); + lock.lock(); + + try { + + List networkPicBooks = bookFeignClient.queryNetworkPicBooks(Constants.LOCAL_PIC_PREFIX, 100); + for (Book book : networkPicBooks) { + + String filePath = "/images/default.gif"; + File file = FileUtil.networkPic2Temp(book.getPicUrl()); + if (file != null) { + + filePath = fileService.uploadFile(file); + + + } + + + bookFeignClient.updateBookPic(filePath, book.getId()); + //3秒钟转化一张图片,10分钟转化200张 + Thread.sleep(3000); + } + + } catch (Exception e) { + log.error(e.getMessage(), e); + } + + lock.unlock(); + + + } +} diff --git a/novel-file/src/main/java/com/java2nb/novel/service/FileService.java b/novel-file/src/main/java/com/java2nb/novel/service/FileService.java new file mode 100644 index 0000000..bb73808 --- /dev/null +++ b/novel-file/src/main/java/com/java2nb/novel/service/FileService.java @@ -0,0 +1,21 @@ +package com.java2nb.novel.service; + + +import java.io.File; + +/** + * 文件服务接口 + * @author xiongxiaoyang + * @version 1.0 + * @since 2020/6/2 + */ +public interface FileService { + + /** + * 上传文件 + * @param file 上传文件 + * @return 上传地址 + * */ + String uploadFile(File file); + +} diff --git a/novel-file/src/main/java/com/java2nb/novel/service/impl/FileServiceImpl.java b/novel-file/src/main/java/com/java2nb/novel/service/impl/FileServiceImpl.java new file mode 100644 index 0000000..00c8435 --- /dev/null +++ b/novel-file/src/main/java/com/java2nb/novel/service/impl/FileServiceImpl.java @@ -0,0 +1,70 @@ +package com.java2nb.novel.service.impl; + +import com.aliyun.oss.OSSClient; +import com.aliyun.oss.model.CannedAccessControlList; +import com.aliyun.oss.model.CreateBucketRequest; +import com.aliyun.oss.model.PutObjectRequest; +import com.aliyun.oss.model.PutObjectResult; +import com.java2nb.novel.common.utils.Constants; +import com.java2nb.novel.common.utils.FileUtil; +import com.java2nb.novel.common.utils.UUIDUtil; +import com.java2nb.novel.config.OssProperties; +import com.java2nb.novel.service.FileService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.io.File; + +/** + * 文件服务接口实现类 + * @author xiongxiaoyang + * @version 1.0 + * @since 2020/6/2 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class FileServiceImpl implements FileService { + + private final OssProperties ossProperties; + + @Override + public String uploadFile(File file) { + + String fileName = file.getName(); + String filePath = + Constants.LOCAL_PIC_PREFIX + UUIDUtil.getUUID32() + fileName.substring(fileName.lastIndexOf(".")); + + filePath = filePath.replaceFirst("/",""); + + OSSClient ossClient = new OSSClient(ossProperties.getEndpoint(), ossProperties.getKeyId(), ossProperties.getKeySecret()); + try { + //容器不存在,就创建 + if (!ossClient.doesBucketExist(ossProperties.getBucketName())) { + ossClient.createBucket(ossProperties.getBucketName()); + CreateBucketRequest createBucketRequest = new CreateBucketRequest(ossProperties.getBucketName()); + createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead); + ossClient.createBucket(createBucketRequest); + } + //上传文件 + PutObjectResult result = ossClient.putObject(new PutObjectRequest(ossProperties.getBucketName(), filePath, file)); + //设置权限 这里是公开读 + ossClient.setBucketAcl(ossProperties.getBucketName(), CannedAccessControlList.PublicRead); + + if(result != null) { + return ossProperties.getWebUrl() + "/" + filePath; + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + //关闭 + ossClient.shutdown(); + } + + return "/images/default.gif"; + } + + +} diff --git a/novel-file/src/main/resources/application.yml b/novel-file/src/main/resources/application.yml new file mode 100644 index 0000000..26ec507 --- /dev/null +++ b/novel-file/src/main/resources/application.yml @@ -0,0 +1,4 @@ +spring: + profiles: + include: [common] + diff --git a/novel-file/src/main/resources/bootstrap.yml b/novel-file/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..28edfed --- /dev/null +++ b/novel-file/src/main/resources/bootstrap.yml @@ -0,0 +1,13 @@ +spring: + application: + name: novel-file + profiles: + active: dev + + cloud: + nacos: + config: + ext‐config[0]: + data‐id: novel-redis.yml + group: novel-common + refresh: true \ No newline at end of file diff --git a/novel-file/src/main/resources/logback-boot.xml b/novel-file/src/main/resources/logback-boot.xml new file mode 100644 index 0000000..13d435a --- /dev/null +++ b/novel-file/src/main/resources/logback-boot.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + + UTF-8 + + + + + + + + + logs/novel-file.log + + + + + + logs/debug.%d.%i.log + + 30 + + + 10MB + + + + + + %d %p (%file:%line\)- %m%n + + + UTF-8 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/novel-search/src/main/java/com/java2nb/novel/search/listener/BookVisitAddListener.java b/novel-search/src/main/java/com/java2nb/novel/search/listener/BookVisitAddListener.java index e7b6136..d952397 100644 --- a/novel-search/src/main/java/com/java2nb/novel/search/listener/BookVisitAddListener.java +++ b/novel-search/src/main/java/com/java2nb/novel/search/listener/BookVisitAddListener.java @@ -16,7 +16,10 @@ import org.springframework.stereotype.Component; /** - * @author 11797 + * 消息监听器 + * @author xiongxiaoyang + * @version 1.0 + * @since 2020/6/2 */ @Component @Slf4j diff --git a/novel-search/src/main/java/com/java2nb/novel/search/service/SearchService.java b/novel-search/src/main/java/com/java2nb/novel/search/service/SearchService.java index 7aaf3ff..9d73d2f 100644 --- a/novel-search/src/main/java/com/java2nb/novel/search/service/SearchService.java +++ b/novel-search/src/main/java/com/java2nb/novel/search/service/SearchService.java @@ -1,7 +1,6 @@ package com.java2nb.novel.search.service; -import com.github.pagehelper.PageInfo; import com.java2nb.novel.book.entity.Book; import com.java2nb.novel.common.bean.PageBean; import com.java2nb.novel.search.vo.EsBookVO; diff --git a/pom.xml b/pom.xml index 3cde299..f6e3228 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,7 @@ novel-pay novel-search novel-author + novel-file pom diff --git a/temp/localPic/2020/06/02/4e9ae3c8fe054aefaff9f2f8d5488695.jpg b/temp/localPic/2020/06/02/4e9ae3c8fe054aefaff9f2f8d5488695.jpg new file mode 100644 index 0000000000000000000000000000000000000000..32c22996daf25963968eadae874359b1deda4415 GIT binary patch literal 23531 zcmbSycT`i))@W$bQK>4u6MApbdjg?^-lYT$5IRT^q*rMn^xhK?q!Um?nshI_SO`L+=oaCH2l$9vS z6awV~5nc$Gp95PU!rjAHK2VY4U)<$y^Z&dS;b8k0il3Vzhw49K*(@N2Z0epqFg7V+ zX(1;u30XE7Ibks=IazT@K{jzwF=-J|IT0~QAyF}TQ8{^0F}8mL$E`S01s`V@d8mfg zzop$iDRTU~UeBLD7k(}w?CJAVL`+UjPDE5(L|k0x7DLE4$ivSeP{_lV^Itsvi-`uz z*U1O&q@-P>U`~#5u>bJ+pXgO(BsA5er8H%v zG^NGFG-cH^)TFf}R7KS_)I}vUwWR+;3-a*wbMSD2{YN(ZR`$PXHU6)(^6EY?2R~0A z6Hia~|CoZ|GfzKH-)Ej)Z0hR&biE84pE=ya+4H$C@V|PBRN=pBt^xCb2f&=Qd^{0s z|7vS_`2V1XxReY`0tOQk5_c4L7IJZvgbB%sNjV5f$;mm2IygExOFGDM{F~nS|F;cA z{xL&R1(APj=Krvve;Mb$Ufx>gKLbPLzemPDPyao-U>?qjyw7=+m2MUR>HvH^ynpV0 z0|61iKX8YTkbsEz4l(gRiG-A#l!SzggqWC&l8lU;;ueTWsqRuzQ2mqthsi(W|4`lD z6ePqX|6u&T!OaK2eGIOQn|=Vx?W}Q!;C5d5Zy+SPbE^&jj{u+S zwp{bxKRF@-{C{anKtKe*!zUmlx(~QROwCS0BgUahN^9gm$0_cu7LiUy&jl@IkZA1r z{QZ}tK#uhPaITA$C~>C_yXZg2pKTTJ}>1or{TfT|Z5jeuYV?p*$q z3VAY<%(Ul0JgZwh_-jRqS1bDo8O;Ii_e)dyr)-+p8$-B9AVWu6>GrtGXLs^wlo@+et%hWCZ4 zOe>b-YIQa?++dc_=sld!JJLo6K!F665Z4$%U5SAvwgu!du4;oa%>4)k!n?;TDu-dR zr!G5W;tvG8s4nW8bl5_t0LLCdH-IXxtmYp=)|bg9n2(v7{&Q=>dVd;W_${=8*)z+7 z9CJnnj37gn1+8sm?VvMDDPjY3n)nGQr!&b!Q;D!5TDA~lta=8?+*U_)(wl64c9fIp z)v9M!_uwVawXO+~o4I(r$Y31tS+4ivcQb_yl^IjXZTKi(8Z|YE3~xS*Qbk~2^YmBS zqFHH-30upIFpkl9*eViQiviJd^hZ~J+dl1io${)cYdngdUVUt0pvE9H9yWwNP-s5Lrp%d z-Q;X(hrbcZX0lkSY^zK);!Oc4{H41r2&$QO>@S2=DB$qFx8W9z(Z7G%?nNS18bO5Q z3qg(VDSeW~wGaiUjwic~F+!VX2Ug1m0re$s!a%$`#a?1g&lA<|O7N+bLot-}BWqHu zn_QLc;Hc2cPFfJwfb7oJRrj3d2?lSUeDXT8rPW=iIsNy6Vn9=Nn$gh-_+9@{NN0Kv zJLwC?3Iq~-sNL$)7+I@~4 z5i|6|n6t9cvf((R)Q-ARoUIncQ>R+^>M*I*-8(z(m-hEZ)t@tG>_Bz2~9NS@;6tnrEJuVBR|h7U0t2(U|bZiTc> z9DSYcUVmUdlD2BD`u??HSBll<>O#s5fWoHSe(sU_mtv7py20()IBQJSTDEiFRZAV7 zj_E`lm0(gu48lIiV(~z8vYb_5sGU<2c(OuQbOeHlR#VZLGsbHNq%?Th%sO$h*~PYLyn5$qvU_F@cElzbmJ; z`h*n^LoI`K^^}lbaA3-|e6IUjMTo7x-Y{0&lNzJVlV&>fm6;>VLU2x3pPa+0Fz6ZF zwf(YRd>6##{SV8FzgqHx@!$Oiu$5^U zt-!5k(n{L%#q@1=eOiSzp=}F-6{3fY4TL}?IhMe+ROJa$|8A68es5MWp?+>n$5|~| zy-(cz%I5NO4}IsQbIPV!)f>PslcP#L3k;RQ?{L!YKL)b<^Hdm>T6pKqJt!TGn6Tu= z8Cm_IH`MK}C-GIS4gJqS;?y~B-eAd*`X3Cvh2HFCwvP=vi|cqdb_l=xG4a<<8{<{_ z;YjTQY4U{04@vV61p7DV@p}tM?mSMuDx|!pQKRA4lr`b76$0{BJ0ml`mjlV1DEImv zSnagRE{<&(GeqxZ^>Wh%^n10+n#fS+Tdmvx7XBUzbgKNgDCB6TF_KuE+4M=Vs;cQS zLgl@Fz9xUK#3Hq|m9G+xXsYBH(7;Xw#1O>KTW0>9AElgnWbg+QuMUG7slQ5!a;y3f z`QqDlT(#&^s{C48A)}*3oe5fmPKEJYe_lpQ;1Szgm8W%F+pYmoOVYm1*Qo7dr=c9$ zd!5s^Z-PeZW{YagBxWs#?i-nhzKk0{?HIE6ffFrZ4doY5SuVR>a+wa~2woe<&hFWy|1@Ya~?Gq#si^YYSNm2r_k`fO|A ztiTh)t35BT6F8yM?M$Cr=U337j{f*f+~lH8T9-mPei%#=m}XMxfo0$-60Qwfu<_HJ zJ#Z|Uu4zu27B1KG@wYH$@+f5X;i~ajh#`|tJR_npLQUt>;;UKPP12UV)(q9fTnps8 zyj!t;<{sNizPud4nMQXYr}S-Z`xfiHb_!AM#wi#6MA*zI3SDJxI7{6N?m8p&+El=Q{9BPv+?r|!F)&Ma8!rh{OocjtZi^a&w;bVaaNPgl;V zCLJ+T+TI55o#nxqD`6!s4hJ9DuU{ldjX(D=c5idgpkgqb`Z%9^0)#VkxH%@vv}UJCuXO&)QN_xfQ;>^@ebQw@3YJntUe zJEGUk$c4Sd0r@2@^fh4Rd(?XyQ5SUqSA~5{_;sQzG!m2TrVqbWLiCgKf4@h6v=7kW zNtdVnNXK7j>=d<_wmi5EO_}ZxP&MBn7;BqX-sf>cfHOoyHb&@+*E=)fDZ6aK`xB$a zupTnvi@QQ4MM50{t*MiaE#F;$etA1%51-UNvr5yf3(2(m5LO0KT1V$wP_UXmJ1|3& zhjAokwPw;U{$)eTc6DVsxgm+VU%-gB2IBp282hzD{rpSl&^C9o8*T8&A9~&&LG)+z*A}M26q(xI zZEKYhv<;Mt-yefc*U!x_sJvh*BPTK&Ef12|*Rhq{WQf9c1qtvs7+#XbW>}m6Nl*V&J$1FS zbduW`t0Vt4R$2MbF}wPqY1>rR)HoDm=wJ1^h{=QI&{|r_qylYqTBX#+qmn;k>q6^a zW#URPceD=s$J9 zEaegY+hwns`J&ge1qC}(UyzlSD(+x~!?O$TlrO$OQwa^ypM9x3x}uA^shF0utSOKV z{|#WY0N?}k9|)L2RjvLor~~qT_@xwe!CJ-oB2_-Al9^}zDP&L1e5{qQ*#O2H5$WTiCuUsg$gMyjzgRqA!khR_cGjod~O^55iZ6gnlQP( zV#{ngld7#jSD8<;;Pt^2WGQ7p2heD};_Cn}%1eQ^mQumez~yRR*gbhlDcF)<1`9?t zlnLJ5Ath@<>_@%o$z6(5M~}ni%ZAhXN=F6v)0cl|QWJBtYp`CsABKbLt^H^`bC58T zc?qnYi*F*79%YSGON1mx#;zJ1DXq~=YdVN%Ua*auHtmWojXYAxqShwYw3avP1srbu zvPd|*;@ysC$ig6ZTOa)9ccU85uuO{=%Q47&zY_%^Ol52rVq@)AuyG9amn$frx{#{P z^BmFEdOmahO78Bvr8vu~jT#fMlwvj+9ys25%6xlenT&~##x`>4wd|yk8{~Ul#(rl> zW`JezvkvASEGlZ~l$2-brBU3Q@^R6ZM$U?Q>IgZDH=Wj%rMjfXZ)9881;6EUyDS~I zgllKS9s{-fUw0Y4*7A^`%4{~!1%Lg;CIjQMRVp(9oox;ue&VVI>5eYTxfX#ZETi@9 z?1m?JTE)=pVE;HyZja^w>|>@-iv0WqkM;e5p5B_ZJVRgGri_;B(qn`WmP*`l-bMZV zDf3@8`(24kUw7ttdl(P;lat^jD0fSEHhzs~q zT5kaPu1sz{gg&8-_VU@xVm?;r&z8C(qKgP+sZ4a;Kyz!q?3dL+tacJ3*Ijqz-;ltX zljTgmVW0BO<$zqhC?vVj7hTfC?i?|bs(e+Jj3tEDSLr3(QdbEQPj&-ub*dY!fG#wu z+2+YJ}*mRM>SI;99ESRzYngp4>48XzKSONN!zN}=yxqhu2WQ^$u$R; zccN@ny~}IbEv!JRJ17gax_$3~J-bj6^z`u!z#dna%Q5)$vLhWcRME0lPfj5JbnG1@ zZW|(xS`}I+x&bJtUe`*z>(ZNEmKC4;fq6>2o0o0s-M>d3@Wu$rfexq4IP$XQEWQEw zo((d>dZ^*zt0t>6?M!@D#)u)1Avyoq*5V8)3-NqL+&9qiSNrLun%E81E2uiDA2;4( zd)U*6p>}|;$BX>MR}wx)GqP{Ey;@Te$TMjknuv z!&Fwd$erJ-^gFc`Gg}>XwW3}j*4HoHzAfY*iB6@f-PS-Kd;=JU#w7&cybSJm_9Y-rCiMV41JXRfbS0;Rv&igf4%5$y zY)5d+D|{PjU{&?-+<(>XT4Z&+EZW5|@m6=PGI68hTx-A+-m zGUv`JaJqKJb=M@G;zj=NqA7Ovc08}xNgQ;+$O;7_IPnSUkxuJL{ObN>&b4Viw8^dcYwTd6^azKB0Q>puSIG`QNNesJ|R9{PMCO%SO<5aB@-{?P$_PcPdp z1Dt2}Mg8p=SJ~e>kiskGopH0218{f^G)WJHszUYndaI~CXFLvVhav`< zk_YdI`9&ReGKv@$`>5BqF$V}l6E6UX13@gAQJpKv@9>4Z-8oz7+J5X!IkZ%I==tmU z30hQOzw59l?7P!k9{vmuszT$0b&DbiXzQ!dUrJBYP98&6W+Yk~jJ>*hzjpykR22@8 zOU~q8y5YVF59baH&2lK2r(yCDyaq>fOlG(PAlO#R$+gbp`O>$S0bbzbh{pisYRVfx z@1^?7MK0VdZKe5)dta91cq@UMgdry3gvR@dgAV9mWAE1eLPWm()3bT~Hxk!QvF*HK zdAn2gPwrfXlR{Bhkz`o?kM|}?u?;%->uYHgIG_O@SJSDDKQ_oNN}{dLR6Kiqbje1r zvlaNkM8uyL6^Tc`d`dvJ45_@9$YmZ*k-hZwb;e3cpO#>_GvDLvz0gU7(FdHKX^6Eu zQZ~td-unPwp{Hpys}oWicKX1tvhR#90%X+iRh1$0OcYd;ud#)u^H*_nTc+$I8lcK{ z=&DJU;Z8bUBbJe?a=3KD5kQ>ol%(Hh>at#jFmRE6wiC1Y74=&h?o~1vG+;^MJPfUR zmtXwYnEM%T(&^t1ZPc^!7hEs;W$rE3&DH3eH~{ZjR+QrJcY-xhxG5`!6>=i`B)p*a zPP29DW0}E`mNC*8rBY+HM`RXG@5*w1bOzzEkC%PLCvKye0ft`fgUe+{iabU7_0&GD zlrmEZ9$fH74Jtzwm9!ZnbcS05WIj=DY%Ws@o2tg|`fK1XCSzyg4G5{rkVJ7zNrA+7 z->6uXtB+bO+xj@^ZJf08nE6&1mq`*j08dfT1FBufQIRY%6Q~Y7wgDRLHF1m!u5!o( zhi+3+zdtN(v zPoOXV^!c3!{2bd5wxJL{&sXO*HL2bKpq0p+TBwm*Qs za%abJw&S5==WgQTdX*AwW#kDk5n@^uARheC-)13%vG^rPp{1>^k%h)ZQhr5CU4kmT zugePHvLfYf>qim>Dnn65jSEEf!*R%TQ-!W%!57nrY}$j#Y%UpoV}wu(N(ApO+aaKQ@wX~Tt6S|qPt*6O`AOcM22HDbcs4AD5atbSp4?X5!F?_2R1Ek&`- zyw}f9R^GCh=qmYaH?5rpMvg971}+7ecU%W<=fEEZ70hLe-|h$eprqV6;7iWp_`tQS z);suZaGo4|#~`_CX88Wr5JF1E8kCO10=wGEz5+;@7Lr0m6UTu;S8pv)!{(z^*4|mI z8J#<1Y+NDN8855pkNaw3q4l2_9^~dp(aJGPsF}pky^|g>%m23H>FXA__Apx1@|iW( zy8G8R|E~At4`VKS-yZa$@`5ymjr(sEE zZr$AV8ZI~P!?sa?t z8Mw+Azs6dE-@Gx0@3rO}gH5d~n!i*H)w0`GuT^q7QGp?IUuU@p8B`JM8gy-9OzzMh z2u9=L?=Y1cz*C%MR_%7^kEx?;>rZ((Olt1dne2ivFn{Wjj?Eyo+{N_|Tk_ahbyG(Z zmtkk21X0jJRn> z;59Q})p~GyzECu;500OVU7F`=keTReUvTVUa)rX4t2)AX@~wR!LZ5*JtOjn&6iC9& zZ@p617flx%v7D^+fHrHoJ~NK@-+sDLA&+Z&&CU*_=ebnVt^T-(u?fX{Hu7}i(Lvpg z4Sz+jwc$e(y9u`=Zds1#$mmO1E`#c5R?>y%M0)_jk&g3+tRN0!7u6)u=KJ#xLcx4K zMMnqExp;^8jj#$d( z?xryjQb@rhedjJw&$m~l&u6Ri;DyOVX^baw9>mGJ?cEi49(Ra#d2)wFX#aALd1~kd z1(owp)aw>xwEEMtJ5-;)-d$PYHAy6&w;m$i>%V z$fHM3Dy2J`6CryLY=6O@+!@Z#o!Cs|kJmiTlu{)%RSUoL4Ouwc+#=iiVp%y)DV{FVOQ`1HD2ADS{VZ&RgS6}L8gH6SPE zcgi4Z5nl9d46_}X78T|S3W%jAoOP5V>Y`)wy|EXAKPcf?vL2S%7gsl-gxm7oT%*!&n&F&jIlAK+Cf|L3W@*A|%zCC_t*js} zn(vwEPy>5R8l;{$1q2-HQr9wmet7?3IYf9AqQ1@yKve;`cvkUsPha%k~gV`{d``&a@bPhEr$XCaW2 zLa+H2JtN)oeeUL`DKM(SasBR18*VND zvWVuQ9+GglIY^eQf~kLtVuwES<&m);M}Dd7xywa|Ih$bwConKFGAySi{mLy+ zQJXvP@eZaNp1s>Ab?YuE^QcR0Hy(|Pd9*q`Dw?`4v>}bhb0GB5OlV+8iRz&+rj_@w zGsSmoW78?m%<@H36-uoj%r%-v1+tgPO&wK{21$!JX+B1NxdAvA(r#F}wLVP!SZn;$ zg%A|>IjhZCw$1T5O(@zjhB-YfpaLCPzDan(o4f3*y`@xg^a(abipUgPc6B76muXbBS^)K9n$KQzH=!~~ijP82U0 zVHPpx&i%1WVWzqY_{9BNtF9ln+OPsOael4W->by}Da%jQ%Ohh0Fe2BWqoXB2I3$4{ zGJF*Fl@i;#CO*&Z0+G9N$XZ1B=(wl|6Gh9aa4};VR+oYp*pgn!bYzld^$AetS=Rw= zqD}}ttj;UXL|m?X^^B%u5lAtbHr-70MKYduVSL-q4d;$PxQq?9h^@gk31Z6-aVeC~ zZyxGNf6q2&`R9M2sCrq#K!S(SvC+=?I&JnkxR+8y8o=g|6iA}Mx3iwFx-Pk(2^8q$ z#~W~odd*;PNqi!>C`_D1t;ii;h0Kb&B3y^1KCm~4PXH<-y=oldfnE7aJiX-f&RO1B zWWx;4Ju)-q9_UEfdw-%$_CB3*t)kV&yTK`xG@le-lYoCh>)2wR6^u$?-Ib0%sm4`k zMs;B+;7O{=ew43|=j|%^H$`KOJj}FwPKvHjUaY|S`wog>%(iTl_+2#ZMWX38mh%7+ zA3WgCit#oXtEjlM(QgU=IQ-pu`U29^idDG$K0C5(?n04K?a$Ql&?K=-yqrl}FKdmv z!$_^377Sp(V!OHQR#s6v#V!jQ-B^sWoH!(Zy zlFVWYu6!hr&Y>DlkOa0=NgQSS!u`}$R+?~$kvxrw+1ZL%=2=OkpHSxZdD5G!Tq|U8 z?&vvv$+as6)n;St4(03;VoZ~vi&2WD#t&NO9d4uYV``px+8>)Nj9v|J(fd6%=v?k& zxW`lBX`ghVWF?*%f=^o?sNlUFAs*~CoVS*@>qk~6U8gy!E`sU!%;ese4(t>9E&CQf zn>JpEEhO!b>%LR?ONxghqYpp6El}Sxg9MIlzXhKTZ$5PpxFjn+rpi%COb18ii0y2`NqxAlI^h9n+cxmv-&CHbk$KM zK-X-M&R?eQSjL{8g%qySByUi}8I0D$Gs!AVG_-vE5(SrfZH&PA&Y80;y0J^#lyZ;G zHrMkHQwTKhzC5zr{VVk<^VU5yZ3XJB3w9ffx=ocv(g{4@8W1Dhf`-5n?JSoW?d$zkMkyF4f9T_9Isoa_w>+fZ|jQd zRB6}r#lj;pPYbTzNY|96sRG!k`FV`)`%z2AI$uu!-tC(5dnjtuxcrUSM-T0_ z-0bpyXRk`6^52w!=`9 zO1xbIj?F7e=ZufcubK^FIH8j7Q+PgJDaiWL*|%Jf1_)5p>p-zsko_~<4FG-bZqn48 z4xgleRRA>tD4#qAQHAdih-ga|yoI=;!ei6g$?v~Lo%VJ^Mo756ZnN7Qmk#As(#K5U`#o8 zE8fkTvJ?oT<|34wH0lsww?;vRRJm>PSJ~WnU{3O`%fWAGae`kCFlo*HG7tDFQMa4r zaG}(VS6`@r)(Rz=f@jv_C|6s`;gJ9$NqUFAg?oLjMtJ(%1SvZcJATWqzf7OpVOA8F z^!zAJo9h#m{Fa+s?cPZmJsv6O@~)AxowB=h2Yqbrm!m@J(X{F6Kt7=M^-PfmG5SdE zx~;!sLc)5wOEN%vG~-c{zK`ilEznih8Y;b*X5+@$)!H>sFnC#%9qq>1~-8M)^fTbF5^^EJ!h4m|{VOm(v5IT#F z;YHkBLv_r^USWAJmT<`?YU|aVF@yU3kpP8&Jj3;pG`wR+5*a1;&>KK*=Ov--XPa}Q zB4pWB^%^!wW>G!LRZZ)&+xg+v!|?UCXimSU%v|=~od(4vppQ<8!iFmWg~F8|Hv3}) zg1+08u7rm5{@I1VHnas6{ba#9V8{nHmKRIbi)R;WiLe(rR+F25YrldRuni4>yT5y8 zfs+?`{*b0=kKoruk2EKM1Vw_xgh`?MqgKRZ)UPPTku_F~;tzfNA_)?_n~+m@4AKlo zX+f8%>*@mh>|Y`bi%7wXw>vpd(ypH&tZ!1?M zl4hW1SumJynUcDr4zJP+H5jm44GCvsi?Ko5UF8E5f37hLsmyahSuxP3jZnn4AS9(^ zm%1EVRS1Km!?@v%AV^I71{VcC{9=*+O%D6$*s!lyjeNV%JTv8}fz12cSow!_>x<*a zk(!!U8vY4D=;Vl6Zvqux`CvtalP80+aisZNE*~hPweqmr3;266Mfu?6Jkxm~Fx+A! zxTzZ&)cH2e^<4AyqFHdN5s7zAsQ$|!v1OBJ`AX?_{q-33ib%58flS}-7A2axz@O__ zy)i+{h!=8-N>Rnp*;ff7j|ad;02^9%YCJ1Nr8iR^I`HUsxsUw@xz&Ol~zM~KL0b&DKDb|%EC`gDd_x2ukB|HLfMtmf23oE1eQfFD! z3IQ9@6L?k;=sx4S^VM4nr}o-i&9NmqQnr24H>N6By~67JV)@1Z0mN1%3_sBa>^P3d^K+H;KIKfNzCNE&9k&Z z(RzkY+fIh8MR!(uKxZHaN^$2e2LXalXdRL_1X}EQS~2pCSzvhzz3y;-1FXdH8O^BtDL0qFzy2dI|7KBFtw44YT%ZM$5PxBr=;rXW3%l$H-Nss3a~=+Ok;$Ud3>sf zvT4niJm(W4;`k5OU7%n!Utm}2yi-UA zwYp#PMzl{)lEfI&(5iF7#^~uk&}0JQ$E%2XBFI=MA3js_N{SB{vP33?K5F5%-XHIH ztS$4)8`loCl%y%2Rx;G9^KumUUdCBkRp5y=`0@BJSEOeLi4j4HwvyM!w7EjlwrDvau{1s99IGta1sM`Uc*W73pXP*2vT2V@ z!|Z>gop(k-M{uDKYf&POocJMK2?1-)ZX@5wvOXeB2d>O}1`Vf&(KbO%ZX>AvI9z}P z!Fy;_?<^vm;DA*6(${Sz(WbM?9o=DA@VjI7VeC$rbxqI~3b9^`3@f%~VrdvPk(ZpC zH2IFauvSkr;0M#Zls@!5WpW_uuQp{HdpD1+bH}d?gn}+#qW&tcy&BBPuyBYj1*Lrl z*p#gV{>{7rux`BifQ~R>e8u$iO5U)Vl%FCW?lvXIe&cG>)=`SA`_?!rdDB0jo5d)3{;*c7Uk|!g>OJ$U{X9j5k_)?p6;dM?xQ|N zupIx&3_lze-d_H$FF9MqUjSff7hX#<80CE#^dWKZ)8O-Cy8GiX!U{P@NicBhe(Q&P z+P`v_K6eN>-s&OAl@rwoqhnsnW^^z-4nJl2dM$fF5mNmr!NNF|-&qnfK4NyRqu*<~ zHdDF>X#aM;@^c(tD4XATXsWDTKOZhF{1(SA1Ad2(43#D>e)*oHHj}n)GMiC7iLBcn z&Q5p($WA{y3jNS~DA;oa=Fme6>}Aqf>Dmop*VBj+yV1vy1&2AWxVn#~^8Hm#d-Dx( zX-eU&{?-%I%=;rhRD8}I5;+qyiFCX{A~I}rt%KX71ix$lbiJ7%Tx8+%(r|Upi-HzM z{s2)%GFU5o7$jG~s_{n9k8B40FoxO4$BeXY`@qij6hZ6M2RZ#=o3 zo@Y|y^uu2jtk{As>qme6L44M61IXfb@bhWSemeVuZ!DGj^y{m2t+fdYTsxDBwWm9H z1_@P9@K~{0xd(X@F0uY|G>!e8ExrJpim2pX`pC$8%n8u02tTIY(>>VO)mNXa*MiD? zhg54u-q^cy-D)cdZ)=cgW%WgTvjDlnhtYha{=Fo=s|yDM8GZ?itP!|UbwOUP7kdavtxFvZ zK%J_TpYW`=eZLlV$K%@bDTfG(D?d?gZ9QznR11Ua zv>mer#i#m=WF!}#J)WvadmJ|dVzXQKp`wPe>Iw_B(;}C=Lfn?r%#_*{7QEg5ij8W} zq9(1TeixT4gT_dNwK58-69ot(%Yl^5?N3(cgcWMPO+UfAJg$37&TkC=#W@{gu~DNg zin<;gac>hevl;1-?BU}WddxZkTPLd^X19eCll*HJ0U0i^)e1_H*9^kf^(z!oP_*%O~cWZy1TqH?S zO_zw>dP?oBL2iqqbNC-vlm;|xQk_$ozQ17r3CVGDaBXN9b288gUi~aqi-dlg8ys$* zd^y3i(>TkVWlc&1R^f+^&%EJHy}AJuQM&q!+-{UE@EiVSj5H0qt9%0}{^4aRG)sns zE_ER*$*Zo=xBp}qN9VocDf#s7$OcFy4^0Jwc-HS?k1y9m?`fKhzRo3QkmfT0gvx$= zf}o!cr#7NKuXQ)(YHW0yC}mtIZH=f1PGgYc9+Jg%24%UT8#MS|`O*q{F% zM1yiI(bCgYO4Y1}VEt9%G`Y){(@Z6uT~|?s=~!C(UNc1vtLxi?T=?ed+=SBeXlYsx ztr`0X-k!Cy+aXZHBXeCiPdlvGnF8(Nv6Cd*aTHQqhq9hpaV|sy3T5oDO&Gd3I=~miQkU5e$vdueYeiFksg;3 z62$v+7~6yDYI)ZUMhHngJfBb&f(*4z6_3K-R*%P1r7500c`G-!X6~By=+$v*qszT5 zME$heehEf{!0GzXl|_O4dYqTkAt*x3y9PB;Y%|+aC5|Bj@}v`F+~kwWf-z8>mWO^Lp^z z2O;jzFb)wEzk2Ygtx|e=k(R_rdUFd&dAdX;;vx2_Sg2qi=N{&W9>-$z!Xg_a^Zsk# zwac|~7O@m*uD)W&-7jE1a*_Sz9wJR8VkRf#kNv@*_VVX%lk=DRlWbU7+*L1FIj+5U zp1e*bk7MUM_J^T^;AE-dSxhfti##``oCOL`JHr=>Wt96>4Bk`Ahi2RWx-Vj~f9M?0 zn$}RLd1VHx*;uKa-vIWPUOGgWJ}PgOdz0&~6}Eqhvqyje8MJT&97 z_%;=M*-3$fb-?{1%GmLe;LvxMS#PBbXcN_6cV>ODaL#&f%X^I1IWT@jU2$>nBINRc z3%D-`SDRBy)k;^Ivr`XbQ+BRw5oB-3q!vsn2`>{Gy09l<+OmRQxTE)I-K~%~TjRt7 zZqgHAWsK3nH5FH!|oYQD@UFmPW$w7$u%d{r1u-y6|8Qf&V7y3vtVhv(m6L{OQCjsPhWnj zed7axUYg5=+wZxLpx6Un9oAmIW=Yi0m_9Kz-BH~hqUk3~H}s8%FrIetx7}#L?1ySh zC@WP1KMW}!8OPPv>z5$>8-T%fgrS7nY^N-yX;jseC;4sCflx|7^Z|WWIKg7i#goW4 zhBo*;{`XOF&`P4H-Nsw5e)HS8+bgk8v{`c~h)tHQKTy|AjT)>*_j7zO1#k0OwD1K= z!3JCx65*)uH%R6Nuy)5&Xwa~5NZK=ft+=G?1~3nJ5PqVj>t0D@@Lr8k8<0u!VYBy4 z`Fx91c32OPp-L|#AfVWaFu0ywu>Y3iB-Y`zu7XJNT>WbOM9T&DZTWFf>ge0jwqB!`tv2n6rj8 zJ&tD6Dty^C(By#w{Lr62UtX?p==#{&F~Ti9TQ<@g&GDk6D(x{#_PZM9c+`$_;rz*{xj6VTgPn0rU~+z&JRuKcL(XJ zV;u9O56%ynG9bG>_uY=a6!L91wOS}6J{uBC``W?IVBO4jwd!98#;Z1glFIZ1dF2Z?k9U+!KPV&aJSiI$II25d&uk8fO zNB6&RWIV@b1W?g9M?|GOrH?##`ItYWhml9f;lnZU$pfXXS>jm5s%J9!~{@$^sP@1?!h8zo<2OU;K4 z!dNXo(fBf9XDeo$Vr9O(36;3z{@X8U@4~h_wH_PgM6LD>K;!N# zR8ZlqdNC67N=7qn+?6pkA(Lx{zN)Gc{Px-YeP>#`{)&s&vPa%Me?`NdSUSq`fyf@q zk#;j|ehXRMeztrcj-P*f&U$<8O?tfP>&>T@7dL>bMe71idF(cWzV$Sr(NQtP@=~Wc zMqk~a(ddo&yug)&>)REHV_|!*rsu%h=ogj3=^M0!{?s|E`sZ(}+0&geD%Y*!v9e7+ z{|=mSe)SmSiBJL;9lIpYia&|sLGYKdD5^H|aHh0Wv`cX<0Li4ubd=VTE?4Z-k#jmj zUph_ne|`d05y{`9*7M_rv($S~oe9cr_Fp<2&m}*6Jq_RQ3Dswxc^UhPzAWVN+?|_9H1Sz++}Ab3WStf&zV`sShK+7yb*o^7lA(0CLM&~pmu!OC7_WLBdUJCKB&ZcQ z)A5CCZ3r&$U!T#p;}$GTOfVQrD<*ZW2lagR(Wzh)r5;t@)brJl8-R;>N%G^}8$hk1 zUX^P-sOstK!mOimsI{x{)FYdqk(@<6TA^%4rK-;yD@e*Xz$;JUMe{M9B)80`(o87> z@!YqkE)9==I_-IN=NpzA|7J5b#id)MbBp8lOI3|bu}hiPg^P-hJ%`M33-9V=f8es5aWlwjdByh=+nTOekj}k0LuM<{poMIkMQcd zBwUj9M*ebbGv5{ZkZn;73GT}Kh6V+G_?qA$xl@i+14p(A@vC(aMiz;-?+_5p);{vc zJO9eLM7$L~Qjurf(&lsZ>~|y$l{ud z`YF}cJv^Si1>gN7mDy$X&q9(vIy38Pwh4fr{jMA73IB{fHH5u2MCQWy91mx&5j|$h zy^sTMGfSk6rKx#^@nSl)#HV_7!9_Q(-Z|4RxJv4hXxa+?mBRKmuHaIk{}q-QY38&7 zJpTYclzQHd{Tmx3k7>TZoAt;R4~Qq|i)zvpE!P=ncU#_43Q@|(`L_AJy}y^X3so(9 zLI(8>#=%~sl0M!rHQ5hGaZhh7sVUptVPtu@u_Kh+6UTxM3uYj#o-Cx==}E5dq~bVF79b!Md%2?uAZIgqKfULTWy!UksavT;sJjA^>~X&DwJ!K)E#9XLwV>2KHEs#bGR&K(H(l(>k?a3tmk$x_0#?Wbl73P0F z>km^b(K<~;gdN?(S+v$Mim&Y%?!Jn@v)$A#o9ds#ViL69CRDO6NXS#|pB={f2CDYH_&L1QYrc;!u>j zk^mc0u#x$gDWHF8&#EbGE>rMyy_$S*lKaZaZ6u=R+d%n_Rrd3RYa6|rPxJf`u6sK# z^!kG^Xd9}mvb_Fy6tmn| zd6XqeN}L--?|yCR!`+#Bq0%q*&!3shqMs(GLt1GrCPG7{NNE>SX#{cWTECtm{{X@d z;>(@2XHBy%Y1gS4Y67a0Bh#`)A1!6|8n$LqLyDB-Xpz+G@>@!aE%y?R(pDFBji%MP z)Ly05^LJ1>lU=6O+fru-V zk}v6~kmBMT2_GpL1e4yzSJ1i!hfCJ_#OJ8U=)jGwX<=y2;NmTj#?#y%hX)o^#_FH1 zUYP0GnoL@kO<8|6?oebn?P114w^svdT2Qs|8S_uXL!-e|{4z&H83|%Kl%Tm;#nxlA zLU|=dIRk-U1M%EZ9=P>I~5sZI263U)w0Si{*6JS%1XA7J@WBY#;z6q~|t~+3e0ePz?y-9Hc z(yzU#W$@6=xwoROnR0Y0e@zma3BEe%Ip6CFocBdN&>e2i&*Wqo2qZ z(AYT`=dF3m)YzzrX_Po>gHu<_BWia(N|bll72lPTPaNX3x`IUy-D~K2KG*JeAl3Y& zcl_{2{{Ry0aG#t$AKK5A>beK185=6nsgaeIDh@SHRC-%68;tCCWFZpNQL*OBVJJ%f z04yZ(Nf@K#Q{v}VP_!Vyn&{lvN&J~SU(;?Q5^50GOvuEyPujNKSUY4U=V}xM zij(H%{sw-$d4b1$;5{T9}P;4ESJb3mT9~Q%roAcUpwPnWY(cmT7Zf{DVM{X-|LEt1dizO&X z@ICzB2<+XLT%)?;kl1!AZ`yGQ2KWB}ffo1Y_QA!dINT_#nY2v=fN#Uhes)O(Ms-R% zQkO;l09qVI)qVX|Ki3(L6DE~CauVMj5;(?U=z7dC(Ojax^0a9n5B)=^LF1by>Ld2P z-|2w8^(@&pu;f_qG3rt6!(k+oxE0v=8;!r<1_PH#1Sirh$Oq0El)XCOTV}$>_!6b| zAheZS@>OBS?f(EA0bi_vYyQ~pDb4Ps7Cw*%80ac5ampTeT_B_lpzZPZ`{Dlp_;G7K zP0_BR683Y_ss^sc%uKgKg-5Kd369i<>4(yt*1gUA9#7a} zYqy2%8rwy2=*pltXcs8+FC^H=zr1;=!_P*&_GhI)?_v zcYcdYq(J#~O%Z^9x~jga*Zv_MmAnQ2jIGgngX<$5FPa(jwowmXVcMf$x; zBX7CA@JW+?D!ndgZc3tMg=(i$nTsrC=3+vFN{)u&Q`W>nlFKeOh9j37{G?nJat}WE z%Z7Ir-Q{~8r_v%57Pc@NE5gbBFJlCUYLVR`Y&E$_Re>B_3{Sq0J~aI|bz4rZ=SAkK7M<3aS|~2JoNI7tQ)fqP znF&)Z_sQ)g$qNBQa;?L{aTU6Fm6P&!#c!*wpmO%2Qgt27ucq}(%+&g0Y5YEyCTd%1 z6cjoZu=}ZE84Fr$i>xFI?zN#U^1S@rJ<*SZQM$+OtQ+C?pmaSJ_;%*1{*>ipFEVC^ zGYsk?%c0tpRiP$gDJ(fL-k8$SoV1xG`a4OZ03u5(+tev>K8 z;=}b(4viLM66z`nZE?_*yu)rZ>H~;*ZMoZ0kfkJyJl_-DT2Dov1u9hm?CC!tPj)_6 z$x?F-GrXCUaO<&=I!j4n&UXV1OHd8+PXgF^x}l_KwoCYbWPLTS<-Ef)OY_9cwUFsF z3RBC2GD>E?!*LmLLB^I5U>5k@bs;W-6>^eJjgkOf0i|9YR-;Zxf$nbScEO?b^Zx*9 z0rfG_4@JzuovC@6A5^+;ln@z^9gmSJ6#6YOw*neyAx_iOQWR8+6|G8A*K$gZAxBvG zbEAkoFiFe3J8BlFXME*Qt2E?zS*{Av3Ydb0m*lcl5vxAF+bwXTaIR8pPA{Y4SxeFl zUCH`;qB^xft!64Fv}UF#H2QrmV&U3ERvn&~UU9cuSYfdm=V4LYQRyP&Q)gR$4_zGT z>*J-9f$Y>*1Orslw55@?!m&_3C} zy4leCMJs+eogp-9;uTy`8*KUISk|w65;Rvy`dOw($z3RErmN~y2z2<)Q|h$=NJRGn zTTGYTlCn|=ve#{-sYc%S#B8p7IWt<_ON#hs^z7ou8!)Ao?(9LTDNY3Q}7?9rn)A8LtMUd5?vxJd1U{{Rs;u)}40T5xOg(P!x6k?g0m zJ9bTzj}9GQq2)nL-_TBGp}L^w!9?odKcmKu z>!PPjt(w~1d~7I=SxZfnB|~xDyN#e&+*+r8FnZfFELK;iFXSALmXQ0g>nv5W=y@os z#Jiy=0mjf#`H2B&H?o@^MUFFHEQV0Vllk>t(?y}uH>b&|m5s0n7~$Qy9QbGTP)1~S zTbfxbU^!{Bes)lIYsMI1%UXjlUyu)JGS+`Mx+M zdrk&Ql?}>dR@jQ;GMX#zyk6WNxdj$YhRvemf^X-HOCb;z4p&KWU5b$v(A!IB-FLR{ zPy}*3i(8BF{c&$!47yD-q`!ycY~xY>?MjhQhch-hDiNc+l<=UIH#i9j?6BD=0Px{9 zARJT5780v~2kd-c#AZpKhnU^#uv4(7Xt^It`L-%*^sbgLz-u?LwZw z%kNZMhKP(IVxZeAaW4b_=G}vC+y0V9>stKap;_XmB|)^mxD{DGfzAH_>TyxJSxD>w z%G;N(Xg4&wadSrA0Ufhso@?m7fPMr$F}Ks-N7<^0oG2MHFxIm6qtv2Lb)>?nID*tz z2ey^11i01Btdcq22e>e|;OD53^HW_xWCDf+omI}+Qk=qU0)%%faz14#@#qnf;3@H9 zd}0yi9+CBrDP^g3YL;4+1*mb;Qjo)vqX~^N<4?Soj9}+@Ahi-hfRuiQRG3S3ONB8MN(z$1+KVqCw;6FwI)b)AJ;nBuBJMcSR2UDmR#04Qn+Hz%bB;+_ zk#%h-d@(ZG*4>llC=*x5m(VLT{?ISP!zguOtQvoyaH>p{k*ks-ady|#N}=F>*HOw^8|9;HmnnRazun&Xu|!>D}-Dn&XnB}`q`pb&=IaW+zj zN|r(aD!9Xul6rL2cXg&+Ge+{3tI%dsU3V*0rqm~@F=lW;b@^=In9b}R?-TB5)y$S#W>=Ulz=(hq=OeeADw1U`ri1rY7bkwjSiEhnsukS zVL5h)sr6J8DtnClx)9s01P9(sbx2y$(oWLzWB7qI_n5kK)vV>HVwYIT*^QjbG7}Xh z;?V3RKxNgI!jiPCSR^bPg067V$u{$qUOh&W-hf(m-)>Kk7Ans!lyvUnOPauyE2gEUzJIfGE)qQt}nLhveMq;WBF>}f({;S7t7fjP4g$? z&FTJx>CC4fI?P&@Ud=Am-+5~(3TbF{mQ>nGM=DB+xBv?hN6oxkvzaN(cH3M**(HWX z6K05QdS{x)K3EIrW2*|x4N%TWnOn#@GoBf4Dk4FtPt9`T$!#f7J9f6x(hwGx>7|b5fk+`nPRn76>s{V0>Uii|Lf82kjI)!X1=NONB`(wra_`s}17hvEY zPv?vdU;hA{3!>)9*?E^6h~sEOuQK|AnL~uFr4=CEK-$Ef2M6Bx;|jSVn^9D@R)oH= z;41BRZE^bKUXjh*+qUXnoo z@xdV2`yUt^{?O{Z=G-pFDrsyul43LEmiul001q}#2LAx4@@{#@DB0Gd5u_mxG!ocl zDF!=dhL%>3Ddx&2&z}b45k+Va4exP=J`Fjos(Eh^>QiH2p5Lh|;NOldh@zgI5d2K2Iaf^bZ5KCY z+EoE`3aw_g^X3Bo0R1td>Yj|mX5%q_`;z6j>Rd-C466PR0!vG}meTz1pS1#?n{#GX zXG!zcWK7wtRH09z<`)}ngdO2DsLnE5apbgA3c}r60moCm)`|}23JjT!hbz@W($!FT zD-=k{CB;V#GjKnJzyekW2)5+%Z;n)q;X{ovp5=0=+f2!Vuv15jv$eS8wW`O0QE~!J z!odnxtDHp?v&r3LWcfW)s;Q|Nw=LAN#ZNCnW;5AR9GMBGPp4E^ogLXOOp4p;b!u)- z=UYnF=gjR=#^o&~oqKh=koug`tv8oIs85YlhfsN$RO*X+q|%sb6H(c5WdOEOa&~P} zmY~v ztuj)}&oG2YrnK4>7q}fkxVa7~%kCDi78fIo3!AtWp6S|oj54-X^3o66DZ#2Rinf+B zOkAGKsBQQAu9c;Ldx=Q1x7wubyMWx2asUL|4CJXn$*!#{?p?(Yt6P1~n|og!q322P zAQ-JKIAFH(L>SGe?RMMwDZTw7$I5+(zB)+HRVi;NZo3_7TaG$^!!$0Xwi;K2_$UBd zz&wi{aa&8J_cxofYtVK70BHXJ1Mn4bpPFV=;Fe!oYHdZUgtCN_+iW(DKnH~J_wsIez?j+Ypv4)ONO`oe9WOSK z*l%!4+k2N*-NySzlj-)g&JI`u!3w8L>d(?j*;&ZSaI?O3i%_TSX-Op9SPvL(C&>#^ zxxe25AFHkPsFTW#&4q{b9xxqJO0caqkU}#m!<