Repository: tiancixiong/leyou Branch: master Commit: 3b0c521b5028 Files: 161 Total size: 286.9 KB Directory structure: gitextract_x80tcxed/ ├── .gitignore ├── .mvn/ │ └── wrapper/ │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── ly-api-gateway/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── leyou/ │ │ ├── LyApiGatewayApplication.java │ │ ├── config/ │ │ │ ├── FilterProperties.java │ │ │ ├── JwtProperties.java │ │ │ └── LeyouCorsConfiguration.java │ │ └── filter/ │ │ └── LoginFilter.java │ └── resources/ │ └── application.yml ├── ly-auth/ │ ├── ly-auth-common/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── leyou/ │ │ └── auth/ │ │ ├── entity/ │ │ │ └── UserInfo.java │ │ └── utils/ │ │ ├── JwtConstans.java │ │ ├── JwtUtils.java │ │ ├── ObjectUtils.java │ │ └── RsaUtils.java │ ├── ly-auth-service/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── leyou/ │ │ │ │ └── auth/ │ │ │ │ ├── LyAuthApplication.java │ │ │ │ ├── client/ │ │ │ │ │ └── UserClient.java │ │ │ │ ├── config/ │ │ │ │ │ └── JwtProperties.java │ │ │ │ ├── controller/ │ │ │ │ │ └── AuthController.java │ │ │ │ └── service/ │ │ │ │ └── AuthService.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── leyou/ │ │ └── auth/ │ │ └── test/ │ │ └── JwtTest.java │ └── pom.xml ├── ly-cart/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── leyou/ │ │ └── cart/ │ │ ├── LyCartApplication.java │ │ ├── client/ │ │ │ └── GoodsClient.java │ │ ├── config/ │ │ │ ├── JwtProperties.java │ │ │ └── MvcConfig.java │ │ ├── controller/ │ │ │ └── CartController.java │ │ ├── interceptor/ │ │ │ └── LoginInterceptor.java │ │ ├── pojo/ │ │ │ └── Cart.java │ │ └── service/ │ │ └── CartService.java │ └── resources/ │ └── application.yml ├── ly-common/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── leyou/ │ └── common/ │ ├── pojo/ │ │ └── PageResult.java │ └── utils/ │ ├── CookieUtils.java │ ├── IdWorker.java │ ├── JsonUtils.java │ └── NumberUtils.java ├── ly-goods-web/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── leyou/ │ │ ├── LyGoodsWebApplication.java │ │ ├── client/ │ │ │ ├── BrandClient.java │ │ │ ├── CategoryClient.java │ │ │ ├── GoodsClient.java │ │ │ └── SpecificationClient.java │ │ ├── controller/ │ │ │ └── GoodsController.java │ │ ├── listener/ │ │ │ └── GoodsListener.java │ │ ├── service/ │ │ │ ├── GoodsHtmlService.java │ │ │ └── GoodsService.java │ │ └── utils/ │ │ └── ThreadUtils.java │ └── resources/ │ ├── application.yml │ └── templates/ │ └── item.html ├── ly-item/ │ ├── ly-item-interface/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── leyou/ │ │ └── item/ │ │ ├── api/ │ │ │ ├── BrandApi.java │ │ │ ├── CategoryApi.java │ │ │ ├── GoodsApi.java │ │ │ └── SpecificationApi.java │ │ ├── bo/ │ │ │ └── SpuBo.java │ │ └── pojo/ │ │ ├── Brand.java │ │ ├── Category.java │ │ ├── Sku.java │ │ ├── SpecGroup.java │ │ ├── SpecParam.java │ │ ├── Spu.java │ │ ├── SpuDetail.java │ │ └── Stock.java │ ├── ly-item-service/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── leyou/ │ │ │ └── item/ │ │ │ ├── LyItemServiceApplication.java │ │ │ ├── controller/ │ │ │ │ ├── BrandController.java │ │ │ │ ├── CategoryController.java │ │ │ │ ├── GoodsController.java │ │ │ │ └── SpecificationController.java │ │ │ ├── mapper/ │ │ │ │ ├── BrandMapper.java │ │ │ │ ├── CategoryMapper.java │ │ │ │ ├── SkuMapper.java │ │ │ │ ├── SpecGroupMapper.java │ │ │ │ ├── SpecParamMapper.java │ │ │ │ ├── SpuDetailMapper.java │ │ │ │ ├── SpuMapper.java │ │ │ │ └── StockMapper.java │ │ │ └── service/ │ │ │ ├── BrandService.java │ │ │ ├── CategoryService.java │ │ │ ├── GoodsService.java │ │ │ └── SpecificationService.java │ │ └── resources/ │ │ └── application.yml │ └── pom.xml ├── ly-order/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── leyou/ │ │ └── order/ │ │ ├── LyOrderApplication.java │ │ ├── config/ │ │ │ ├── IdWorkerConfig.java │ │ │ ├── IdWorkerProperties.java │ │ │ ├── JwtProperties.java │ │ │ ├── MvcConfig.java │ │ │ ├── PayConfig.java │ │ │ ├── PayProperties.java │ │ │ └── SwaggerConfig.java │ │ ├── controller/ │ │ │ └── OrderController.java │ │ ├── interceptor/ │ │ │ └── LoginInterceptor.java │ │ ├── mapper/ │ │ │ ├── OrderDetailMapper.java │ │ │ ├── OrderMapper.java │ │ │ └── OrderStatusMapper.java │ │ ├── pojo/ │ │ │ ├── Order.java │ │ │ ├── OrderDetail.java │ │ │ └── OrderStatus.java │ │ ├── service/ │ │ │ └── OrderService.java │ │ └── utils/ │ │ ├── PayHelper.java │ │ └── PayState.java │ └── resources/ │ ├── application.yml │ └── mapper/ │ └── OrderMapper.xml ├── ly-registry/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── leyou/ │ │ └── LyRegistryApplication.java │ └── resources/ │ └── application.yml ├── ly-search/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── leyou/ │ │ │ ├── LySearchApplication.java │ │ │ ├── client/ │ │ │ │ ├── BrandClient.java │ │ │ │ ├── CategoryClient.java │ │ │ │ ├── GoodsClient.java │ │ │ │ └── SpecificationClient.java │ │ │ ├── controller/ │ │ │ │ └── SearchController.java │ │ │ ├── listener/ │ │ │ │ └── GoodsListener.java │ │ │ ├── pojo/ │ │ │ │ ├── Goods.java │ │ │ │ ├── SearchRequest.java │ │ │ │ └── SearchResult.java │ │ │ ├── repository/ │ │ │ │ └── GoodsRepository.java │ │ │ └── service/ │ │ │ └── SearchService.java │ │ └── resources/ │ │ └── application.yml │ └── test/ │ └── java/ │ └── com/ │ └── leyou/ │ └── client/ │ ├── CategoryClientTest.java │ └── ElasticsearchTest.java ├── ly-sms-service/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── leyou/ │ │ └── sms/ │ │ ├── LySmsApplication.java │ │ ├── config/ │ │ │ └── SmsProperties.java │ │ ├── listener/ │ │ │ └── SmsListener.java │ │ └── utils/ │ │ └── SmsUtils.java │ └── resources/ │ └── application.yml ├── ly-upload/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── leyou/ │ │ │ ├── LyUploadServiceApplication.java │ │ │ ├── config/ │ │ │ │ ├── FastClientImporter.java │ │ │ │ └── GlobalCorsConfig.java │ │ │ ├── controller/ │ │ │ │ └── UploadController.java │ │ │ └── service/ │ │ │ └── UploadService.java │ │ └── resources/ │ │ └── application.yml │ └── test/ │ └── java/ │ └── FdfsTest.java ├── ly-user/ │ ├── ly-user-interface/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ ├── api/ │ │ │ └── UserApi.java │ │ └── com/ │ │ └── leyou/ │ │ └── user/ │ │ └── pojo/ │ │ └── User.java │ ├── ly-user-service/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── leyou/ │ │ │ │ └── user/ │ │ │ │ ├── LyUserApplication.java │ │ │ │ ├── controller/ │ │ │ │ │ └── UserController.java │ │ │ │ ├── mapper/ │ │ │ │ │ └── UserMapper.java │ │ │ │ ├── service/ │ │ │ │ │ └── UserService.java │ │ │ │ └── utils/ │ │ │ │ └── CodecUtils.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── leyou/ │ │ └── test/ │ │ └── RedisTest.java │ └── pom.xml ├── mvnw ├── mvnw.cmd └── pom.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ HELP.md target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/** !**/src/test/** ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans .sts4-cache ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr ### NetBeans ### /nbproject/private/ /nbbuild/ /dist/ /nbdist/ /.nb-gradle/ build/ ### VS Code ### .vscode/ ================================================ FILE: .mvn/wrapper/MavenWrapperDownloader.java ================================================ /* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you 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 https://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. */ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.util.Properties; public class MavenWrapperDownloader { /** * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. */ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; /** * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to * use instead of the default one. */ private static final String MAVEN_WRAPPER_PROPERTIES_PATH = ".mvn/wrapper/maven-wrapper.properties"; /** * Path where the maven-wrapper.jar will be saved to. */ private static final String MAVEN_WRAPPER_JAR_PATH = ".mvn/wrapper/maven-wrapper.jar"; /** * Name of the property which should be used to override the default download url for the wrapper. */ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; public static void main(String args[]) { System.out.println("- Downloader started"); File baseDirectory = new File(args[0]); System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); // If the maven-wrapper.properties exists, read it and check if it contains a custom // wrapperUrl parameter. File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); String url = DEFAULT_DOWNLOAD_URL; if (mavenWrapperPropertyFile.exists()) { FileInputStream mavenWrapperPropertyFileInputStream = null; try { mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); Properties mavenWrapperProperties = new Properties(); mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); } catch (IOException e) { System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); } finally { try { if (mavenWrapperPropertyFileInputStream != null) { mavenWrapperPropertyFileInputStream.close(); } } catch (IOException e) { // Ignore ... } } } System.out.println("- Downloading from: : " + url); File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); if (!outputFile.getParentFile().exists()) { if (!outputFile.getParentFile().mkdirs()) { System.out.println( "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); } } System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); try { downloadFileFromURL(url, outputFile); System.out.println("Done"); System.exit(0); } catch (Throwable e) { System.out.println("- Error downloading"); e.printStackTrace(); System.exit(1); } } private static void downloadFileFromURL(String urlString, File destination) throws Exception { URL website = new URL(urlString); ReadableByteChannel rbc; rbc = Channels.newChannel(website.openStream()); FileOutputStream fos = new FileOutputStream(destination); fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); fos.close(); rbc.close(); } } ================================================ FILE: .mvn/wrapper/maven-wrapper.properties ================================================ distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================ FILE: README.md ================================================ # 乐优商城 [![stars](https://img.shields.io/github/stars/tiancixiong/leyou?color=42b883&logo=github&style=flat-square)](https://github.com/tiancixiong/leyou/stargazers) [![forks](https://img.shields.io/github/forks/tiancixiong/leyou?color=42b883&logo=github&style=flat-square)](https://github.com/tiancixiong/leyou/network/members) [![license](https://img.shields.io/github/license/tiancixiong/leyou?color=42b883&style=flat-square)](https://github.com/tiancixiong/leyou/blob/master/LICENSE) ## 1.项目介绍 ### 1.1.项目简介 - 乐优商城是一个全品类的电商购物网站(B2C)。 - 用户可以在线购买商品、加入购物车、下单、秒杀商品 - 可以品论已购买商品 - 管理员可以在后台管理商品的上下架、促销活动 - 管理员可以监控商品销售状况 - 客服可以在后台处理退款操作 - 希望未来3到5年可以支持千万用户的使用 ### 1.2.系统架构 乐优商城架构缩略图: ![1573821025276](https://gitee.com/tiancixiong/BlogIMG/raw/master/blog/20191115_leyou/README/1573821025276.png) 整个乐优商城可以分为两部分:后台管理系统、前台门户系统。 - 后台管理: - 后台系统主要包含以下功能: - 商品管理,包括商品分类、品牌、商品规格等信息的管理 - 销售管理,包括订单统计、订单退款处理、促销活动生成等 - 用户管理,包括用户控制、冻结、解锁等 - 权限管理,整个网站的权限控制,采用JWT鉴权方案,对用户及API进行权限控制 - 统计,各种数据的统计分析展示 - 后台系统会采用前后端分离开发,而且整个后台管理系统会使用Vue.js框架搭建出单页应用(SPA)。 - 预览图:![1573821246869](https://gitee.com/tiancixiong/BlogIMG/raw/master/blog/20191115_leyou/README/1573821246869.png) - 前台门户 - 前台门户面向的是客户,包含与客户交互的一切功能。例如: - 搜索商品 - 加入购物车 - 下单 - 评价商品等等 - 前台系统我们会使用Thymeleaf模板引擎技术来完成页面开发。出于SEO优化的考虑,我们将不采用单页应用。 - 预览图:![1573821296666](https://gitee.com/tiancixiong/BlogIMG/raw/master/blog/20191115_leyou/README/1573821296666.png) ### 1.3.技术选型 前端技术: - 基础的HTML、CSS、JavaScript(基于ES6标准) - JQuery - Vue.js 2.0以及基于Vue的框架:Vuetify - 前端构建工具:WebPack - 前端安装包工具:NPM - Vue脚手架:Vue-cli - Vue路由:vue-router - ajax框架:axios - 基于Vue的富文本框架:quill-editor 后端技术: - 基础的SpringMVC、Spring 5.0和MyBatis3 - Spring Boot 2.0.1版本 - Spring Cloud 最新版 Finchley.RC1 - Redis-4.0 - RabbitMQ-3.4 - Elasticsearch-5.6.8 - nginx-1.10.2: - FastDFS - 5.0.8 - MyCat - Thymeleaf ## 2.项目结构 - [leyou](https://github.com/tiancixiong/leyou):后台管理系统后台 - ly-registry:注册中心模块 - ly-api-gateway:网关模块 - ly-item:商品服务模块 - ly-common:通用工具模块 - ly-upload:图片上传模块 - ly-search:搜索服务模块 - ly-goods-web:商品详情页服务模块 - ly-user:用户中心模块 - ly-sms-service:短信服务模块 - ly-auth:授权中心模块 - ly-cart:购物车服务模块 - ly-order:订单服务模块 - [leyou-manage-web](https://github.com/tiancixiong/leyou-manage-web):后台管理系统前端 - [leyou-protal](https://github.com/tiancixiong/leyou-protal):前台门户 另:[leyou-demo](https://github.com/tiancixiong/leyou-demo)(乐优商城demo练习项目),包括:Spring Cloud组件(Eureka、Zuul、Robbin、Feign、Hystix)学习、Elasticsearch和Spring Data Elasticsearch学习、RabbitMQ和Spring AMQP学习、阿里大于学习 ## 3.资料 ### 3.1.笔记 - GitHub:https://github.com/tiancixiong/leyou/tree/notes - Gitee:https://gitee.com/tiancixiong/leyou/tree/notes ### 3.2.数据库 [leyou.sql](https://github.com/tiancixiong/leyou/tree/db) ### 3.3.配置文件 #### 3.3.1.hosts ``` # 乐优商城 127.0.0.1 api.leyou.com # 网关Zuul 127.0.0.1 manage.leyou.com # 后台系统 127.0.0.1 www.leyou.com # 乐优门户 192.168.56.101 image.leyou.com # 图片服务器,虚拟机地址 ``` #### 3.3.2.nginx ```nginx # 乐优商城 server { listen 80; server_name manage.leyou.com; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; location / { proxy_pass http://127.0.0.1:9001; proxy_connect_timeout 600; proxy_read_timeout 600; } } server { listen 80; server_name api.leyou.com; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 转发时,携带自身的host,而不是转发后的host(127.0.0.1) proxy_set_header Host $host; # 上传路径的映射 location /api/upload { proxy_pass http://127.0.0.1:8082; proxy_connect_timeout 600; proxy_read_timeout 600; # 对请求路径进行重写 eg:/api/upload/image -> /upload/image rewrite "^/api/(.*)$" /$1 break; } location / { proxy_pass http://127.0.0.1:10010; proxy_connect_timeout 600; proxy_read_timeout 600; } } # 乐优门户 server { listen 80; server_name www.leyou.com; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; location /item { # 先找本地 root html/leyou; if (!-f $request_filename) { # 请求的文件不存在,就反向代理 proxy_pass http://127.0.0.1:8084; break; } } location / { proxy_pass http://127.0.0.1:9002; proxy_connect_timeout 600; proxy_read_timeout 600; } } ``` ================================================ FILE: ly-api-gateway/pom.xml ================================================ leyou com.leyou.parent 1.0.0-SNAPSHOT 4.0.0 com.leyou.common ly-api-gateway 1.0.0-SNAPSHOT 网关模块 org.springframework.cloud spring-cloud-starter-netflix-zuul org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-configuration-processor true org.projectlombok lombok com.leyou.common ly-common ${leyou.latest.version} com.leyou.auth ly-auth-common ${leyou.latest.version} ================================================ FILE: ly-api-gateway/src/main/java/com/leyou/LyApiGatewayApplication.java ================================================ package com.leyou; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-01 10:15 */ @SpringBootApplication @EnableDiscoveryClient @EnableZuulProxy public class LyApiGatewayApplication { public static void main(String[] args) { SpringApplication.run(LyApiGatewayApplication.class, args); } } ================================================ FILE: ly-api-gateway/src/main/java/com/leyou/config/FilterProperties.java ================================================ package com.leyou.config; import lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.List; /** * @Author: TianCi.Xiong * @Description: 配置Filter白名单 * @Date: Created in 2019-11-14 12:54 */ @Getter @Setter @ConfigurationProperties(prefix = "leyou.filter") public class FilterProperties { private List allowPaths; } ================================================ FILE: ly-api-gateway/src/main/java/com/leyou/config/JwtProperties.java ================================================ package com.leyou.config; import com.leyou.auth.utils.RsaUtils; import lombok.Getter; import lombok.Setter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; import javax.annotation.PostConstruct; import java.security.PublicKey; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-14 12:20 */ @Getter @Setter @ConfigurationProperties(prefix = "leyou.jwt") public class JwtProperties { private String pubKeyPath;// 公钥 private PublicKey publicKey; // 公钥 private String cookieName; private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class); @PostConstruct public void init() { try { // 获取公钥和私钥 this.publicKey = RsaUtils.getPublicKey(pubKeyPath); } catch (Exception e) { logger.error("初始化公钥失败!", e); throw new RuntimeException(); } } } ================================================ FILE: ly-api-gateway/src/main/java/com/leyou/config/LeyouCorsConfiguration.java ================================================ package com.leyou.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; /** * @Author: TianCi.Xiong * @Description: CorsFilter解决跨域问题 * @Date: Created in 2019-11-02 8:30 */ @Configuration public class LeyouCorsConfiguration { @Bean public CorsFilter corsFilter() { //1.添加CORS配置信息 CorsConfiguration config = new CorsConfiguration(); //1) 允许的域,不要写*,否则cookie就无法使用了 config.addAllowedOrigin("http://manage.leyou.com"); config.addAllowedOrigin("http://www.leyou.com"); //2) 是否发送Cookie信息 config.setAllowCredentials(true); //3) 允许的请求方式 config.addAllowedMethod("OPTIONS"); config.addAllowedMethod("HEAD"); config.addAllowedMethod("GET"); config.addAllowedMethod("PUT"); config.addAllowedMethod("POST"); config.addAllowedMethod("DELETE"); config.addAllowedMethod("PATCH"); // 4)允许的头信息 config.addAllowedHeader("*"); //2.添加映射路径,我们拦截一切请求 UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource(); configSource.registerCorsConfiguration("/**", config); //3.返回新的CorsFilter. return new CorsFilter(configSource); } } ================================================ FILE: ly-api-gateway/src/main/java/com/leyou/filter/LoginFilter.java ================================================ package com.leyou.filter; import com.leyou.auth.utils.JwtUtils; import com.leyou.common.utils.CookieUtils; import com.leyou.config.FilterProperties; import com.leyou.config.JwtProperties; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-14 12:22 */ @Component @EnableConfigurationProperties({JwtProperties.class, FilterProperties.class}) public class LoginFilter extends ZuulFilter { @Autowired private JwtProperties jwtProperties; @Autowired private FilterProperties filterProperties; private static final Logger logger = LoggerFactory.getLogger(LoginFilter.class); @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 5; } @Override public boolean shouldFilter() { // 获取上下文 RequestContext ctx = RequestContext.getCurrentContext(); // 获取request HttpServletRequest req = ctx.getRequest(); // 获取路径 String requestURI = req.getRequestURI(); // 判断白名单 // 遍历允许访问的路径 for (String path : this.filterProperties.getAllowPaths()) { // 然后判断是否是符合 if (requestURI.startsWith(path)) { return false; } } return true; } @Override public Object run() throws ZuulException { // 获取上下文 RequestContext context = RequestContext.getCurrentContext(); // 获取request HttpServletRequest request = context.getRequest(); // 获取token String token = CookieUtils.getCookieValue(request, this.jwtProperties.getCookieName()); // 校验 try { // 校验通过什么都不做,即放行 JwtUtils.getInfoFromToken(token, this.jwtProperties.getPublicKey()); } catch (Exception e) { // 校验出现异常,返回403 context.setSendZuulResponse(false); context.setResponseStatusCode(HttpStatus.FORBIDDEN.value()); } return null; } } ================================================ FILE: ly-api-gateway/src/main/resources/application.yml ================================================ server: port: 10010 spring: application: name: api-gateway eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka registry-fetch-interval-seconds: 5 instance: prefer-ip-address: true ip-address: 127.0.0.1 instance-id: ${spring.application.name}:${server.port} zuul: prefix: /api # 添加路由前缀 retryable: true # 开启重试 routes: item-service: /item/** # 将商品微服务映射到/item/** search-service: /search/** # 搜索微服务 user-service: /user/** # 用户微服务 auth-service: /auth/** # 授权中心微服务 cart-service: /cart/** # 购物车微服务 order-service: /** # 订单中心微服务,由于整合了Swagger,所以前缀配置在Controller中 ignored-services: - upload-service # 忽略upload-service服务 add-host-header: true # 携带请求本身的head头信息 sensitive-headers: # 配置禁止使用的头信息,这里设置为null,否则set-cookie无效 ribbon: ConnectTimeout: 250 # 连接超时时间(ms) ReadTimeout: 2000 # 通信超时时间(ms) OkToRetryOnAllOperations: true # 是否对所有操作重试 MaxAutoRetriesNextServer: 1 # 同一服务不同实例的重试次数 MaxAutoRetries: 1 # 同一实例的重试次数 hystrix: command: default: execution: isolation: thread: timeoutInMillisecond: 10000 # 熔断超时时长:10000ms leyou: jwt: pubKeyPath: C:\\tmp\\rsa\\rsa.pub # 公钥地址 cookieName: LY_TOKEN # cookie的名称 filter: # filter白名单 allowPaths: - /api/auth - /api/search - /api/user/register - /api/user/check - /api/user/code - /api/item ================================================ FILE: ly-auth/ly-auth-common/pom.xml ================================================ ly-auth com.leyou.auth 1.0.0-SNAPSHOT 4.0.0 com.leyou.auth ly-auth-common 1.0.0-SNAPSHOT 授权服务通用模块 org.apache.commons commons-lang3 io.jsonwebtoken jjwt 0.9.0 joda-time joda-time org.projectlombok lombok ================================================ FILE: ly-auth/ly-auth-common/src/main/java/com/leyou/auth/entity/UserInfo.java ================================================ package com.leyou.auth.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @Author: TianCi.Xiong * @Description: 载荷对象 * @Date: Created in 2019-11-14 9:33 */ @Data @NoArgsConstructor @AllArgsConstructor public class UserInfo { private Long id; private String username; } ================================================ FILE: ly-auth/ly-auth-common/src/main/java/com/leyou/auth/utils/JwtConstans.java ================================================ package com.leyou.auth.utils; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-14 9:36 */ public abstract class JwtConstans { public static final String JWT_KEY_ID = "id"; public static final String JWT_KEY_USER_NAME = "username"; } ================================================ FILE: ly-auth/ly-auth-common/src/main/java/com/leyou/auth/utils/JwtUtils.java ================================================ package com.leyou.auth.utils; import com.leyou.auth.entity.UserInfo; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.joda.time.DateTime; import java.security.PrivateKey; import java.security.PublicKey; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-14 9:34 */ public class JwtUtils { /** * 私钥加密token * * @param userInfo 载荷中的数据 * @param privateKey 私钥 * @param expireMinutes 过期时间,单位秒 * @return * @throws Exception */ public static String generateToken(UserInfo userInfo, PrivateKey privateKey, int expireMinutes) throws Exception { return Jwts.builder() .claim(JwtConstans.JWT_KEY_ID, userInfo.getId()) .claim(JwtConstans.JWT_KEY_USER_NAME, userInfo.getUsername()) .setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate()) .signWith(SignatureAlgorithm.RS256, privateKey) .compact(); } /** * 私钥加密token * * @param userInfo 载荷中的数据 * @param privateKey 私钥字节数组 * @param expireMinutes 过期时间,单位秒 * @return * @throws Exception */ public static String generateToken(UserInfo userInfo, byte[] privateKey, int expireMinutes) throws Exception { return Jwts.builder() .claim(JwtConstans.JWT_KEY_ID, userInfo.getId()) .claim(JwtConstans.JWT_KEY_USER_NAME, userInfo.getUsername()) .setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate()) .signWith(SignatureAlgorithm.RS256, RsaUtils.getPrivateKey(privateKey)) .compact(); } /** * 公钥解析token * * @param token 用户请求中的token * @param publicKey 公钥 * @return * @throws Exception */ private static Jws parserToken(String token, PublicKey publicKey) { return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token); } /** * 公钥解析token * * @param token 用户请求中的token * @param publicKey 公钥字节数组 * @return * @throws Exception */ private static Jws parserToken(String token, byte[] publicKey) throws Exception { return Jwts.parser().setSigningKey(RsaUtils.getPublicKey(publicKey)) .parseClaimsJws(token); } /** * 获取token中的用户信息 * * @param token 用户请求中的令牌 * @param publicKey 公钥 * @return 用户信息 * @throws Exception */ public static UserInfo getInfoFromToken(String token, PublicKey publicKey) throws Exception { Jws claimsJws = parserToken(token, publicKey); Claims body = claimsJws.getBody(); return new UserInfo( ObjectUtils.toLong(body.get(JwtConstans.JWT_KEY_ID)), ObjectUtils.toString(body.get(JwtConstans.JWT_KEY_USER_NAME)) ); } /** * 获取token中的用户信息 * * @param token 用户请求中的令牌 * @param publicKey 公钥 * @return 用户信息 * @throws Exception */ public static UserInfo getInfoFromToken(String token, byte[] publicKey) throws Exception { Jws claimsJws = parserToken(token, publicKey); Claims body = claimsJws.getBody(); return new UserInfo( ObjectUtils.toLong(body.get(JwtConstans.JWT_KEY_ID)), ObjectUtils.toString(body.get(JwtConstans.JWT_KEY_USER_NAME)) ); } } ================================================ FILE: ly-auth/ly-auth-common/src/main/java/com/leyou/auth/utils/ObjectUtils.java ================================================ package com.leyou.auth.utils; import org.apache.commons.lang3.StringUtils; /** * @Author: TianCi.Xiong * @Description: 从jwt解析得到的数据是Object类型,转换为具体类型可能出现空指针,这个工具类进行了一些转换 * @Date: Created in 2019-11-14 9:35 */ public class ObjectUtils { public static String toString(Object obj) { if (obj == null) { return null; } return obj.toString(); } public static Long toLong(Object obj) { if (obj == null) { return 0L; } if (obj instanceof Double || obj instanceof Float) { return Long.valueOf(StringUtils.substringBefore(obj.toString(), ".")); } if (obj instanceof Number) { return Long.valueOf(obj.toString()); } if (obj instanceof String) { return Long.valueOf(obj.toString()); } else { return 0L; } } public static Integer toInt(Object obj) { return toLong(obj).intValue(); } } ================================================ FILE: ly-auth/ly-auth-common/src/main/java/com/leyou/auth/utils/RsaUtils.java ================================================ package com.leyou.auth.utils; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-14 9:36 */ public class RsaUtils { /** * 从文件中读取公钥 * * @param filename 公钥保存路径,相对于classpath * @return 公钥对象 * @throws Exception */ public static PublicKey getPublicKey(String filename) throws Exception { byte[] bytes = readFile(filename); return getPublicKey(bytes); } /** * 从文件中读取密钥 * * @param filename 私钥保存路径,相对于classpath * @return 私钥对象 * @throws Exception */ public static PrivateKey getPrivateKey(String filename) throws Exception { byte[] bytes = readFile(filename); return getPrivateKey(bytes); } /** * 获取公钥 * * @param bytes 公钥的字节形式 * @return * @throws Exception */ public static PublicKey getPublicKey(byte[] bytes) throws Exception { X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes); KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePublic(spec); } /** * 获取密钥 * * @param bytes 私钥的字节形式 * @return * @throws Exception */ public static PrivateKey getPrivateKey(byte[] bytes) throws Exception { PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes); KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePrivate(spec); } /** * 根据密文,生存rsa公钥和私钥,并写入指定文件 * * @param publicKeyFilename 公钥文件路径 * @param privateKeyFilename 私钥文件路径 * @param secret 生成密钥的密文 * @throws IOException * @throws NoSuchAlgorithmException */ public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); SecureRandom secureRandom = new SecureRandom(secret.getBytes()); keyPairGenerator.initialize(1024, secureRandom); KeyPair keyPair = keyPairGenerator.genKeyPair(); // 获取公钥并写出 byte[] publicKeyBytes = keyPair.getPublic().getEncoded(); writeFile(publicKeyFilename, publicKeyBytes); // 获取私钥并写出 byte[] privateKeyBytes = keyPair.getPrivate().getEncoded(); writeFile(privateKeyFilename, privateKeyBytes); } private static byte[] readFile(String fileName) throws Exception { return Files.readAllBytes(new File(fileName).toPath()); } private static void writeFile(String destPath, byte[] bytes) throws IOException { File dest = new File(destPath); if (!dest.exists()) { dest.createNewFile(); } Files.write(dest.toPath(), bytes); } } ================================================ FILE: ly-auth/ly-auth-service/pom.xml ================================================ ly-auth com.leyou.auth 1.0.0-SNAPSHOT 4.0.0 com.leyou.auth ly-auth-service 1.0.0-SNAPSHOT 授权服务模块 org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-openfeign org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test org.springframework.boot spring-boot-configuration-processor true org.projectlombok lombok com.leyou.auth ly-auth-common ${leyou.latest.version} com.leyou.common ly-common ${leyou.latest.version} com.leyou.user ly-user-interface ${leyou.latest.version} ================================================ FILE: ly-auth/ly-auth-service/src/main/java/com/leyou/auth/LyAuthApplication.java ================================================ package com.leyou.auth; 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: TianCi.Xiong * @Description: * @Date: Created in 2019-11-14 9:30 */ @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class LyAuthApplication { public static void main(String[] args) { SpringApplication.run(LyAuthApplication.class, args); } } ================================================ FILE: ly-auth/ly-auth-service/src/main/java/com/leyou/auth/client/UserClient.java ================================================ package com.leyou.auth.client; import api.UserApi; import org.springframework.cloud.openfeign.FeignClient; @FeignClient(value = "user-service") public interface UserClient extends UserApi { } ================================================ FILE: ly-auth/ly-auth-service/src/main/java/com/leyou/auth/config/JwtProperties.java ================================================ package com.leyou.auth.config; import com.leyou.auth.utils.RsaUtils; import lombok.Getter; import lombok.Setter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; import javax.annotation.PostConstruct; import java.io.File; import java.security.PrivateKey; import java.security.PublicKey; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-14 10:55 */ @Getter @Setter @ConfigurationProperties(prefix = "leyou.jwt") public class JwtProperties { private String secret; // 密钥 private String pubKeyPath;// 公钥 private String priKeyPath;// 私钥 private int expire;// token过期时间 private PublicKey publicKey; // 公钥 private PrivateKey privateKey; // 私钥 private String cookieName; // cookie的name private Integer cookieMaxAge; // cookie的存活时间 private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class); /** * @PostContruct:在构造方法执行之后执行该方法 */ @PostConstruct public void init() { try { File pubKey = new File(pubKeyPath); File priKey = new File(priKeyPath); if (!pubKey.exists() || !priKey.exists()) { // 生成公钥和私钥 RsaUtils.generateKey(pubKeyPath, priKeyPath, secret); } // 获取公钥和私钥 this.publicKey = RsaUtils.getPublicKey(pubKeyPath); this.privateKey = RsaUtils.getPrivateKey(priKeyPath); } catch (Exception e) { logger.error("初始化公钥和私钥失败!", e); throw new RuntimeException(); } } } ================================================ FILE: ly-auth/ly-auth-service/src/main/java/com/leyou/auth/controller/AuthController.java ================================================ package com.leyou.auth.controller; import com.leyou.auth.config.JwtProperties; import com.leyou.auth.entity.UserInfo; import com.leyou.auth.service.AuthService; import com.leyou.auth.utils.JwtUtils; import com.leyou.common.utils.CookieUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-14 11:00 */ @RestController @EnableConfigurationProperties(JwtProperties.class) public class AuthController { @Autowired private AuthService authService; @Autowired private JwtProperties properties; /** * 登录授权 * * @param username * @param password * @return */ @PostMapping("/accredit") public ResponseEntity authentication( @RequestParam("username") String username, @RequestParam("password") String password, HttpServletRequest request, HttpServletResponse response) { // 登录校验 String token = this.authService.authentication(username, password); if (StringUtils.isBlank(token)) { return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); } // 将token写入cookie,并指定httpOnly为true,防止通过JS获取和修改 CookieUtils.setCookie(request, response, properties.getCookieName(), token, properties.getCookieMaxAge(), null, true); return ResponseEntity.ok().build(); } /** * 验证用户信息 * * @param token * @return */ @GetMapping("/verify") public ResponseEntity verifyUser( @CookieValue("LY_TOKEN") String token, HttpServletRequest request, HttpServletResponse response ) { try { // 从token中解析token信息 UserInfo userInfo = JwtUtils.getInfoFromToken(token, this.properties.getPublicKey()); // 解析成功要重新刷新token token = JwtUtils.generateToken(userInfo, this.properties.getPrivateKey(), this.properties.getExpire()); // 更新cookie中的token CookieUtils.setCookie(request, response, this.properties.getCookieName(), token, this.properties.getCookieMaxAge()); // 解析成功返回用户信息 return ResponseEntity.ok(userInfo); } catch (Exception e) { e.printStackTrace(); } // 出现异常则,响应500 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } ================================================ FILE: ly-auth/ly-auth-service/src/main/java/com/leyou/auth/service/AuthService.java ================================================ package com.leyou.auth.service; import com.leyou.auth.client.UserClient; import com.leyou.auth.config.JwtProperties; import com.leyou.auth.entity.UserInfo; import com.leyou.auth.utils.JwtUtils; import com.leyou.user.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-14 11:18 */ @Service public class AuthService { @Autowired private UserClient userClient; @Autowired private JwtProperties properties; public String authentication(String username, String password) { try { // 调用微服务,执行查询 User user = this.userClient.queryUser(username, password); // 如果查询结果为null,则直接返回null if (user == null) { return null; } // 如果有查询结果,则生成token String token = JwtUtils.generateToken(new UserInfo(user.getId(), user.getUsername()), properties.getPrivateKey(), properties.getExpire()); return token; } catch (Exception e) { e.printStackTrace(); } return null; } } ================================================ FILE: ly-auth/ly-auth-service/src/main/resources/application.yml ================================================ server: port: 8087 spring: application: name: auth-service eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka registry-fetch-interval-seconds: 10 instance: lease-renewal-interval-in-seconds: 5 # 每隔5秒发送一次心跳 lease-expiration-duration-in-seconds: 10 # 10秒不发送就过期 leyou: jwt: secret: leyou@Login(Auth}*^31)&heiMa% # 登录校验的密钥 pubKeyPath: C:\\tmp\\rsa\\rsa.pub # 公钥地址 priKeyPath: C:\\tmp\\rsa\\rsa.pri # 私钥地址 expire: 30 # 过期时间,单位分钟 cookieName: LY_TOKEN # cookie的name cookieMaxAge: 30 # cookie的存活时间 ================================================ FILE: ly-auth/ly-auth-service/src/test/java/com/leyou/auth/test/JwtTest.java ================================================ package com.leyou.auth.test; import com.leyou.auth.entity.UserInfo; import com.leyou.auth.utils.JwtUtils; import com.leyou.auth.utils.RsaUtils; import org.junit.Before; import org.junit.Test; import java.security.PrivateKey; import java.security.PublicKey; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-14 10:00 */ public class JwtTest { private static final String pubKeyPath = "C:\\tmp\\rsa\\rsa.pub"; private static final String priKeyPath = "C:\\tmp\\rsa\\rsa.pri"; private PublicKey publicKey; private PrivateKey privateKey; /** * 生成公钥和私钥 * * @throws Exception */ @Test public void testRsa() throws Exception { RsaUtils.generateKey(pubKeyPath, priKeyPath, "234"); } @Before public void testGetRsa() throws Exception { this.publicKey = RsaUtils.getPublicKey(pubKeyPath); this.privateKey = RsaUtils.getPrivateKey(priKeyPath); } /** * 生成token * * @throws Exception */ @Test public void testGenerateToken() throws Exception { // 生成token String token = JwtUtils.generateToken(new UserInfo(20L, "jack"), privateKey, 5); System.out.println("token = " + token); } /** * 解析token * * @throws Exception */ @Test public void testParseToken() throws Exception { String token = "eyJhbGciOiJSUzI1NiJ9.eyJpZCI6MjAsInVzZXJuYW1lIjoiamFjayIsImV4cCI6MTU3MzY5NzQ4MH0.hf5PNQvy0H9K_Tz-wvSne3NdjshsmbXs1E_MtFGzwFALcxM8BxGa1v8U2Cq258SeP6UhWf3Xa7EA3dhgLbv4Fd0Rxfwk5HKAZ88Zo3JSsl94o-1d6lw9D0ozx4XLo3hELcICOUj0m94XdszeFRa0N4YDVgqpIyJvlkCHkuqwVOo"; // 解析token UserInfo user = JwtUtils.getInfoFromToken(token, publicKey); System.out.println("id: " + user.getId()); System.out.println("userName: " + user.getUsername()); } } ================================================ FILE: ly-auth/pom.xml ================================================ leyou com.leyou.parent 1.0.0-SNAPSHOT 4.0.0 com.leyou.auth ly-auth 1.0.0-SNAPSHOT 授权中心模块 ly-auth-common ly-auth-service pom ================================================ FILE: ly-cart/pom.xml ================================================ leyou com.leyou.parent 1.0.0-SNAPSHOT 4.0.0 com.leyou.cart ly-cart 1.0.0-SNAPSHOT 购物车服务模块 org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-openfeign org.springframework.boot spring-boot-starter-data-redis org.springframework.boot spring-boot-configuration-processor true org.projectlombok lombok com.leyou.auth ly-auth-common ${leyou.latest.version} com.leyou.common ly-common ${leyou.latest.version} com.leyou.service ly-item-interface ${leyou.latest.version} ================================================ FILE: ly-cart/src/main/java/com/leyou/cart/LyCartApplication.java ================================================ package com.leyou.cart; 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: TianCi.Xiong * @Description: * @Date: Created in 2019-11-14 13:07 */ @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class LyCartApplication { public static void main(String[] args) { SpringApplication.run(LyCartApplication.class, args); } } ================================================ FILE: ly-cart/src/main/java/com/leyou/cart/client/GoodsClient.java ================================================ package com.leyou.cart.client; import com.leyou.item.api.GoodsApi; import org.springframework.cloud.openfeign.FeignClient; @FeignClient("item-service") public interface GoodsClient extends GoodsApi { } ================================================ FILE: ly-cart/src/main/java/com/leyou/cart/config/JwtProperties.java ================================================ package com.leyou.cart.config; import com.leyou.auth.utils.RsaUtils; import lombok.Getter; import lombok.Setter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; import javax.annotation.PostConstruct; import java.security.PublicKey; /** * @Author: TianCi.Xiong * @Description: JWT配置类 * @Date: Created in 2019-11-14 15:39 */ @Getter @Setter @ConfigurationProperties(prefix = "leyou.jwt") public class JwtProperties { private String pubKeyPath;// 公钥 private PublicKey publicKey; // 公钥 private String cookieName; private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class); @PostConstruct public void init() { try { // 获取公钥和私钥 this.publicKey = RsaUtils.getPublicKey(pubKeyPath); } catch (Exception e) { logger.error("初始化公钥失败!", e); throw new RuntimeException(); } } } ================================================ FILE: ly-cart/src/main/java/com/leyou/cart/config/MvcConfig.java ================================================ package com.leyou.cart.config; import com.leyou.cart.interceptor.LoginInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @Author: TianCi.Xiong * @Description: SpringMVC配置类 * @Date: Created in 2019-11-14 15:44 */ @Configuration @EnableConfigurationProperties(JwtProperties.class) public class MvcConfig implements WebMvcConfigurer { @Autowired private JwtProperties jwtProperties; @Bean public LoginInterceptor loginInterceptor() { return new LoginInterceptor(jwtProperties); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor()).addPathPatterns("/**"); } } ================================================ FILE: ly-cart/src/main/java/com/leyou/cart/controller/CartController.java ================================================ package com.leyou.cart.controller; import com.leyou.cart.pojo.Cart; import com.leyou.cart.service.CartService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-14 17:35 */ @Controller public class CartController { @Autowired private CartService cartService; /** * 添加购物车 * * @param cart * @return */ @PostMapping public ResponseEntity addCart(@RequestBody Cart cart) { this.cartService.addCart(cart); return ResponseEntity.ok().build(); } /** * 查询购物车列表 * * @return */ @GetMapping public ResponseEntity> queryCartList() { List carts = this.cartService.queryCartList(); if (carts == null) { // return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); // 购物车为空时返回空数组 return ResponseEntity.ok(new ArrayList()); } return ResponseEntity.ok(carts); } /** * 修改购物车数量 * * @param cart * @return */ @PutMapping public ResponseEntity updateNum(@RequestBody Cart cart) { this.cartService.updateCarts(cart); return ResponseEntity.noContent().build(); } /** * 删除购物车中的商品 * * @param skuId * @return */ @DeleteMapping("/{skuId}") public ResponseEntity deleteCart(@PathVariable("skuId") String skuId) { this.cartService.deleteCart(skuId); return ResponseEntity.ok().build(); } } ================================================ FILE: ly-cart/src/main/java/com/leyou/cart/interceptor/LoginInterceptor.java ================================================ package com.leyou.cart.interceptor; import com.leyou.auth.entity.UserInfo; import com.leyou.auth.utils.JwtUtils; import com.leyou.cart.config.JwtProperties; import com.leyou.common.utils.CookieUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpStatus; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @Author: TianCi.Xiong * @Description: SpringMVC拦截器 * @Date: Created in 2019-11-14 15:42 */ public class LoginInterceptor extends HandlerInterceptorAdapter { private JwtProperties jwtProperties; // 定义一个线程域,存放登录用户 private static final ThreadLocal tl = new ThreadLocal<>(); public LoginInterceptor(JwtProperties jwtProperties) { this.jwtProperties = jwtProperties; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 查询token String token = CookieUtils.getCookieValue(request, "LY_TOKEN"); if (StringUtils.isBlank(token)) { // 未登录,返回401 response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } // 有token,查询用户信息 try { // 解析成功,证明已经登录 UserInfo user = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey()); // 放入线程域 tl.set(user); return true; } catch (Exception e) { // 抛出异常,证明未登录,返回401 response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { tl.remove(); } public static UserInfo getLoginUser() { return tl.get(); } } ================================================ FILE: ly-cart/src/main/java/com/leyou/cart/pojo/Cart.java ================================================ package com.leyou.cart.pojo; import lombok.Data; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-14 15:47 */ @Data public class Cart { private Long userId;// 用户id private Long skuId;// 商品id private String title;// 标题 private String image;// 图片 private Long price;// 加入购物车时的价格 private Integer num;// 购买数量 private String ownSpec;// 商品规格参数 } ================================================ FILE: ly-cart/src/main/java/com/leyou/cart/service/CartService.java ================================================ package com.leyou.cart.service; import com.leyou.auth.entity.UserInfo; import com.leyou.cart.client.GoodsClient; import com.leyou.cart.interceptor.LoginInterceptor; import com.leyou.cart.pojo.Cart; import com.leyou.common.utils.JsonUtils; import com.leyou.item.pojo.Sku; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.BoundHashOperations; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import java.util.List; import java.util.stream.Collectors; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-14 17:35 */ @Service public class CartService { @Autowired private StringRedisTemplate redisTemplate; @Autowired private GoodsClient goodsClient; static final String KEY_PREFIX = "leyou:cart:uid:"; static final Logger logger = LoggerFactory.getLogger(CartService.class); /** * 添加购物车 * * @param cart */ public void addCart(Cart cart) { // 获取登录用户 UserInfo user = LoginInterceptor.getLoginUser(); // Redis的key String key = KEY_PREFIX + user.getId(); // 获取hash操作对象 BoundHashOperations hashOps = this.redisTemplate.boundHashOps(key); // 查询是否存在 Long skuId = cart.getSkuId(); Integer num = cart.getNum(); Boolean boo = hashOps.hasKey(skuId.toString()); if (boo) { // 存在,获取购物车数据 String json = hashOps.get(skuId.toString()).toString(); cart = JsonUtils.parse(json, Cart.class); // 修改购物车数量 cart.setNum(cart.getNum() + num); } else { // 不存在,新增购物车数据 cart.setUserId(user.getId()); // 其它商品信息,需要查询商品服务 Sku sku = this.goodsClient.querySkuById(skuId); cart.setImage(StringUtils.isBlank(sku.getImages()) ? "" : StringUtils.split(sku.getImages(), ",")[0]); cart.setPrice(sku.getPrice()); cart.setTitle(sku.getTitle()); cart.setOwnSpec(sku.getOwnSpec()); } // 将购物车数据写入redis hashOps.put(cart.getSkuId().toString(), JsonUtils.serialize(cart)); } /** * 查询购物车列表 * * @return */ public List queryCartList() { // 获取登录用户 UserInfo user = LoginInterceptor.getLoginUser(); // 判断是否存在购物车 String key = KEY_PREFIX + user.getId(); if (!this.redisTemplate.hasKey(key)) { // 不存在,直接返回 return null; } BoundHashOperations hashOps = this.redisTemplate.boundHashOps(key); List carts = hashOps.values(); // 判断是否有数据 if (CollectionUtils.isEmpty(carts)) { return null; } // 查询购物车数据 return carts.stream().map(o -> JsonUtils.parse(o.toString(), Cart.class)).collect(Collectors.toList()); } /** * 修改购物车数量 * * @param cart */ public void updateCarts(Cart cart) { // 获取登陆信息 UserInfo userInfo = LoginInterceptor.getLoginUser(); String key = KEY_PREFIX + userInfo.getId(); // 获取hash操作对象 BoundHashOperations hashOperations = this.redisTemplate.boundHashOps(key); // 获取购物车信息 String cartJson = hashOperations.get(cart.getSkuId().toString()).toString(); Cart cart1 = JsonUtils.parse(cartJson, Cart.class); // 更新数量 cart1.setNum(cart.getNum()); // 写入购物车 hashOperations.put(cart.getSkuId().toString(), JsonUtils.serialize(cart1)); } /** * 删除购物车中的商品 * * @param skuId */ public void deleteCart(String skuId) { // 获取登录用户 UserInfo user = LoginInterceptor.getLoginUser(); String key = KEY_PREFIX + user.getId(); BoundHashOperations hashOps = this.redisTemplate.boundHashOps(key); hashOps.delete(skuId); } } ================================================ FILE: ly-cart/src/main/resources/application.yml ================================================ server: port: 8088 spring: application: name: cart-service redis: host: 192.168.56.101 eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka registry-fetch-interval-seconds: 10 instance: lease-renewal-interval-in-seconds: 5 lease-expiration-duration-in-seconds: 15 leyou: jwt: pubKeyPath: C:/tmp/rsa/rsa.pub # 公钥地址 cookieName: LY_TOKEN # cookie的名称 ================================================ FILE: ly-common/pom.xml ================================================ leyou com.leyou.parent 1.0.0-SNAPSHOT 4.0.0 com.leyou.common ly-common 1.0.0-SNAPSHOT 通用工具模块 org.springframework.boot spring-boot-starter-logging org.apache.commons commons-lang3 org.projectlombok lombok org.apache.tomcat.embed tomcat-embed-core com.fasterxml.jackson.core jackson-databind ================================================ FILE: ly-common/src/main/java/com/leyou/common/pojo/PageResult.java ================================================ package com.leyou.common.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; /** * @Author: TianCi.Xiong * @Description: 分页结果封装类 * @Date: Created in 2019-11-02 9:20 */ @Data @NoArgsConstructor @AllArgsConstructor public class PageResult { private Long total;// 总条数 private Integer totalPage;// 总页数 private List items;// 当前页数据 public PageResult(Long total, List items) { this.total = total; this.items = items; } } ================================================ FILE: ly-common/src/main/java/com/leyou/common/utils/CookieUtils.java ================================================ package com.leyou.common.utils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; /** * @Author: TianCi.Xiong * @Description: Cookie工具类 * @Date: Created in 2019-11-14 11:01 */ public class CookieUtils { static final Logger logger = LoggerFactory.getLogger(CookieUtils.class); /** * 得到Cookie的值, 不编码 * * @param request * @param cookieName * @return */ public static String getCookieValue(HttpServletRequest request, String cookieName) { return getCookieValue(request, cookieName, false); } /** * 得到Cookie的值, * * @param request * @param cookieName * @return */ public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) { Cookie[] cookieList = request.getCookies(); if (cookieList == null || cookieName == null) { return null; } String retValue = null; try { for (int i = 0; i < cookieList.length; i++) { if (cookieList[i].getName().equals(cookieName)) { if (isDecoder) { retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8"); } else { retValue = cookieList[i].getValue(); } break; } } } catch (UnsupportedEncodingException e) { logger.error("Cookie Decode Error.", e); } return retValue; } /** * 得到Cookie的值, * * @param request * @param cookieName * @return */ public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) { Cookie[] cookieList = request.getCookies(); if (cookieList == null || cookieName == null) { return null; } String retValue = null; try { for (int i = 0; i < cookieList.length; i++) { if (cookieList[i].getName().equals(cookieName)) { retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString); break; } } } catch (UnsupportedEncodingException e) { logger.error("Cookie Decode Error.", e); } return retValue; } /** * 生成cookie,并指定编码 * * @param request 请求 * @param response 响应 * @param cookieName name * @param cookieValue value * @param encodeString 编码 */ public static final void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, String encodeString) { setCookie(request, response, cookieName, cookieValue, null, encodeString, null); } /** * 生成cookie,并指定生存时间 * * @param request 请求 * @param response 响应 * @param cookieName name * @param cookieValue value * @param cookieMaxAge 生存时间 */ public static final void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, Integer cookieMaxAge) { setCookie(request, response, cookieName, cookieValue, cookieMaxAge, null, null); } /** * 设置cookie,不指定httpOnly属性 */ public static final void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, Integer cookieMaxAge, String encodeString) { setCookie(request, response, cookieName, cookieValue, cookieMaxAge, encodeString, null); } /** * 设置Cookie的值,并使其在指定时间内生效 * * @param cookieMaxAge cookie生效的最大秒数 */ public static final void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, Integer cookieMaxAge, String encodeString, Boolean httpOnly) { try { if (StringUtils.isBlank(encodeString)) { encodeString = "utf-8"; } if (cookieValue == null) { cookieValue = ""; } else { cookieValue = URLEncoder.encode(cookieValue, encodeString); } Cookie cookie = new Cookie(cookieName, cookieValue); if (cookieMaxAge != null && cookieMaxAge > 0) cookie.setMaxAge(cookieMaxAge); if (null != request)// 设置域名的cookie cookie.setDomain(getDomainName(request)); cookie.setPath("/"); if (httpOnly != null) { cookie.setHttpOnly(httpOnly); } response.addCookie(cookie); } catch (Exception e) { logger.error("Cookie Encode Error.", e); } } /** * 得到cookie的域名 */ private static final String getDomainName(HttpServletRequest request) { String domainName = null; String serverName = request.getRequestURL().toString(); if (serverName == null || serverName.equals("")) { domainName = ""; } else { serverName = serverName.toLowerCase(); serverName = serverName.substring(7); final int end = serverName.indexOf("/"); serverName = serverName.substring(0, end); final String[] domains = serverName.split("\\."); int len = domains.length; if (len > 3) { // www.xxx.com.cn domainName = domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1]; } else if (len <= 3 && len > 1) { // xxx.com or xxx.cn domainName = domains[len - 2] + "." + domains[len - 1]; } else { domainName = serverName; } } if (domainName != null && domainName.indexOf(":") > 0) { String[] ary = domainName.split("\\:"); domainName = ary[0]; } return domainName; } } ================================================ FILE: ly-common/src/main/java/com/leyou/common/utils/IdWorker.java ================================================ package com.leyou.common.utils; import java.lang.management.ManagementFactory; import java.net.InetAddress; import java.net.NetworkInterface; /** *

名称:IdWorker.java

*

描述:分布式自增长ID

*
 *     Twitter的 Snowflake JAVA实现方案
 * 
* 核心代码为其IdWorker这个类实现,其原理结构如下,我分别用一个0表示一位,用—分割开部分的作用: * 1||0---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000 * 在上面的字符串中,第一位为未使用(实际上也可作为long的符号位),接下来的41位为毫秒级时间, * 然后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识), * 然后12位该毫秒内的当前毫秒内的计数,加起来刚好64位,为一个Long型。 * 这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID作区分), * 并且效率较高,经测试,snowflake每秒能够产生26万ID左右,完全满足需要。 *

* 64位ID (42(毫秒)+5(机器ID)+5(业务编码)+12(重复累加)) * * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-15 9:57 */ public class IdWorker { // 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动) private final static long twepoch = 1288834974657L; // 机器标识位数 private final static long workerIdBits = 5L; // 数据中心标识位数 private final static long datacenterIdBits = 5L; // 机器ID最大值 private final static long maxWorkerId = -1L ^ (-1L << workerIdBits); // 数据中心ID最大值 private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); // 毫秒内自增位 private final static long sequenceBits = 12L; // 机器ID偏左移12位 private final static long workerIdShift = sequenceBits; // 数据中心ID左移17位 private final static long datacenterIdShift = sequenceBits + workerIdBits; // 时间毫秒左移22位 private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; private final static long sequenceMask = -1L ^ (-1L << sequenceBits); /* 上次生产id时间戳 */ private static long lastTimestamp = -1L; // 0,并发控制 private long sequence = 0L; private final long workerId; // 数据标识id部分 private final long datacenterId; public IdWorker() { this.datacenterId = getDatacenterId(maxDatacenterId); this.workerId = getMaxWorkerId(datacenterId, maxWorkerId); } /** * @param workerId 工作机器ID * @param datacenterId 序列号 */ public IdWorker(long workerId, long datacenterId) { if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); } if (datacenterId > maxDatacenterId || datacenterId < 0) { throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); } this.workerId = workerId; this.datacenterId = datacenterId; } /** * 获取下一个ID * * @return */ public synchronized long nextId() { long timestamp = timeGen(); if (timestamp < lastTimestamp) { throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } if (lastTimestamp == timestamp) { // 当前毫秒内,则+1 sequence = (sequence + 1) & sequenceMask; if (sequence == 0) { // 当前毫秒内计数满了,则等待下一秒 timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0L; } lastTimestamp = timestamp; // ID偏移组合生成最终的ID,并返回ID long nextId = ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence; return nextId; } private long tilNextMillis(final long lastTimestamp) { long timestamp = this.timeGen(); while (timestamp <= lastTimestamp) { timestamp = this.timeGen(); } return timestamp; } private long timeGen() { return System.currentTimeMillis(); } /** *

* 获取 maxWorkerId *

*/ protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) { StringBuffer mpid = new StringBuffer(); mpid.append(datacenterId); String name = ManagementFactory.getRuntimeMXBean().getName(); if (!name.isEmpty()) { /* * GET jvmPid */ mpid.append(name.split("@")[0]); } /* * MAC + PID 的 hashcode 获取16个低位 */ return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1); } /** *

* 数据标识id部分 *

*/ protected static long getDatacenterId(long maxDatacenterId) { long id = 0L; try { InetAddress ip = InetAddress.getLocalHost(); NetworkInterface network = NetworkInterface.getByInetAddress(ip); if (network == null) { id = 1L; } else { byte[] mac = network.getHardwareAddress(); id = ((0x000000FF & (long) mac[mac.length - 1]) | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6; id = id % (maxDatacenterId + 1); } } catch (Exception e) { System.out.println(" getDatacenterId: " + e.getMessage()); } return id; } } ================================================ FILE: ly-common/src/main/java/com/leyou/common/utils/JsonUtils.java ================================================ package com.leyou.common.utils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.sun.istack.internal.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.List; import java.util.Map; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-14 17:43 */ public class JsonUtils { public static final ObjectMapper mapper = new ObjectMapper(); private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class); @Nullable public static String serialize(Object obj) { if (obj == null) { return null; } if (obj.getClass() == String.class) { return (String) obj; } try { return mapper.writeValueAsString(obj); } catch (JsonProcessingException e) { logger.error("json序列化出错:" + obj, e); return null; } } @Nullable public static T parse(String json, Class tClass) { try { return mapper.readValue(json, tClass); } catch (IOException e) { logger.error("json解析出错:" + json, e); return null; } } @Nullable public static List parseList(String json, Class eClass) { try { return mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, eClass)); } catch (IOException e) { logger.error("json解析出错:" + json, e); return null; } } @Nullable public static Map parseMap(String json, Class kClass, Class vClass) { try { return mapper.readValue(json, mapper.getTypeFactory().constructMapType(Map.class, kClass, vClass)); } catch (IOException e) { logger.error("json解析出错:" + json, e); return null; } } @Nullable public static T nativeRead(String json, TypeReference type) { try { return mapper.readValue(json, type); } catch (IOException e) { logger.error("json解析出错:" + json, e); return null; } } } ================================================ FILE: ly-common/src/main/java/com/leyou/common/utils/NumberUtils.java ================================================ package com.leyou.common.utils; import java.util.Random; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-13 17:29 */ public class NumberUtils { /** * 生成指定位数的随机数字 * * @param len 随机数的位数 * @return 生成的随机数 */ public static String generateCode(int len) { len = Math.min(len, 8); int min = Double.valueOf(Math.pow(10, len - 1)).intValue(); int num = new Random().nextInt( Double.valueOf(Math.pow(10, len + 1)).intValue() - 1) + min; return String.valueOf(num).substring(0, len); } } ================================================ FILE: ly-goods-web/pom.xml ================================================ leyou com.leyou.parent 1.0.0-SNAPSHOT 4.0.0 com.leyou.goods ly-goods-web 1.0.0-SNAPSHOT 商品详情页服务模块 org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-openfeign org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-amqp com.leyou.service ly-item-interface 1.0.0-SNAPSHOT ================================================ FILE: ly-goods-web/src/main/java/com/leyou/LyGoodsWebApplication.java ================================================ package com.leyou; 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: TianCi.Xiong * @Description: * @Date: Created in 2019-11-11 13:33 */ @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class LyGoodsWebApplication { public static void main(String[] args) { SpringApplication.run(LyGoodsWebApplication.class, args); } } ================================================ FILE: ly-goods-web/src/main/java/com/leyou/client/BrandClient.java ================================================ package com.leyou.client; import com.leyou.item.api.BrandApi; import org.springframework.cloud.openfeign.FeignClient; @FeignClient(value = "item-service") public interface BrandClient extends BrandApi { } ================================================ FILE: ly-goods-web/src/main/java/com/leyou/client/CategoryClient.java ================================================ package com.leyou.client; import com.leyou.item.api.CategoryApi; import org.springframework.cloud.openfeign.FeignClient; @FeignClient(value = "item-service") public interface CategoryClient extends CategoryApi { } ================================================ FILE: ly-goods-web/src/main/java/com/leyou/client/GoodsClient.java ================================================ package com.leyou.client; import com.leyou.item.api.GoodsApi; import org.springframework.cloud.openfeign.FeignClient; @FeignClient(value = "item-service") public interface GoodsClient extends GoodsApi { } ================================================ FILE: ly-goods-web/src/main/java/com/leyou/client/SpecificationClient.java ================================================ package com.leyou.client; import com.leyou.item.api.SpecificationApi; import org.springframework.cloud.openfeign.FeignClient; @FeignClient(value = "item-service") public interface SpecificationClient extends SpecificationApi { } ================================================ FILE: ly-goods-web/src/main/java/com/leyou/controller/GoodsController.java ================================================ package com.leyou.controller; import com.leyou.service.GoodsService; import com.leyou.service.GoodsHtmlService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import java.util.Map; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-11 13:56 */ @Controller @RequestMapping("/item") public class GoodsController { @Autowired private GoodsService goodsService; @Autowired private GoodsHtmlService goodsHtmlService; /** * 跳转到商品详情页 * * @param model * @param id * @return */ @GetMapping("/{id}.html") public String toItemPage(Model model, @PathVariable("id") Long id) { // 加载所需的数据 Map modelMap = this.goodsService.loadModel(id); // 放入模型 model.addAllAttributes(modelMap); // 页面静态化 this.goodsHtmlService.asyncExcute(id); return "/item"; } } ================================================ FILE: ly-goods-web/src/main/java/com/leyou/listener/GoodsListener.java ================================================ package com.leyou.listener; import com.leyou.service.GoodsHtmlService; import org.springframework.amqp.core.ExchangeTypes; import org.springframework.amqp.rabbit.annotation.Exchange; import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.QueueBinding; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * @Author: TianCi.Xiong * @Description: mq监听器 * @Date: Created in 2019-11-13 11:41 */ @Component public class GoodsListener { @Autowired private GoodsHtmlService goodsHtmlService; /** * 处理insert和update的消息 * * @param id * @throws Exception */ @RabbitListener(bindings = @QueueBinding( value = @Queue(value = "leyou.create.web.queue", durable = "true"), exchange = @Exchange( value = "leyou.item.exchange", ignoreDeclarationExceptions = "true", type = ExchangeTypes.TOPIC), key = {"item.insert", "item.update"})) public void listenCreate(Long id) throws Exception { if (id == null) { return; } // 创建页面 goodsHtmlService.createHtml(id); } /** * 处理delete的消息 * * @param id */ @RabbitListener(bindings = @QueueBinding( value = @Queue(value = "leyou.delete.web.queue", durable = "true"), exchange = @Exchange( value = "leyou.item.exchange", ignoreDeclarationExceptions = "true", type = ExchangeTypes.TOPIC), key = "item.delete")) public void listenDelete(Long id) { if (id == null) { return; } // 删除页面 goodsHtmlService.deleteHtml(id); } } ================================================ FILE: ly-goods-web/src/main/java/com/leyou/service/GoodsHtmlService.java ================================================ package com.leyou.service; import com.leyou.utils.ThreadUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; import java.io.File; import java.io.PrintWriter; import java.util.Map; /** * @Author: TianCi.Xiong * @Description: 整合Thymeleaf静态化商品详情页 * @Date: Created in 2019-11-12 13:46 */ @Service public class GoodsHtmlService { @Autowired private GoodsService goodsService; @Autowired private TemplateEngine templateEngine; private static final Logger logger = LoggerFactory.getLogger(GoodsHtmlService.class); /** * 创建html页面 * * @param spuId */ public void createHtml(Long spuId) { PrintWriter writer = null; try { // 获取页面数据 Map spuMap = this.goodsService.loadModel(spuId); // 创建thymeleaf上下文对象 Context context = new Context(); // 把数据放入上下文对象 context.setVariables(spuMap); // 创建输出流 File file = new File("D:\\JAVA\\nginx-1.12.2\\html\\leyou\\" + spuId + ".html"); writer = new PrintWriter(file); // 执行页面静态化方法 templateEngine.process("item", context, writer); } catch (Exception e) { logger.error("页面静态化出错:{}," + e, spuId); } finally { if (writer != null) { writer.close(); } } } /** * 新建线程处理页面静态化 * * @param spuId */ public void asyncExcute(Long spuId) { ThreadUtils.execute(() -> createHtml(spuId)); /*ThreadUtils.execute(new Runnable() { @Override public void run() { createHtml(spuId); } });*/ } /** * 删除页面 * * @param id */ public void deleteHtml(Long id) { File file = new File("D:\\JAVA\\nginx-1.12.2\\html\\leyou\\", id + ".html"); file.deleteOnExit(); } } ================================================ FILE: ly-goods-web/src/main/java/com/leyou/service/GoodsService.java ================================================ package com.leyou.service; import com.leyou.client.BrandClient; import com.leyou.client.CategoryClient; import com.leyou.client.GoodsClient; import com.leyou.client.SpecificationClient; import com.leyou.item.pojo.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.*; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-11 14:21 */ @Service public class GoodsService { @Autowired private BrandClient brandClient; @Autowired private CategoryClient categoryClient; @Autowired private GoodsClient goodsClient; @Autowired private SpecificationClient specificationClient; /** * 封装数据模型 * * @param spuId * @return */ public Map loadModel(Long spuId) { Map map = new HashMap<>(); // 根据id查询spu对象 Spu spu = this.goodsClient.querySpuById(spuId); // 查询spudetail SpuDetail spuDetail = this.goodsClient.querySpuDetailBySpuId(spuId); // 查询sku集合 List skus = this.goodsClient.querySkusBySpuId(spuId); // 查询分类 List cids = Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()); List names = this.categoryClient.queryNameByIds(cids); List> categories = new ArrayList<>(); for (int i = 0; i < cids.size(); i++) { Map categoryMap = new HashMap<>(); categoryMap.put("id", cids.get(i)); categoryMap.put("name", names.get(i)); categories.add(categoryMap); } // 查询品牌 Brand brand = this.brandClient.queryBrandById(spu.getBrandId()); // 查询规格参数组 List groups = this.specificationClient.querySpecsByCid(spu.getCid3()); // 查询特殊的规格参数 List params = this.specificationClient.querySpecParam(null, spu.getCid3(), false, null); Map paramMap = new HashMap<>(); params.forEach(param -> { paramMap.put(param.getId(), param.getName()); }); // 封装spu map.put("spu", spu); // 封装spuDetail map.put("spuDetail", spuDetail); // 封装sku集合 map.put("skus", skus); // 分类 map.put("categories", categories); // 品牌 map.put("brand", brand); // 规格参数组 map.put("groups", groups); // 查询特殊规格参数 map.put("paramMap", paramMap); return map; } } ================================================ FILE: ly-goods-web/src/main/java/com/leyou/utils/ThreadUtils.java ================================================ package com.leyou.utils; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @Author: TianCi.Xiong * @Description: 线程工具类 * @Date: Created in 2019-11-12 13:49 */ public class ThreadUtils { private static final ExecutorService es = Executors.newFixedThreadPool(10); public static void execute(Runnable runnable) { es.submit(runnable); } } ================================================ FILE: ly-goods-web/src/main/resources/application.yml ================================================ server: port: 8084 spring: application: name: goods-web thymeleaf: cache: false rabbitmq: host: 192.168.56.101 username: leyou password: 123456 virtual-host: /leyou eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka instance: lease-renewal-interval-in-seconds: 5 # 每隔5秒发送一次心跳 lease-expiration-duration-in-seconds: 10 # 10秒不发送就过期 ================================================ FILE: ly-goods-web/src/main/resources/templates/item.html ================================================  乐优商城--商品详情页

价  格
¥降价通知
累计评价612188
促  销
加价购 满999.00另加20.00元,或满1999.00另加30.00元,或满2999.00另加40.00元,即可在购物车换 购热销商品
支  持
以旧换新,闲置手机回收 4G套餐超值抢 礼品购
配 送 至
上海 有货
{{paramMap[k]}}
{{str}} 
  • 手机
  • 手机壳
  • 内存卡
  • Iphone配件
  • 贴膜
  • 手机耳机
  • 移动电源
  • 平板电脑

推荐品牌

选择搭配

¥5299 +
  • Feless费勒斯VR
  • Feless费勒斯VR
  • Feless费勒斯VR
  • Feless费勒斯VR
已选购0件商品
套餐价
¥5299
  • {{i}}:{{param}}

{{group.name}}

{{param.name}}
{{param.v + (param.unit || '')}}

包装清单

售后保障

商品评价

手机社区

================================================ FILE: ly-item/ly-item-interface/pom.xml ================================================ ly-item com.leyou.service 1.0.0-SNAPSHOT 4.0.0 com.leyou.service ly-item-interface 1.0.0-SNAPSHOT javax.persistence persistence-api 1.0 org.springframework spring-webmvc 5.0.6.RELEASE org.projectlombok lombok com.leyou.common ly-common 1.0.0-SNAPSHOT ================================================ FILE: ly-item/ly-item-interface/src/main/java/com/leyou/item/api/BrandApi.java ================================================ package com.leyou.item.api; import com.leyou.item.pojo.Brand; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; /** * 提供品牌接口 */ @RequestMapping("/brand") public interface BrandApi { /** * 通过id查询品牌 * * @param id * @return */ @GetMapping("/{id}") Brand queryBrandById(@PathVariable("id") Long id); /** * 通过ids查询品牌 * * @param ids * @return */ @GetMapping("/list") List queryBrandByIds(@RequestParam("ids") List ids); } ================================================ FILE: ly-item/ly-item-interface/src/main/java/com/leyou/item/api/CategoryApi.java ================================================ package com.leyou.item.api; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; /** * 提供商品分类服务接口 */ @RequestMapping("/category") public interface CategoryApi { /** * 根据商品分类id查询名称 * * @param ids 要查询的分类id集合 * @return 多个名称的集合 */ @GetMapping("/names") List queryNameByIds(@RequestParam("ids") List ids); } ================================================ FILE: ly-item/ly-item-interface/src/main/java/com/leyou/item/api/GoodsApi.java ================================================ package com.leyou.item.api; import com.leyou.common.pojo.PageResult; import com.leyou.item.bo.SpuBo; import com.leyou.item.pojo.Sku; import com.leyou.item.pojo.Spu; import com.leyou.item.pojo.SpuDetail; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; /** * 提供商品服务接口 */ public interface GoodsApi { /** * 分页查询商品 * * @param page * @param rows * @param key * @param saleable * @return */ @GetMapping("/spu/page") PageResult querySpuByPage( @RequestParam(value = "page", defaultValue = "1") Integer page, @RequestParam(value = "rows", defaultValue = "5") Integer rows, @RequestParam(value = "key", required = false) String key, @RequestParam(value = "saleable", required = false) Boolean saleable ); /** * 根据spu商品id查询详情 * * @param spuId * @return */ @GetMapping("/spu/detail/{spuId}") SpuDetail querySpuDetailBySpuId(@PathVariable("spuId") Long spuId); /** * 根据spu的id查询sku * * @param spuId * @return */ @GetMapping("/sku/list") List querySkusBySpuId(@RequestParam("id") Long spuId); /** * 根据spu_id查询spu * * @param id * @return */ @GetMapping("/spu/{id}") Spu querySpuById(@PathVariable("id") Long id); /** * 通过id查询sku * * @param id * @return */ @GetMapping("/sku/{id}") Sku querySkuById(@PathVariable("id") Long id); } ================================================ FILE: ly-item/ly-item-interface/src/main/java/com/leyou/item/api/SpecificationApi.java ================================================ package com.leyou.item.api; import com.leyou.item.pojo.SpecGroup; import com.leyou.item.pojo.SpecParam; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; /** * 提供规格参数接口 */ @RequestMapping("/spec") public interface SpecificationApi { /** * 根据条件查询规格参数 * * @param gid * @param cid * @param generic * @param searching * @return */ @GetMapping("/params") List querySpecParam( @RequestParam(value = "gid", required = false) Long gid, @RequestParam(value = "cid", required = false) Long cid, @RequestParam(value = "generic", required = false) Boolean generic, @RequestParam(value = "searching", required = false) Boolean searching ); /** * 通过商品分类id查询分组 * * @param cid * @return */ @GetMapping("/groups/{cid}") List queryGroupsByCid(@PathVariable("cid") Long cid); /** * 查询规格参数组,及组内参数 * * @param cid * @return */ @GetMapping("/{cid}") List querySpecsByCid(@PathVariable("cid") Long cid); } ================================================ FILE: ly-item/ly-item-interface/src/main/java/com/leyou/item/bo/SpuBo.java ================================================ package com.leyou.item.bo; import com.leyou.item.pojo.Sku; import com.leyou.item.pojo.Spu; import com.leyou.item.pojo.SpuDetail; import lombok.Data; import javax.persistence.Transient; import java.util.List; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-05 8:24 */ @Data public class SpuBo extends Spu { @Transient String cname;// 商品分类名称 @Transient String bname;// 品牌名称 @Transient SpuDetail spuDetail;// 商品详情 @Transient List skus;// sku列表 } ================================================ FILE: ly-item/ly-item-interface/src/main/java/com/leyou/item/pojo/Brand.java ================================================ package com.leyou.item.pojo; import lombok.Data; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; /** * @Author: TianCi.Xiong * @Description: 品牌表 * @Date: Created in 2019-11-02 9:17 */ @Data @Table(name = "tb_brand") public class Brand { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name;// 品牌名称 private String image;// 品牌图片 private Character letter; } ================================================ FILE: ly-item/ly-item-interface/src/main/java/com/leyou/item/pojo/Category.java ================================================ package com.leyou.item.pojo; import lombok.Data; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; /** * @Author: TianCi.Xiong * @Description: 商品类目实体 * @Date: Created in 2019-11-01 21:58 */ @Data @Table(name = "tb_category") public class Category { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private Long parentId; private Boolean isParent; // 注意isParent生成的getter和setter方法需要手动加上Is private Integer sort; } ================================================ FILE: ly-item/ly-item-interface/src/main/java/com/leyou/item/pojo/Sku.java ================================================ package com.leyou.item.pojo; import lombok.Data; import javax.persistence.*; import java.util.Date; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-05 21:39 */ @Data @Table(name = "tb_sku") public class Sku { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long spuId; private String title; private String images; private Long price; private String ownSpec;// 商品特殊规格的键值对 private String indexes;// 商品特殊规格的下标 private Boolean enable;// 是否有效,逻辑删除用 private Date createTime;// 创建时间 private Date lastUpdateTime;// 最后修改时间 @Transient private Integer stock;// 库存 } ================================================ FILE: ly-item/ly-item-interface/src/main/java/com/leyou/item/pojo/SpecGroup.java ================================================ package com.leyou.item.pojo; import lombok.Data; import javax.persistence.*; import java.util.List; /** * @Author: TianCi.Xiong * @Description: 规格参数的分组表 * @Date: Created in 2019-11-04 20:40 */ @Data @Table(name = "tb_spec_group") public class SpecGroup { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long cid; private String name; @Transient private List params; // 该组下的所有规格参数集合 } ================================================ FILE: ly-item/ly-item-interface/src/main/java/com/leyou/item/pojo/SpecParam.java ================================================ package com.leyou.item.pojo; import lombok.Data; import javax.persistence.*; /** * @Author: TianCi.Xiong * @Description: 规格参数组下的参数名 * @Date: Created in 2019-11-04 20:40 */ @Data @Table(name = "tb_spec_param") public class SpecParam { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long cid; private Long groupId; private String name; @Column(name = "`numeric`") private Boolean numeric; private String unit; private Boolean generic; private Boolean searching; private String segments; } ================================================ FILE: ly-item/ly-item-interface/src/main/java/com/leyou/item/pojo/Spu.java ================================================ package com.leyou.item.pojo; import lombok.Data; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import java.util.Date; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-05 8:21 */ @Data @Table(name = "tb_spu") public class Spu { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long brandId; private Long cid1;// 1级类目 private Long cid2;// 2级类目 private Long cid3;// 3级类目 private String title;// 标题 private String subTitle;// 子标题 private Boolean saleable;// 是否上架 private Boolean valid;// 是否有效,逻辑删除用 private Date createTime;// 创建时间 private Date lastUpdateTime;// 最后修改时间 } ================================================ FILE: ly-item/ly-item-interface/src/main/java/com/leyou/item/pojo/SpuDetail.java ================================================ package com.leyou.item.pojo; import lombok.Data; import javax.persistence.Id; import javax.persistence.Table; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-05 8:22 */ @Data @Table(name="tb_spu_detail") public class SpuDetail { @Id private Long spuId;// 对应的SPU的id private String description;// 商品描述 private String specialSpec;// 商品特殊规格的名称及可选值模板 private String genericSpec;// 商品的全局规格属性 private String packingList;// 包装清单 private String afterService;// 售后服务 } ================================================ FILE: ly-item/ly-item-interface/src/main/java/com/leyou/item/pojo/Stock.java ================================================ package com.leyou.item.pojo; import lombok.Data; import javax.persistence.Id; import javax.persistence.Table; /** * @Author: TianCi.Xiong * @Description: 库存表,代表库存,秒杀库存等信息 * @Date: Created in 2019-11-05 21:40 */ @Data @Table(name = "tb_stock") public class Stock { @Id private Long skuId; private Integer seckillStock;// 秒杀可用库存 private Integer seckillTotal;// 已秒杀数量 private Integer stock;// 正常库存 } ================================================ FILE: ly-item/ly-item-service/pom.xml ================================================ ly-item com.leyou.service 1.0.0-SNAPSHOT 4.0.0 com.leyou.service ly-item-service 1.0.0-SNAPSHOT org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.boot spring-boot-starter-web org.mybatis.spring.boot mybatis-spring-boot-starter ${mybatis.starter.version} tk.mybatis mapper-spring-boot-starter ${mapper.starter.version} com.github.pagehelper pagehelper-spring-boot-starter ${pageHelper.starter.version} mysql mysql-connector-java ${mysql.version} org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-amqp org.apache.commons commons-lang3 com.leyou.service ly-item-interface ${leyou.latest.version} com.leyou.common ly-common ${leyou.latest.version} ================================================ FILE: ly-item/ly-item-service/src/main/java/com/leyou/item/LyItemServiceApplication.java ================================================ package com.leyou.item; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import tk.mybatis.spring.annotation.MapperScan; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-01 10:27 */ @SpringBootApplication @EnableDiscoveryClient @MapperScan("com.leyou.item.mapper") // mapper接口的包扫描 public class LyItemServiceApplication { public static void main(String[] args) { SpringApplication.run(LyItemServiceApplication.class, args); } } ================================================ FILE: ly-item/ly-item-service/src/main/java/com/leyou/item/controller/BrandController.java ================================================ package com.leyou.item.controller; import com.leyou.common.pojo.PageResult; import com.leyou.item.service.BrandService; import com.leyou.item.pojo.Brand; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.*; import java.util.List; /** * @Author: TianCi.Xiong * @Description: 品牌 * @Date: Created in 2019-11-02 9:18 */ @RestController @RequestMapping("/brand") public class BrandController { @Autowired private BrandService brandService; /** * 条件查询品牌-含分页 * * @param page 当前页 * @param rows 每页大小 * @param sortBy 排序字段 * @param desc 是否为降序 * @param key 搜索关键字 * @return */ @GetMapping("/page") public ResponseEntity> queryBrandByPage( @RequestParam(value = "page", defaultValue = "1") Integer page, @RequestParam(value = "rows", defaultValue = "5") Integer rows, @RequestParam(value = "sortBy", required = false) String sortBy, @RequestParam(value = "desc", defaultValue = "false") Boolean desc, @RequestParam(value = "key", required = false) String key ) { PageResult result = this.brandService.queryBrandByPageAndSort(page, rows, sortBy, desc, key); if (result == null || result.getItems().size() == 0) { // 404 return new ResponseEntity<>(HttpStatus.NOT_FOUND); } return ResponseEntity.ok(result); } /** * 新增品牌 * * @param brand * @param cids * @return */ @PostMapping public ResponseEntity saveBrand(Brand brand, @RequestParam("cids") List cids) { this.brandService.saveBrand(brand, cids); return new ResponseEntity<>(HttpStatus.CREATED); } /** * 通过bid删除品牌 * 删除tb_brand中的数据 * * @param bid * @return */ @DeleteMapping("/{bid}") public ResponseEntity deleteBrand(@PathVariable("bid") String bid) { this.brandService.deleteBrand(Long.parseLong(bid)); return ResponseEntity.status(HttpStatus.OK).build(); } /** * 根据分类id查询品牌 * * @param cid * @return */ @GetMapping("/cid/{cid}") public ResponseEntity> queryBrandsByCid(@PathVariable("cid") Long cid) { List list = this.brandService.queryBrandsByCid(cid); if (CollectionUtils.isEmpty(list)) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(list); } /** * 通过id查询品牌 * * @param id * @return */ @GetMapping("/{id}") public ResponseEntity queryBrandById(@PathVariable("id") Long id) { Brand brand = this.brandService.queryBrandById(id); if (brand == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(brand); } /** * 通过ids查询品牌 * * @param ids * @return */ @GetMapping("/list") public ResponseEntity> queryBrandByIds(@RequestParam("ids") List ids) { List list = this.brandService.queryBrandByIds(ids); if (list == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(list); } } ================================================ FILE: ly-item/ly-item-service/src/main/java/com/leyou/item/controller/CategoryController.java ================================================ package com.leyou.item.controller; import com.leyou.item.service.CategoryService; import com.leyou.item.pojo.Category; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; /** * @Author: TianCi.Xiong * @Description: 商品类目 * @Date: Created in 2019-11-01 22:03 */ @Controller @RequestMapping("/category") public class CategoryController { @Autowired private CategoryService categoryService; /** * 根据parentId父Id查询类目 * * @param pid * @return */ @GetMapping("/list") public ResponseEntity> queryCategoryListByParentId(@RequestParam(value = "pid", defaultValue = "0") Long pid) { try { if (pid == null || pid.longValue() < 0) { // pid为null或小于等于0,响应400 return ResponseEntity.badRequest().build(); } // 执行查询操作 List categoryList = this.categoryService.queryCategoryListByParentId(pid); if (CollectionUtils.isEmpty(categoryList)) { // 返回结果集为空,响应404 return ResponseEntity.notFound().build(); } // 查询成功,响应200 return ResponseEntity.ok(categoryList); } catch (Exception e) { e.printStackTrace(); } // 响应500 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } /** * 通过品牌id查询商品分类 * * @param bid 品牌id * @return */ @GetMapping("/bid/{bid}") public ResponseEntity> queryByBrandId(@PathVariable("bid") Long bid) { List list = this.categoryService.queryByBrandId(bid); if (list == null || list.size() == 0) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } return ResponseEntity.ok(list); } /** * 根据商品分类id查询名称 * * @param ids 要查询的分类id集合 * @return 多个名称的集合 */ @GetMapping("/names") public ResponseEntity> queryNameByIds(@RequestParam("ids") List ids) { List list = this.categoryService.queryNameByIds(ids); if (list == null || list.size() < 1) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(list); } /** * 根据3级分类id,查询1~3级的分类 * * @param id * @return */ @GetMapping("/all/level") public ResponseEntity> queryAllByCid3(@RequestParam("id") Long id) { List list = this.categoryService.queryAllByCid3(id); if (list == null || list.size() < 1) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } return ResponseEntity.ok(list); } } ================================================ FILE: ly-item/ly-item-service/src/main/java/com/leyou/item/controller/GoodsController.java ================================================ package com.leyou.item.controller; import com.leyou.item.bo.SpuBo; import com.leyou.common.pojo.PageResult; import com.leyou.item.service.GoodsService; import com.leyou.item.pojo.Sku; import com.leyou.item.pojo.Spu; import com.leyou.item.pojo.SpuDetail; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.*; import java.util.List; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-05 8:25 */ @RestController public class GoodsController { @Autowired private GoodsService goodsService; /** * 新增商品 * * @param spuBo * @return */ @PostMapping("/goods") public ResponseEntity saveGoods(@RequestBody SpuBo spuBo) { try { this.goodsService.saveGoods(spuBo); return new ResponseEntity<>(HttpStatus.CREATED); } catch (Exception e) { e.printStackTrace(); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } /** * 修改商品信息 * * @param spuBo * @return */ @PutMapping("/goods") public ResponseEntity updateGoods(@RequestBody SpuBo spuBo) { this.goodsService.updateGoods(spuBo); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } /** * 通过spu_id删除商品goods * * @param spuId * @return */ @DeleteMapping("/goods/{spuId}") public ResponseEntity deleteGoods(@PathVariable("spuId") Long spuId) { try { this.goodsService.deleteGoods(spuId); return ResponseEntity.ok().build(); } catch (Exception e) { e.printStackTrace(); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } /** * 根据spu_id查询spu * * @param id * @return */ @GetMapping("/spu/{id}") public ResponseEntity querySpuById(@PathVariable("id") Long id) { Spu spu = this.goodsService.querySpuById(id); if (spu == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(spu); } /** * 分页查询商品 * * @param page * @param rows * @param key * @param saleable * @return */ @GetMapping("/spu/page") public ResponseEntity> querySpuBoByPage( @RequestParam(value = "page", defaultValue = "1") Integer page, @RequestParam(value = "rows", defaultValue = "5") Integer rows, @RequestParam(value = "key", required = false) String key, @RequestParam(value = "saleable", required = false) Boolean saleable ) { // 分页查询spu信息 PageResult result = this.goodsService.querySpuByPageAndSort(page, rows, key, saleable); if (result == null || result.getItems().size() == 0) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(result); } /** * 通过spu_id查询SPU详情 * * @param spuId * @return */ @GetMapping("/spu/detail/{spuId}") public ResponseEntity querySpuDetailBySpuId(@PathVariable("spuId") Long spuId) { SpuDetail spuDetail = this.goodsService.querySpuDetailBySpuId(spuId); if (spuDetail == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(spuDetail); } /** * 根据spu_id查询sku的集合 * * @param spuId * @return */ @GetMapping("/sku/list") public ResponseEntity> querySkusBySpuId(@RequestParam("id") Long spuId) { List skus = this.goodsService.querySkusBySpuId(spuId); if (CollectionUtils.isEmpty(skus)) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(skus); } /** * 通过id查询sku * * @param id * @return */ @GetMapping("/sku/{id}") public ResponseEntity querySkuById(@PathVariable("id") Long id) { Sku sku = this.goodsService.querySkuById(id); if (sku == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } return ResponseEntity.ok(sku); } /** * 通过spu_id修改商品上下架状态 * * @param spuId * @return */ @PutMapping("/goods/saleable/{spuId}") public ResponseEntity changeSaleable(@PathVariable("spuId") Long spuId) { this.goodsService.changeSaleable(spuId); return ResponseEntity.ok().build(); } } ================================================ FILE: ly-item/ly-item-service/src/main/java/com/leyou/item/controller/SpecificationController.java ================================================ package com.leyou.item.controller; import com.leyou.item.service.SpecificationService; import com.leyou.item.pojo.SpecGroup; import com.leyou.item.pojo.SpecParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.*; import java.util.List; /** * @Author: TianCi.Xiong * @Description: 商品规格参数 * @Date: Created in 2019-11-04 17:41 */ @RestController @RequestMapping("/spec") public class SpecificationController { @Autowired private SpecificationService specificationService; /** * 通过商品分类id查询分组 * * @param cid * @return */ @GetMapping("/groups/{cid}") public ResponseEntity> queryGroupsByCid(@PathVariable("cid") Long cid) { List groups = this.specificationService.queryGroupsByCid(cid); if (CollectionUtils.isEmpty(groups)) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(groups); } /** * 根据条件查询规格参数 * * @param gid * @param cid * @param generic * @param searching * @return */ @GetMapping("/params") public ResponseEntity> querySpecParam( @RequestParam(value = "gid", required = false) Long gid, @RequestParam(value = "cid", required = false) Long cid, @RequestParam(value = "generic", required = false) Boolean generic, @RequestParam(value = "searching", required = false) Boolean searching ) { List params = this.specificationService.querySpecParams(gid, cid, generic, searching); if (CollectionUtils.isEmpty(params)) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(params); } /** * 新增规格模板分组 * * @param specGroup * @return */ @PostMapping("/group") public ResponseEntity saveSpecGroup(@RequestBody SpecGroup specGroup) { specificationService.saveSpecGroup(specGroup); return new ResponseEntity<>(HttpStatus.OK); } /** * 更新规格模板信息 * * @param specGroup * @return */ @PutMapping("/group") public ResponseEntity updateSpecGroup(@RequestBody SpecGroup specGroup) { specificationService.updateSpecGroup(specGroup); return new ResponseEntity<>(HttpStatus.OK); } /** * 通过id删除规格模板 * * @param id * @return */ @DeleteMapping("/group/{id}") public ResponseEntity deleteSpecGroup(@PathVariable("id") Long id) { specificationService.deleteSpecGroup(id); return new ResponseEntity<>(HttpStatus.OK); } /** * 新增规格模板下的规格参数 * * @param specParam * @return */ @PostMapping("/param") public ResponseEntity saveSpecParam(@RequestBody SpecParam specParam) { specificationService.saveSpecParam(specParam); return new ResponseEntity<>(HttpStatus.CREATED); } /** * 更新规格模板下规格参数信息 * * @param specParam * @return */ @PutMapping("/param") public ResponseEntity updateSpecParam(@RequestBody SpecParam specParam) { specificationService.updateSpecParam(specParam); return new ResponseEntity<>(HttpStatus.OK); } /** * 通过paramId删除规格模板下某一参数 * * @param pid * @return */ @DeleteMapping("/param/{pid}") public ResponseEntity deleteSpecParam(@PathVariable("pid") Long pid) { specificationService.deleteSpecParam(pid); return new ResponseEntity<>(HttpStatus.OK); } /** * 查询规格参数组,及组内参数 * * @param cid * @return */ @GetMapping("/{cid}") public ResponseEntity> querySpecsByCid(@PathVariable("cid") Long cid) { List list = this.specificationService.querySpecsByCid(cid); if (list == null || list.size() == 0) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } return ResponseEntity.ok(list); } } ================================================ FILE: ly-item/ly-item-service/src/main/java/com/leyou/item/mapper/BrandMapper.java ================================================ package com.leyou.item.mapper; import com.leyou.item.pojo.Brand; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import tk.mybatis.mapper.additional.idlist.SelectByIdListMapper; import tk.mybatis.mapper.common.Mapper; import java.util.List; public interface BrandMapper extends Mapper, SelectByIdListMapper { /** * 新增商品分类和品牌中间表数据 * * @param cid 商品分类id * @param bid 品牌id */ @Insert("insert into tb_category_brand(category_id,brand_id) values(#{cid},#{bid})") int insertCategoryBrand(@Param("cid") Long cid, @Param("bid") Long bid); /** * 根据brand id删除中间表相关数据 * * @param bid */ @Delete("delete from tb_category_brand where brand_id=#{bid}") void deleteByBrandIdInCategoryBrand(@Param("bid") Long bid); /** * 根据分类id查询品牌 * * @param cid * @return */ @Select("select b.* from tb_brand b inner join tb_category_brand cb on b.id=cb.brand_id where cb.category_id=#{cid}") List selectBrandByCid(@Param("cid") Long cid); } ================================================ FILE: ly-item/ly-item-service/src/main/java/com/leyou/item/mapper/CategoryMapper.java ================================================ package com.leyou.item.mapper; import com.leyou.item.pojo.Category; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import tk.mybatis.mapper.additional.idlist.SelectByIdListMapper; import tk.mybatis.mapper.common.Mapper; import java.util.List; public interface CategoryMapper extends Mapper, SelectByIdListMapper { /** * 通过品牌id查询商品分类 * * @param bid 品牌id * @return */ @Select("select * from tb_category where id in (select category_id from tb_category_brand where brand_id=#{bid})") List queryByBrandId(@Param("bid") Long bid); } ================================================ FILE: ly-item/ly-item-service/src/main/java/com/leyou/item/mapper/SkuMapper.java ================================================ package com.leyou.item.mapper; import com.leyou.item.pojo.Sku; import tk.mybatis.mapper.common.Mapper; public interface SkuMapper extends Mapper { } ================================================ FILE: ly-item/ly-item-service/src/main/java/com/leyou/item/mapper/SpecGroupMapper.java ================================================ package com.leyou.item.mapper; import com.leyou.item.pojo.SpecGroup; import tk.mybatis.mapper.common.Mapper; public interface SpecGroupMapper extends Mapper { } ================================================ FILE: ly-item/ly-item-service/src/main/java/com/leyou/item/mapper/SpecParamMapper.java ================================================ package com.leyou.item.mapper; import com.leyou.item.pojo.SpecParam; import tk.mybatis.mapper.common.Mapper; public interface SpecParamMapper extends Mapper { } ================================================ FILE: ly-item/ly-item-service/src/main/java/com/leyou/item/mapper/SpuDetailMapper.java ================================================ package com.leyou.item.mapper; import com.leyou.item.pojo.SpuDetail; import tk.mybatis.mapper.common.Mapper; /** * SPU垂直拆分,存储SPU详情 */ public interface SpuDetailMapper extends Mapper { } ================================================ FILE: ly-item/ly-item-service/src/main/java/com/leyou/item/mapper/SpuMapper.java ================================================ package com.leyou.item.mapper; import com.leyou.item.pojo.Spu; import tk.mybatis.mapper.common.Mapper; public interface SpuMapper extends Mapper { } ================================================ FILE: ly-item/ly-item-service/src/main/java/com/leyou/item/mapper/StockMapper.java ================================================ package com.leyou.item.mapper; import com.leyou.item.pojo.Stock; import tk.mybatis.mapper.common.Mapper; /** * 库存 */ public interface StockMapper extends Mapper { } ================================================ FILE: ly-item/ly-item-service/src/main/java/com/leyou/item/service/BrandService.java ================================================ package com.leyou.item.service; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.leyou.common.pojo.PageResult; import com.leyou.item.mapper.BrandMapper; import com.leyou.item.pojo.Brand; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import tk.mybatis.mapper.entity.Example; import java.util.List; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-02 9:19 */ @Service public class BrandService { @Autowired private BrandMapper brandMapper; /** * 条件查询品牌-含分页 * * @param page 当前页 * @param rows 每页大小 * @param sortBy 排序字段 * @param desc 是否为降序 * @param key 搜索关键字 * @return */ public PageResult queryBrandByPageAndSort(Integer page, Integer rows, String sortBy, Boolean desc, String key) { // 开启分页 PageHelper.startPage(page, rows); // 过滤 Example example = new Example(Brand.class); // if (StringUtils.isNotBlank(key)) { if (key != null && !"".equals(key)) { // 条件非空,name-品牌名称 letter-品牌首字母 example.createCriteria().andLike("name", "%" + key + "%").orEqualTo("letter", key); } if (sortBy != null && !"".equals(sortBy)) { // 排序 order by 属性名 DESC/ASC String orderByClause = sortBy + (desc ? " DESC " : " ASC "); example.setOrderByClause(orderByClause); } // 查询 Page pageInfo = (Page) brandMapper.selectByExample(example); // 返回结果 return new PageResult<>(pageInfo.getTotal(), pageInfo); } /** * 新增品牌 * * @param brand * @param cids */ @Transactional public void saveBrand(Brand brand, List cids) { // 新增品牌信息 this.brandMapper.insertSelective(brand); // 新增品牌和分类中间表 for (Long cid : cids) { this.brandMapper.insertCategoryBrand(cid, brand.getId()); } } /** * 通过bid删除品牌 * 删除tb_brand中的数据 * * @param bid * @return */ @Transactional public void deleteBrand(Long bid) { // 删除品牌信息 this.brandMapper.deleteByPrimaryKey(bid); // 维护中间表 this.brandMapper.deleteByBrandIdInCategoryBrand(bid); } /** * 根据分类id查询品牌 * * @param cid * @return */ public List queryBrandsByCid(Long cid) { return this.brandMapper.selectBrandByCid(cid); } /** * 通过id查询品牌 * * @param id * @return */ public Brand queryBrandById(Long id) { return this.brandMapper.selectByPrimaryKey(id); } /** * 通过ids查询品牌 * * @param ids * @return */ public List queryBrandByIds(List ids) { return this.brandMapper.selectByIdList(ids); } } ================================================ FILE: ly-item/ly-item-service/src/main/java/com/leyou/item/service/CategoryService.java ================================================ package com.leyou.item.service; import com.leyou.item.mapper.CategoryMapper; import com.leyou.item.pojo.Category; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-01 22:04 */ @Service public class CategoryService { @Autowired private CategoryMapper categoryMapper; /** * 根据parentId查询子类目 * * @param pid * @return */ public List queryCategoryListByParentId(Long pid) { Category category = new Category(); category.setParentId(pid); return this.categoryMapper.select(category); } /** * 通过品牌id查询商品分类 * * @param bid 品牌id * @return */ public List queryByBrandId(Long bid) { return this.categoryMapper.queryByBrandId(bid); } /** * 查询spu的商品分类名称,要查三级分类 * 通过tb_spu中cid1、cid2、cid3查询 * * @param ids * @return */ public List queryNameByIds(List ids) { return this.categoryMapper.selectByIdList(ids).stream().map(Category::getName).collect(Collectors.toList()); } /** * 根据3级分类id,查询1~3级的分类 * * @param id * @return */ public List queryAllByCid3(Long id) { Category c3 = this.categoryMapper.selectByPrimaryKey(id); Category c2 = this.categoryMapper.selectByPrimaryKey(c3.getParentId()); Category c1 = this.categoryMapper.selectByPrimaryKey(c2.getParentId()); return Arrays.asList(c1, c2, c3); } } ================================================ FILE: ly-item/ly-item-service/src/main/java/com/leyou/item/service/GoodsService.java ================================================ package com.leyou.item.service; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.leyou.common.pojo.PageResult; import com.leyou.item.bo.SpuBo; import com.leyou.item.mapper.*; import com.leyou.item.pojo.*; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.core.AmqpTemplate; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import tk.mybatis.mapper.entity.Example; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.stream.Collectors; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-05 8:27 */ @Service public class GoodsService { @Autowired private SpuMapper spuMapper; @Autowired private SkuMapper skuMapper; @Autowired private BrandMapper brandMapper; @Autowired private CategoryService categoryService; @Autowired private SpuDetailMapper spuDetailMapper; @Autowired private StockMapper stockMapper; @Autowired private AmqpTemplate amqpTemplate; private static final Logger logger = LoggerFactory.getLogger(GoodsService.class); /** * 分页查询SPU * * @param page * @param rows * @param key * @param saleable true-上架 false-下架 * @return */ public PageResult querySpuByPageAndSort(Integer page, Integer rows, String key, Boolean saleable) { // 1、查询SPU // 开启分页,最多允许查询100条 PageHelper.startPage(page, Math.min(rows, 100)); // 过滤条件 Example example = new Example(Spu.class); Example.Criteria criteria = example.createCriteria(); // 上下架 if (saleable != null) { criteria.orEqualTo("saleable", saleable); } // 模糊查询 if (StringUtils.isNotBlank(key)) { criteria.andLike("title", "%" + key + "%"); } Page pageInfo = (Page) this.spuMapper.selectByExample(example); // 封装SPU视图类SpuBo List list = pageInfo.getResult().stream().map(spu -> { // 2、将spu封装进spuBo SpuBo spuBo = new SpuBo(); // 属性拷贝 BeanUtils.copyProperties(spu, spuBo); // 3、查询spu的商品分类名称,要查三级分类 List names = this.categoryService.queryNameByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3())); // 将分类名称拼接后接入 spuBo.setCname(StringUtils.join(names, "/")); // 4、查询spu的品牌名称 Brand brand = this.brandMapper.selectByPrimaryKey(spu.getBrandId()); spuBo.setBname(brand.getName()); return spuBo; }).collect(Collectors.toList()); return new PageResult<>(pageInfo.getTotal(), list); } /** * 新增商品 * * @param spuBo */ @Transactional public void saveGoods(SpuBo spuBo) { // 新增spu // 设置默认属性 spuBo.setId(null); spuBo.setSaleable(true); spuBo.setValid(true); spuBo.setCreateTime(new Date()); spuBo.setLastUpdateTime(spuBo.getCreateTime()); this.spuMapper.insertSelective(spuBo); // 新增spuDetail SpuDetail spuDetail = spuBo.getSpuDetail(); spuDetail.setSpuId(spuBo.getId()); this.spuDetailMapper.insertSelective(spuDetail); // 存储spu详细信息 this.saveSkuAndStock(spuBo); // 发送消息到mq this.sendMessage(spuBo.getId(), "insert"); } /** * 保存tb_sku和tb_stock * * @param spuBo */ private void saveSkuAndStock(SpuBo spuBo) { spuBo.getSkus().forEach(sku -> { // 新增sku sku.setSpuId(spuBo.getId()); sku.setCreateTime(new Date()); sku.setLastUpdateTime(sku.getCreateTime()); this.skuMapper.insertSelective(sku); // 新增库存 Stock stock = new Stock(); stock.setSkuId(sku.getId()); stock.setStock(sku.getStock()); this.stockMapper.insertSelective(stock); }); } /** * 通过spu_id查询SPU详情 * * @param spuId * @return */ public SpuDetail querySpuDetailBySpuId(Long spuId) { return this.spuDetailMapper.selectByPrimaryKey(spuId); } /** * 根据spu_id查询sku的集合 * * @param spuId * @return */ public List querySkusBySpuId(Long spuId) { Sku sku = new Sku(); sku.setSpuId(spuId); List skus = this.skuMapper.select(sku); // 通过sku_id查询库存 skus.forEach(s -> { Stock stock = this.stockMapper.selectByPrimaryKey(s.getId()); s.setStock(stock.getStock()); }); return skus; } /** * 修改商品信息 * * @param spuBo * @return */ @Transactional public void updateGoods(SpuBo spuBo) { // 查询以前sku List skus = this.querySkusBySpuId(spuBo.getId()); // 如果以前存在,则删除 if (!CollectionUtils.isEmpty(skus)) { List ids = skus.stream().map(s -> s.getId()).collect(Collectors.toList()); // 删除以前库存 Example example = new Example(Stock.class); example.createCriteria().andIn("skuId", ids); this.stockMapper.deleteByExample(example); // 删除以前的sku Sku record = new Sku(); record.setSpuId(spuBo.getId()); this.skuMapper.delete(record); } // 新增sku和库存 saveSkuAndStock(spuBo); // 更新spu spuBo.setLastUpdateTime(new Date()); spuBo.setCreateTime(null); spuBo.setValid(null); spuBo.setSaleable(null); this.spuMapper.updateByPrimaryKeySelective(spuBo); // 更新spu详情 this.spuDetailMapper.updateByPrimaryKeySelective(spuBo.getSpuDetail()); // 向mq发送消息 this.sendMessage(spuBo.getId(), "update"); } /** * 通过spu_id删除商品goods * * @param spuId * @return */ @Transactional public void deleteGoods(Long spuId) { // 先删除sku和库存信息 this.deleteSkuAndStock(spuId); // 再删除spu和spu_detail this.spuMapper.deleteByPrimaryKey(spuId); this.spuDetailMapper.deleteByPrimaryKey(spuId); } /** * 通过spu_id删除tb_sku * 通过sku_id删除tb_stock * * @param spuId */ private void deleteSkuAndStock(Long spuId) { // 通过spu_id查询sku Sku querySku = new Sku(); querySku.setSpuId(spuId); List skus = this.skuMapper.select(querySku); // 删除sku if (!CollectionUtils.isEmpty(skus)) { // 获得sku_id集合 List ids = skus.stream().map(sku -> sku.getId()).collect(Collectors.toList()); // 通过sku_id删除tb_stock Example example = new Example(Stock.class); Example.Criteria criteria = example.createCriteria(); criteria.andIn("skuId", ids); this.stockMapper.deleteByExample(example); } // 删除sku this.skuMapper.delete(querySku); } /** * 通过spu_id修改商品上下架状态 * * @param spuId * @return */ public void changeSaleable(Long spuId) { // 先查后更新 Spu dbSpu = this.spuMapper.selectByPrimaryKey(spuId); if (null != dbSpu) { dbSpu.setSaleable(!dbSpu.getSaleable()); this.spuMapper.updateByPrimaryKey(dbSpu); } } /** * 根据spu_id查询spu * * @param id * @return */ public Spu querySpuById(Long id) { return this.spuMapper.selectByPrimaryKey(id); } /** * 生产消息到mq * * @param id * @param type */ private void sendMessage(Long id, String type) { // 发送消息 try { this.amqpTemplate.convertAndSend("item." + type, id); } catch (Exception e) { logger.error("{}商品消息发送异常,商品id:{}", type, id, e); } } /** * 通过id查询sku * * @param id * @return */ public Sku querySkuById(Long id) { return this.skuMapper.selectByPrimaryKey(id); } } ================================================ FILE: ly-item/ly-item-service/src/main/java/com/leyou/item/service/SpecificationService.java ================================================ package com.leyou.item.service; import com.leyou.item.mapper.SpecGroupMapper; import com.leyou.item.mapper.SpecParamMapper; import com.leyou.item.pojo.SpecGroup; import com.leyou.item.pojo.SpecParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; /** * @Author: TianCi.Xiong * @Description: 商品规格参数 * @Date: Created in 2019-11-04 17:42 */ @Service public class SpecificationService { @Autowired private SpecGroupMapper specGroupMapper; @Autowired private SpecParamMapper specParamMapper; /** * 通过商品分类id查询分组 * * @param cid * @return */ public List queryGroupsByCid(Long cid) { SpecGroup specGroup = new SpecGroup(); specGroup.setCid(cid); return this.specGroupMapper.select(specGroup); } /** * 根据条件查询规格参数 * * @param gid * @param cid * @param generic * @param searching * @return */ public List querySpecParams(Long gid, Long cid, Boolean generic, Boolean searching) { SpecParam record = new SpecParam(); record.setGroupId(gid); record.setCid(cid); record.setGeneric(generic); record.setSearching(searching); return this.specParamMapper.select(record); } /** * 新增规格模板分组 * * @param specGroup * @return */ public void saveSpecGroup(SpecGroup specGroup) { // null属性会使用默认值保存 specGroupMapper.insertSelective(specGroup); } /** * 更新规格模板信息 * * @param specGroup * @return */ public void updateSpecGroup(SpecGroup specGroup) { specGroupMapper.updateByPrimaryKey(specGroup); } /** * 通过id删除规格模板 * * @param id * @return */ @Transactional public void deleteSpecGroup(Long id) { // 先删除此规格模板分组下面的规格参数集合 SpecParam specParam = new SpecParam(); specParam.setGroupId(id); specParamMapper.delete(specParam); // 再删除此规格模板 specGroupMapper.deleteByPrimaryKey(id); } /** * 新增规格模板下的规格参数 * * @param specParam * @return */ public void saveSpecParam(SpecParam specParam) { specParamMapper.insertSelective(specParam); } /** * 更新规格模板下规格参数信息 * * @param specParam * @return */ public void updateSpecParam(SpecParam specParam) { specParamMapper.updateByPrimaryKey(specParam); } /** * 通过paramId删除规格模板下某一参数 * * @param pid * @return */ public void deleteSpecParam(Long pid) { specParamMapper.deleteByPrimaryKey(pid); } /** * 查询规格参数组,及组内参数 * * @param cid * @return */ public List querySpecsByCid(Long cid) { // 查询规格组 List groups = this.queryGroupsByCid(cid); groups.forEach(g -> { // 查询组内参数 g.setParams(this.querySpecParams(g.getId(), null, null, null)); }); return groups; } } ================================================ FILE: ly-item/ly-item-service/src/main/resources/application.yml ================================================ server: port: 8081 spring: application: name: item-service datasource: url: jdbc:mysql://localhost:3306/leyou username: root password: 1234 hikari: maximum-pool-size: 30 minimum-idle: 10 rabbitmq: host: 192.168.56.101 username: leyou password: 123456 virtual-host: /leyou template: exchange: leyou.item.exchange publisher-confirms: true eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka instance: lease-renewal-interval-in-seconds: 5 # 每隔5秒发送一次心跳 lease-expiration-duration-in-seconds: 10 # 10秒不发送就过期 prefer-ip-address: true ip-address: 127.0.0.1 instance-id: ${spring.application.name}:${server.port} ================================================ FILE: ly-item/pom.xml ================================================ leyou com.leyou.parent 1.0.0-SNAPSHOT 4.0.0 com.leyou.service ly-item 1.0.0-SNAPSHOT 商品服务模块 ly-item-interface ly-item-service pom ================================================ FILE: ly-order/pom.xml ================================================ leyou com.leyou.parent 1.0.0-SNAPSHOT 4.0.0 com.leyou.order ly-order 1.0.0-SNAPSHOT 订单服务模块 org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.boot spring-boot-starter-web org.mybatis.spring.boot mybatis-spring-boot-starter tk.mybatis mapper-spring-boot-starter mysql mysql-connector-java org.springframework.cloud spring-cloud-starter-openfeign org.springframework.boot spring-boot-starter-data-redis com.github.pagehelper pagehelper-spring-boot-starter org.springframework.boot spring-boot-configuration-processor true com.github.wxpay wxpay-sdk 0.0.3 io.springfox springfox-swagger2 2.8.0 io.springfox springfox-swagger-ui 2.8.0 org.projectlombok lombok com.leyou.service ly-item-interface ${leyou.latest.version} com.leyou.common ly-common ${leyou.latest.version} com.leyou.auth ly-auth-common ${leyou.latest.version} ================================================ FILE: ly-order/src/main/java/com/leyou/order/LyOrderApplication.java ================================================ package com.leyou.order; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; import tk.mybatis.spring.annotation.MapperScan; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-15 9:39 */ @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients @MapperScan("com.leyou.order.mapper") public class LyOrderApplication { public static void main(String[] args) { SpringApplication.run(LyOrderApplication.class, args); } } ================================================ FILE: ly-order/src/main/java/com/leyou/order/config/IdWorkerConfig.java ================================================ package com.leyou.order.config; import com.leyou.common.utils.IdWorker; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-15 9:52 */ @Configuration @EnableConfigurationProperties(IdWorkerProperties.class) public class IdWorkerConfig { @Bean public IdWorker idWorker(IdWorkerProperties prop) { return new IdWorker(prop.getWorkerId(), prop.getDatacenterId()); } } ================================================ FILE: ly-order/src/main/java/com/leyou/order/config/IdWorkerProperties.java ================================================ package com.leyou.order.config; import lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-15 9:52 */ @Getter @Setter @ConfigurationProperties(prefix = "leyou.worker") public class IdWorkerProperties { private long workerId;// 当前机器id private long datacenterId;// 序列号 } ================================================ FILE: ly-order/src/main/java/com/leyou/order/config/JwtProperties.java ================================================ package com.leyou.order.config; import com.leyou.auth.utils.RsaUtils; import lombok.Getter; import lombok.Setter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; import javax.annotation.PostConstruct; import java.security.PublicKey; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-15 9:53 */ @Getter @Setter @ConfigurationProperties(prefix = "leyou.jwt") public class JwtProperties { private String pubKeyPath;// 公钥 private PublicKey publicKey; // 公钥 private String cookieName; private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class); @PostConstruct public void init() { try { // 获取公钥和私钥 this.publicKey = RsaUtils.getPublicKey(pubKeyPath); } catch (Exception e) { logger.error("初始化公钥失败!", e); throw new RuntimeException(); } } } ================================================ FILE: ly-order/src/main/java/com/leyou/order/config/MvcConfig.java ================================================ package com.leyou.order.config; import com.leyou.order.interceptor.LoginInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-15 9:53 */ @Configuration @EnableConfigurationProperties(JwtProperties.class) public class MvcConfig implements WebMvcConfigurer { @Autowired private JwtProperties jwtProperties; @Bean public LoginInterceptor loginInterceptor() { return new LoginInterceptor(jwtProperties); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor()) .addPathPatterns("/order/**"); } } ================================================ FILE: ly-order/src/main/java/com/leyou/order/config/PayConfig.java ================================================ package com.leyou.order.config; import com.github.wxpay.sdk.WXPayConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; import java.io.InputStream; /** * @Author: TianCi.Xiong * @Description: 支付有关配置 * @Date: Created in 2019-11-15 9:54 */ @Configuration @EnableConfigurationProperties(PayProperties.class) public class PayConfig implements WXPayConfig { @Autowired private PayProperties payProperties; @Override public String getAppID() { return payProperties.getAppId(); } @Override public String getMchID() { return payProperties.getMchId(); } @Override public String getKey() { return payProperties.getKey(); } @Override public InputStream getCertStream() { return null; } @Override public int getHttpConnectTimeoutMs() { return payProperties.getConnectTimeoutMs(); } @Override public int getHttpReadTimeoutMs() { return payProperties.getReadTimeoutMs(); } } ================================================ FILE: ly-order/src/main/java/com/leyou/order/config/PayProperties.java ================================================ package com.leyou.order.config; import lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; /** * @Author: TianCi.Xiong * @Description: 支付有关配置 * @Date: Created in 2019-11-15 9:54 */ @Getter @Setter @ConfigurationProperties(prefix = "leyou.pay") public class PayProperties { private String appId; // 公众账号ID private String mchId; // 商户号 private String key; // 生成签名的密钥 private int connectTimeoutMs; // 连接超时时间 private int readTimeoutMs;// 读取超时时间 } ================================================ FILE: ly-order/src/main/java/com/leyou/order/config/SwaggerConfig.java ================================================ package com.leyou.order.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; /** * @Author: TianCi.Xiong * @Description: Swagger配置类 * @Date: Created in 2019-11-15 9:55 */ @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .host("localhost:8089") .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.leyou.order.controller")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("乐优商城订单系统") .description("乐优商城订单系统接口文档") .version("1.0") .build(); } } ================================================ FILE: ly-order/src/main/java/com/leyou/order/controller/OrderController.java ================================================ package com.leyou.order.controller; import com.leyou.common.pojo.PageResult; import com.leyou.order.pojo.Order; import com.leyou.order.service.OrderService; import com.leyou.order.utils.PayHelper; import com.leyou.order.utils.PayState; import io.swagger.annotations.*; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-15 10:10 */ @Api(tags = {"订单服务接口"}) @RestController @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; @Autowired private PayHelper payHelper; /** * 创建订单 * * @param order 订单对象 * @return 订单编号 */ @PostMapping @ApiOperation(value = "创建订单接口,返回订单编号", notes = "创建订单") @ApiImplicitParam(name = "order", required = true, value = "订单的json对象,包含订单条目和物流信息") public ResponseEntity createOrder(@RequestBody @Valid Order order) { Long id = this.orderService.createOrder(order); return new ResponseEntity<>(id, HttpStatus.CREATED); } /** * 根据订单编号查询订单 * * @param id * @return */ @GetMapping("/{id}") @ApiOperation(value = "根据订单编号查询订单,返回订单对象", notes = "查询订单") @ApiImplicitParam(name = "id", required = true, value = "订单的编号") public ResponseEntity queryOrderById(@PathVariable("id") Long id) { Order order = this.orderService.queryById(id); if (order == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } return ResponseEntity.ok(order); } /** * 分页查询当前用户订单 * * @param status 订单状态 * @return 分页订单数据 */ @GetMapping("/list") @ApiOperation(value = "分页查询当前用户订单,并且可以根据订单状态过滤", notes = "分页查询当前用户订单") @ApiImplicitParams({ @ApiImplicitParam(name = "page", value = "当前页", defaultValue = "1", type = "Integer"), @ApiImplicitParam(name = "rows", value = "每页大小", defaultValue = "5", type = "Integer"), @ApiImplicitParam(name = "status", value = "订单状态:1未付款,2已付款未发货,3已发货未确认,4已确认未评价,5交易关闭,6交易成功,已评价", type = "Integer"), }) @ApiResponses({ @ApiResponse(code = 200, message = "订单的分页结果"), @ApiResponse(code = 404, message = "没有查询到结果"), @ApiResponse(code = 500, message = "查询失败"), }) public ResponseEntity> queryUserOrderList( @RequestParam(value = "page", defaultValue = "1") Integer page, @RequestParam(value = "rows", defaultValue = "5") Integer rows, @RequestParam(value = "status", required = false) Integer status) { PageResult result = this.orderService.queryUserOrderList(page, rows, status); if (result == null) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } return ResponseEntity.ok(result); } /** * 更新订单状态 * * @param id * @param status * @return */ @PutMapping("/{id}/{status}") @ApiOperation(value = "更新订单状态", notes = "更新订单状态") @ApiImplicitParams({ @ApiImplicitParam(name = "id", value = "订单编号", type = "Long"), @ApiImplicitParam(name = "status", value = "订单状态:1未付款,2已付款未发货,3已发货未确认,4已确认未评价,5交易关闭,6交易成功,已评价", type = "Integer"), }) @ApiResponses({ @ApiResponse(code = 204, message = "true:修改状态成功;false:修改状态失败"), @ApiResponse(code = 400, message = "请求参数有误"), @ApiResponse(code = 500, message = "查询失败") }) public ResponseEntity updateStatus(@PathVariable("id") Long id, @PathVariable("status") Integer status) { Boolean boo = this.orderService.updateStatus(id, status); if (boo == null) { // 返回400 return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } // 返回204 return new ResponseEntity<>(HttpStatus.NO_CONTENT); } /** * 生成付款链接 * * @param orderId * @return */ @GetMapping("/url/{id}") @ApiOperation(value = "生成微信扫码支付付款链接", notes = "生成付款链接") @ApiImplicitParam(name = "id", value = "订单编号", type = "Long") @ApiResponses({ @ApiResponse(code = 200, message = "根据订单编号生成的微信支付地址"), @ApiResponse(code = 404, message = "生成链接失败"), @ApiResponse(code = 500, message = "服务器异常"), }) public ResponseEntity generateUrl(@PathVariable("id") Long orderId) { // 生成付款链接 String url = this.payHelper.createPayUrl(orderId); if (StringUtils.isBlank(url)) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } return ResponseEntity.ok(url); } /** * 查询付款状态 * * @param orderId * @return 0, 状态查询失败 1,支付成功 2,支付失败 */ @GetMapping("/state/{id}") @ApiOperation(value = "查询扫码支付付款状态", notes = "查询付款状态") @ApiImplicitParam(name = "id", value = "订单编号", type = "Long") @ApiResponses({ @ApiResponse(code = 200, message = "0, 未查询到支付信息 1,支付成功 2,支付失败"), @ApiResponse(code = 500, message = "服务器异常"), }) public ResponseEntity queryPayState(@PathVariable("id") Long orderId) { PayState payState = this.payHelper.queryOrder(orderId); return ResponseEntity.ok(payState.getValue()); } } ================================================ FILE: ly-order/src/main/java/com/leyou/order/interceptor/LoginInterceptor.java ================================================ package com.leyou.order.interceptor; import com.leyou.auth.entity.UserInfo; import com.leyou.auth.utils.JwtUtils; import com.leyou.common.utils.CookieUtils; import com.leyou.order.config.JwtProperties; import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpStatus; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-15 9:59 */ public class LoginInterceptor extends HandlerInterceptorAdapter { private JwtProperties jwtProperties; // 定义一个线程域,存放登录用户 private static final ThreadLocal tl = new ThreadLocal<>(); public LoginInterceptor(JwtProperties jwtProperties) { this.jwtProperties = jwtProperties; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 查询token String token = CookieUtils.getCookieValue(request, "LY_TOKEN"); if (StringUtils.isBlank(token)) { // 未登录,返回401 response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } // 有token,查询用户信息 try { // 解析成功,证明已经登录 UserInfo user = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey()); // 放入线程域 tl.set(user); return true; } catch (Exception e) { // 抛出异常,证明未登录或超时,返回401 response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { tl.remove(); } public static UserInfo getLoginUser() { return tl.get(); } } ================================================ FILE: ly-order/src/main/java/com/leyou/order/mapper/OrderDetailMapper.java ================================================ package com.leyou.order.mapper; import com.leyou.order.pojo.OrderDetail; import tk.mybatis.mapper.common.Mapper; import tk.mybatis.mapper.common.special.InsertListMapper; public interface OrderDetailMapper extends Mapper, InsertListMapper { } ================================================ FILE: ly-order/src/main/java/com/leyou/order/mapper/OrderMapper.java ================================================ package com.leyou.order.mapper; import com.leyou.order.pojo.Order; import org.apache.ibatis.annotations.Param; import tk.mybatis.mapper.common.Mapper; import java.util.List; public interface OrderMapper extends Mapper { List queryOrderList(@Param("userId") Long userId, @Param("status") Integer status); } ================================================ FILE: ly-order/src/main/java/com/leyou/order/mapper/OrderStatusMapper.java ================================================ package com.leyou.order.mapper; import com.leyou.order.pojo.OrderStatus; import tk.mybatis.mapper.common.Mapper; public interface OrderStatusMapper extends Mapper { } ================================================ FILE: ly-order/src/main/java/com/leyou/order/pojo/Order.java ================================================ package com.leyou.order.pojo; import lombok.Getter; import lombok.Setter; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.Transient; import javax.validation.constraints.NotNull; import java.util.Date; import java.util.List; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-15 10:00 */ @Getter @Setter @Table(name = "tb_order") public class Order { @Id private Long orderId;// id @NotNull private Long totalPay;// 总金额 @NotNull private Long actualPay;// 实付金额 @NotNull private Integer paymentType; // 支付类型,1、在线支付,2、货到付款 private String promotionIds; // 参与促销活动的id private String postFee;// 邮费 private Date createTime;// 创建时间 private String shippingName;// 物流名称 private String shippingCode;// 物流单号 private Long userId;// 用户id private String buyerMessage;// 买家留言 private String buyerNick;// 买家昵称 private Boolean buyerRate;// 买家是否已经评价 private String receiver; // 收货人全名 private String receiverMobile; // 移动电话 private String receiverState; // 省份 private String receiverCity; // 城市 private String receiverDistrict; // 区/县 private String receiverAddress; // 收货地址,如:xx路xx号 private String receiverZip; // 邮政编码,如:310001 private Integer invoiceType;// 发票类型,0无发票,1普通发票,2电子发票,3增值税发票 private Integer sourceType;// 订单来源 1:app端,2:pc端,3:M端,4:微信端,5:手机qq端 @Transient private List orderDetails; @Transient private Integer status; } ================================================ FILE: ly-order/src/main/java/com/leyou/order/pojo/OrderDetail.java ================================================ package com.leyou.order.pojo; import lombok.Getter; import lombok.Setter; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-15 10:01 */ @Getter @Setter @Table(name = "tb_order_detail") public class OrderDetail { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long orderId;// 订单id private Long skuId;// 商品id private Integer num;// 商品购买数量 private String title;// 商品标题 private Long price;// 商品单价 private String ownSpec;// 商品规格数据 private String image;// 图片 } ================================================ FILE: ly-order/src/main/java/com/leyou/order/pojo/OrderStatus.java ================================================ package com.leyou.order.pojo; import lombok.Getter; import lombok.Setter; import javax.persistence.Id; import javax.persistence.Table; import java.util.Date; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-15 10:02 */ @Getter @Setter @Table(name = "tb_order_status") public class OrderStatus { @Id private Long orderId; /** * 初始阶段:1、未付款、未发货;初始化所有数据 * 付款阶段:2、已付款、未发货;更改付款时间 * 发货阶段:3、已发货,未确认;更改发货时间、物流名称、物流单号 * 成功阶段:4、已确认,未评价;更改交易结束时间 * 关闭阶段:5、关闭; 更改更新时间,交易关闭时间。 * 评价阶段:6、已评价 */ private Integer status; private Date createTime;// 创建时间 private Date paymentTime;// 付款时间 private Date consignTime;// 发货时间 private Date endTime;// 交易结束时间 private Date closeTime;// 交易关闭时间 private Date commentTime;// 评价时间 } ================================================ FILE: ly-order/src/main/java/com/leyou/order/service/OrderService.java ================================================ package com.leyou.order.service; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.leyou.auth.entity.UserInfo; import com.leyou.common.pojo.PageResult; import com.leyou.common.utils.IdWorker; import com.leyou.order.interceptor.LoginInterceptor; import com.leyou.order.mapper.OrderDetailMapper; import com.leyou.order.mapper.OrderMapper; import com.leyou.order.mapper.OrderStatusMapper; import com.leyou.order.pojo.Order; import com.leyou.order.pojo.OrderDetail; import com.leyou.order.pojo.OrderStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Date; import java.util.List; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-15 10:05 */ @Service public class OrderService { @Autowired private IdWorker idWorker; @Autowired private OrderMapper orderMapper; @Autowired private OrderDetailMapper detailMapper; @Autowired private OrderStatusMapper statusMapper; private static final Logger logger = LoggerFactory.getLogger(OrderService.class); @Transactional public Long createOrder(Order order) { // 生成orderId long orderId = idWorker.nextId(); // 获取登录用户 UserInfo user = LoginInterceptor.getLoginUser(); // 初始化数据 order.setBuyerNick(user.getUsername()); order.setBuyerRate(false); order.setCreateTime(new Date()); order.setOrderId(orderId); order.setUserId(user.getId()); // 保存数据 this.orderMapper.insertSelective(order); // 保存订单状态 OrderStatus orderStatus = new OrderStatus(); orderStatus.setOrderId(orderId); orderStatus.setCreateTime(order.getCreateTime()); orderStatus.setStatus(1);// 初始状态为未付款 this.statusMapper.insertSelective(orderStatus); // 订单详情中添加orderId order.getOrderDetails().forEach(od -> od.setOrderId(orderId)); // 保存订单详情,使用批量插入功能 this.detailMapper.insertList(order.getOrderDetails()); logger.debug("生成订单,订单编号:{},用户id:{}", orderId, user.getId()); return orderId; } public Order queryById(Long id) { // 查询订单 Order order = this.orderMapper.selectByPrimaryKey(id); // 查询订单详情 OrderDetail detail = new OrderDetail(); detail.setOrderId(id); List details = this.detailMapper.select(detail); order.setOrderDetails(details); // 查询订单状态 OrderStatus status = this.statusMapper.selectByPrimaryKey(order.getOrderId()); order.setStatus(status.getStatus()); return order; } public PageResult queryUserOrderList(Integer page, Integer rows, Integer status) { try { // 分页 PageHelper.startPage(page, rows); // 获取登录用户 UserInfo user = LoginInterceptor.getLoginUser(); // 创建查询条件 Page pageInfo = (Page) this.orderMapper.queryOrderList(user.getId(), status); return new PageResult<>(pageInfo.getTotal(), pageInfo); } catch (Exception e) { logger.error("查询订单出错", e); return null; } } @Transactional public Boolean updateStatus(Long id, Integer status) { OrderStatus record = new OrderStatus(); record.setOrderId(id); record.setStatus(status); // 根据状态判断要修改的时间 switch (status) { case 2: record.setPaymentTime(new Date());// 付款 break; case 3: record.setConsignTime(new Date());// 发货 break; case 4: record.setEndTime(new Date());// 确认收获,订单结束 break; case 5: record.setCloseTime(new Date());// 交易失败,订单关闭 break; case 6: record.setCommentTime(new Date());// 评价时间 break; default: return null; } int count = this.statusMapper.updateByPrimaryKeySelective(record); return count == 1; } } ================================================ FILE: ly-order/src/main/java/com/leyou/order/utils/PayHelper.java ================================================ package com.leyou.order.utils; import com.github.wxpay.sdk.WXPay; import com.leyou.order.config.PayConfig; import com.leyou.order.service.OrderService; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; /** * @Author: TianCi.Xiong * @Description: 支付工具类 * @Date: Created in 2019-11-15 10:12 */ @Component public class PayHelper { private WXPay wxPay; private static final Logger logger = LoggerFactory.getLogger(PayHelper.class); @Autowired private StringRedisTemplate redisTemplate; @Autowired private OrderService orderService; public PayHelper(PayConfig payConfig) { // TODO 真实开发时 wxPay = new WXPay(payConfig); // TODO 测试时 // wxPay = new WXPay(payConfig, WXPayConstants.SignType.MD5, true); } public String createPayUrl(Long orderId) { String key = "ly.pay.url." + orderId; try { String url = this.redisTemplate.opsForValue().get(key); if (StringUtils.isNotBlank(url)) { return url; } } catch (Exception e) { logger.error("查询缓存付款链接异常,订单编号:{}", orderId, e); } try { Map data = new HashMap<>(); // 商品描述 data.put("body", "乐优商城测试"); // 订单号 data.put("out_trade_no", orderId.toString()); //货币 data.put("fee_type", "CNY"); //金额,单位是分 data.put("total_fee", "1"); //调用微信支付的终端IP(乐优商城的IP) data.put("spbill_create_ip", "127.0.0.1"); //回调地址,付款成功后的接口 data.put("notify_url", "http://test.leyou.com/wxpay/notify"); // 交易类型为扫码支付 data.put("trade_type", "NATIVE"); //商品id,使用假数据 data.put("product_id", "1234567"); Map result = this.wxPay.unifiedOrder(data); if ("SUCCESS".equals(result.get("return_code"))) { String url = result.get("code_url"); // 将付款地址缓存,时间为10分钟 try { this.redisTemplate.opsForValue().set(key, url, 10, TimeUnit.MINUTES); } catch (Exception e) { logger.error("缓存付款链接异常,订单编号:{}", orderId, e); } return url; } else { logger.error("创建预交易订单失败,错误信息:{}", result.get("return_msg")); return null; } } catch (Exception e) { logger.error("创建预交易订单异常", e); return null; } } /** * 查询订单状态 * * @param orderId * @return */ public PayState queryOrder(Long orderId) { Map data = new HashMap<>(); // 订单号 data.put("out_trade_no", orderId.toString()); try { Map result = this.wxPay.orderQuery(data); if (result == null) { // 未查询到结果,认为是未付款 return PayState.NOT_PAY; } String state = result.get("trade_state"); if ("SUCCESS".equals(state)) { // success,则认为付款成功 // 修改订单状态 this.orderService.updateStatus(orderId, 2); return PayState.SUCCESS; } else if (StringUtils.equals("USERPAYING", state) || StringUtils.equals("NOTPAY", state)) { // 未付款或正在付款,都认为是未付款 return PayState.NOT_PAY; } else { // 其它状态认为是付款失败 return PayState.FAIL; } } catch (Exception e) { logger.error("查询订单状态异常", e); return PayState.NOT_PAY; } } } ================================================ FILE: ly-order/src/main/java/com/leyou/order/utils/PayState.java ================================================ package com.leyou.order.utils; /** * 支付状态枚举 */ public enum PayState { NOT_PAY(0), SUCCESS(1), FAIL(2); PayState(int value) { this.value = value; } int value; public int getValue() { return value; } } ================================================ FILE: ly-order/src/main/resources/application.yml ================================================ server: port: 8089 spring: application: name: order-service datasource: url: jdbc:mysql://127.0.0.1:3306/leyou?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true username: root password: 1234 driver-class-name: com.mysql.jdbc.Driver druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 1000 test-on-borrow: true rabbitmq: host: 192.168.56.101 username: leyou password: 123456 virtual-host: /leyou template: retry: enabled: true initial-interval: 10000ms max-interval: 300000ms multiplier: 2 exchange: leyou.item.exchange publisher-confirms: true redis: host: 192.168.56.101 eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka instance: prefer-ip-address: true ip-address: 127.0.0.1 instance-id: ${eureka.instance.ip-address}.${server.port} lease-renewal-interval-in-seconds: 3 lease-expiration-duration-in-seconds: 10 mybatis: type-aliases-package: com.leyou.item.pojo mapper-locations: mapper/OrderMapper.xml configuration: map-underscore-to-camel-case: true mapper: not-empty: false identity: mysql leyou: worker: workerId: 1 # 当前机器id datacenterId: 1 # 序列号 jwt: pubKeyPath: C:\\tmp\\rsa\\rsa.pub # 公钥地址 cookieName: LY_TOKEN # cookie的名称 pay: appId: wx8397f8696b538317 mchId: 1473426802 key: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb connectTimeoutMs: 5000 readTimeoutMs: 10000 ================================================ FILE: ly-order/src/main/resources/mapper/OrderMapper.xml ================================================ ================================================ FILE: ly-registry/pom.xml ================================================ leyou com.leyou.parent 1.0.0-SNAPSHOT 4.0.0 com.leyou.common ly-registry 1.0.0-SNAPSHOT 注册中心模块 org.springframework.cloud spring-cloud-starter-netflix-eureka-server ================================================ FILE: ly-registry/src/main/java/com/leyou/LyRegistryApplication.java ================================================ package com.leyou; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-01 10:11 */ @SpringBootApplication @EnableEurekaServer public class LyRegistryApplication { public static void main(String[] args) { SpringApplication.run(LyRegistryApplication.class, args); } } ================================================ FILE: ly-registry/src/main/resources/application.yml ================================================ server: port: 10086 spring: application: name: ly-registry eureka: client: fetch-registry: false register-with-eureka: false service-url: defaultZone: http://127.0.0.1:${server.port}/eureka server: enable-self-preservation: false # 关闭自我保护 eviction-interval-timer-in-ms: 5000 # 每隔5秒进行一次服务列表清理 ================================================ FILE: ly-search/pom.xml ================================================ leyou com.leyou.parent 1.0.0-SNAPSHOT 4.0.0 com.leyou.search ly-search 1.0.0-SNAPSHOT es搜索服务模块 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-elasticsearch org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-openfeign org.springframework.boot spring-boot-starter-amqp org.projectlombok lombok org.apache.commons commons-lang3 com.leyou.service ly-item-interface ${leyou.latest.version} com.leyou.common ly-common 1.0.0-SNAPSHOT ================================================ FILE: ly-search/src/main/java/com/leyou/LySearchApplication.java ================================================ package com.leyou; 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: TianCi.Xiong * @Description: * @Date: Created in 2019-11-07 21:01 */ @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class LySearchApplication { public static void main(String[] args) { SpringApplication.run(LySearchApplication.class, args); } } ================================================ FILE: ly-search/src/main/java/com/leyou/client/BrandClient.java ================================================ package com.leyou.client; import com.leyou.item.api.BrandApi; import org.springframework.cloud.openfeign.FeignClient; /** * 品牌的FeignClient */ @FeignClient(value = "item-service") public interface BrandClient extends BrandApi { } ================================================ FILE: ly-search/src/main/java/com/leyou/client/CategoryClient.java ================================================ package com.leyou.client; import com.leyou.item.api.CategoryApi; import org.springframework.cloud.openfeign.FeignClient; /** * 商品分类的FeignClient */ @FeignClient(value = "item-service") public interface CategoryClient extends CategoryApi { } ================================================ FILE: ly-search/src/main/java/com/leyou/client/GoodsClient.java ================================================ package com.leyou.client; import com.leyou.item.api.GoodsApi; import org.springframework.cloud.openfeign.FeignClient; /** * 商品的FeignClient */ @FeignClient(value = "item-service") public interface GoodsClient extends GoodsApi { } ================================================ FILE: ly-search/src/main/java/com/leyou/client/SpecificationClient.java ================================================ package com.leyou.client; import com.leyou.item.api.SpecificationApi; import org.springframework.cloud.openfeign.FeignClient; /** * 规格参数的FeignClient */ @FeignClient(value = "item-service") public interface SpecificationClient extends SpecificationApi { } ================================================ FILE: ly-search/src/main/java/com/leyou/controller/SearchController.java ================================================ package com.leyou.controller; import com.leyou.common.pojo.PageResult; import com.leyou.pojo.Goods; import com.leyou.pojo.SearchRequest; import com.leyou.service.SearchService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-08 20:52 */ @RestController public class SearchController { @Autowired private SearchService searchService; /** * 搜索商品 * * @param request * @return */ @PostMapping("/page") public ResponseEntity> search(@RequestBody SearchRequest request) { PageResult result = this.searchService.search(request); if (result == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(result); } } ================================================ FILE: ly-search/src/main/java/com/leyou/listener/GoodsListener.java ================================================ package com.leyou.listener; import com.leyou.service.SearchService; import org.springframework.amqp.core.ExchangeTypes; import org.springframework.amqp.rabbit.annotation.Exchange; import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.QueueBinding; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * @Author: TianCi.Xiong * @Description: mq监听器 * @Date: Created in 2019-11-13 11:35 */ @Component public class GoodsListener { @Autowired private SearchService searchService; /** * 处理insert和update的消息 * * @param id * @throws Exception */ @RabbitListener(bindings = @QueueBinding( value = @Queue(value = "leyou.create.index.queue", durable = "true"), exchange = @Exchange( value = "leyou.item.exchange", ignoreDeclarationExceptions = "true", type = ExchangeTypes.TOPIC), key = {"item.insert", "item.update"}) ) public void listenCreate(Long id) throws Exception { if (id == null) { return; } // 创建或更新索引 this.searchService.createIndex(id); } /** * 处理delete的消息 * * @param id */ @RabbitListener(bindings = @QueueBinding( value = @Queue(value = "leyou.delete.index.queue", durable = "true"), exchange = @Exchange( value = "leyou.item.exchange", ignoreDeclarationExceptions = "true", type = ExchangeTypes.TOPIC), key = "item.delete") ) public void listenDelete(Long id) { if (id == null) { return; } // 删除索引 this.searchService.deleteIndex(id); } } ================================================ FILE: ly-search/src/main/java/com/leyou/pojo/Goods.java ================================================ package com.leyou.pojo; import lombok.Data; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import java.util.Date; import java.util.List; import java.util.Map; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-07 21:07 */ @Data @Document(indexName = "goods", type = "docs", shards = 1, replicas = 0) public class Goods { @Id private Long id; // spuId @Field(type = FieldType.text, analyzer = "ik_max_word") private String all; // 所有需要被搜索的信息,包含标题,分类,甚至品牌 @Field(type = FieldType.keyword, index = false) private String subTitle;// 卖点 private Long brandId;// 品牌id private Long cid1;// 1级分类id private Long cid2;// 2级分类id private Long cid3;// 3级分类id private Date createTime;// 创建时间 private List price;// 价格 @Field(type = FieldType.keyword, index = false) private String skus;// List信息的json结构 private Map specs;// 可搜索的规格参数,key是参数名,值是参数值 } ================================================ FILE: ly-search/src/main/java/com/leyou/pojo/SearchRequest.java ================================================ package com.leyou.pojo; import lombok.Data; import java.util.Map; /** * @Author: TianCi.Xiong * @Description: 搜索条件封装类 * @Date: Created in 2019-11-08 20:52 */ @Data public class SearchRequest { private String key;// 搜索条件 private Integer page;// 当前页 private String sortBy;// 排序字段 private Boolean descending;// 是否降序 private Map filter; // 过滤条件 private static final Integer DEFAULT_SIZE = 20;// 每页大小,不从页面接收,而是固定大小 private static final Integer DEFAULT_PAGE = 1;// 默认页 public Integer getPage() { if (page == null) { return DEFAULT_PAGE; } // 获取页码时做一些校验,不能小于1 return Math.max(DEFAULT_PAGE, page); } public Integer getSize() { return DEFAULT_SIZE; } } ================================================ FILE: ly-search/src/main/java/com/leyou/pojo/SearchResult.java ================================================ package com.leyou.pojo; import com.leyou.common.pojo.PageResult; import com.leyou.item.pojo.Brand; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; import java.util.Map; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-09 15:50 */ @Data @NoArgsConstructor @AllArgsConstructor public class SearchResult extends PageResult { private List> categories; // 分类过滤条件 private List brands; // 品牌过滤条件 private List> specs; // 规格参数过滤条件 public SearchResult(Long total, Integer totalPage, List items, List> categories, List brands, List> specs ) { super(total, totalPage, items); this.categories = categories; this.brands = brands; this.specs = specs; } } ================================================ FILE: ly-search/src/main/java/com/leyou/repository/GoodsRepository.java ================================================ package com.leyou.repository; import com.leyou.pojo.Goods; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; public interface GoodsRepository extends ElasticsearchRepository { } ================================================ FILE: ly-search/src/main/java/com/leyou/service/SearchService.java ================================================ package com.leyou.service; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.leyou.client.BrandClient; import com.leyou.client.CategoryClient; import com.leyou.client.GoodsClient; import com.leyou.client.SpecificationClient; import com.leyou.item.pojo.*; import com.leyou.pojo.*; import com.leyou.repository.GoodsRepository; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang.math.NumberUtils; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.Operator; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.aggregations.Aggregation; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.bucket.terms.LongTerms; import org.elasticsearch.search.aggregations.bucket.terms.StringTerms; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage; import org.springframework.data.elasticsearch.core.query.FetchSourceFilter; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import java.io.IOException; import java.util.*; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-07 22:20 */ @Service public class SearchService { @Autowired private CategoryClient categoryClient; @Autowired private GoodsClient goodsClient; @Autowired private SpecificationClient specificationClient; @Autowired private BrandClient brandClient; @Autowired private GoodsRepository goodsRepository; @Autowired private ElasticsearchTemplate elasticsearchTemplate; private ObjectMapper mapper = new ObjectMapper(); private static final Logger logger = LoggerFactory.getLogger(SearchService.class); /** * 将Spu转变为Goods * * @param spu * @return * @throws IOException */ public Goods buildGoods(Spu spu) throws IOException { // 创建goods对象 Goods goods = new Goods(); // 查询商品分类名称 List names = this.categoryClient.queryNameByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3())); // 查询sku List skus = this.goodsClient.querySkusBySpuId(spu.getId()); // 查询详情 SpuDetail spuDetail = this.goodsClient.querySpuDetailBySpuId(spu.getId()); // 查询规格参数 List params = this.specificationClient.querySpecParam(null, spu.getCid3(), true, null); // 处理sku,仅封装id、价格、标题、图片,并获得价格集合 List prices = new ArrayList<>(); List> skuList = new ArrayList<>(); skus.forEach(sku -> { prices.add(sku.getPrice()); Map skuMap = new HashMap<>(); skuMap.put("id", sku.getId()); skuMap.put("title", sku.getTitle()); skuMap.put("price", sku.getPrice()); skuMap.put("image", StringUtils.isBlank(sku.getImages()) ? "" : StringUtils.split(sku.getImages(), ",")[0]); skuList.add(skuMap); }); // 处理规格参数 Map genericSpecs = mapper.readValue(spuDetail.getGenericSpec(), new TypeReference>() { }); Map specialSpecs = mapper.readValue(spuDetail.getSpecialSpec(), new TypeReference>() { }); // 获取可搜索的规格参数 Map searchSpec = new HashMap<>(); // 过滤规格模板,把所有可搜索的信息保存到Map中 Map specMap = new HashMap<>(); params.forEach(p -> { if (p.getSearching()) { if (p.getGeneric()) { String value = genericSpecs.get(p.getId().toString()).toString(); if (p.getNumeric()) { value = chooseSegment(value, p); } specMap.put(p.getName(), StringUtils.isBlank(value) ? "其它" : value); } else { specMap.put(p.getName(), specialSpecs.get(p.getId().toString())); } } }); goods.setId(spu.getId()); goods.setSubTitle(spu.getSubTitle()); goods.setBrandId(spu.getBrandId()); goods.setCid1(spu.getCid1()); goods.setCid2(spu.getCid2()); goods.setCid3(spu.getCid3()); goods.setCreateTime(spu.getCreateTime()); goods.setAll(spu.getTitle() + " " + StringUtils.join(names, " ")); goods.setPrice(prices); goods.setSkus(mapper.writeValueAsString(skuList)); goods.setSpecs(specMap); return goods; } private String chooseSegment(String value, SpecParam p) { double val = NumberUtils.toDouble(value); String result = "其它"; // 保存数值段 for (String segment : p.getSegments().split(",")) { String[] segs = segment.split("-"); // 获取数值范围 double begin = NumberUtils.toDouble(segs[0]); double end = Double.MAX_VALUE; if (segs.length == 2) { end = NumberUtils.toDouble(segs[1]); } // 判断是否在范围内 if (val >= begin && val < end) { if (segs.length == 1) { result = segs[0] + p.getUnit() + "以上"; } else if (begin == 0) { result = segs[1] + p.getUnit() + "以下"; } else { result = segment + p.getUnit(); } break; } } return result; } /** * 搜索商品 * * @param request * @return */ public SearchResult search(SearchRequest request) { // 判断是否有搜索条件,如果没有,直接返回null。不允许搜索全部商品 if (StringUtils.isBlank(request.getKey())) { return null; } // 1、初始化自定义查询构建器 NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); //QueryBuilder basicQuery = QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND); BoolQueryBuilder boolQueryBuilder = buildBooleanQueryBuilder(request); // 1.1、基本查询 //queryBuilder.withQuery(basicQuery); queryBuilder.withQuery(boolQueryBuilder); // 通过sourceFilter设置返回的结果字段,我们只需要id、subTitle、skus queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, null)); // 1.2.分页排序 // 获取分页参数 Integer page = request.getPage(); Integer size = request.getSize(); // 添加分页 queryBuilder.withPageable(PageRequest.of(page - 1, size)); // 1.3、聚合 // 聚合名称 String categoryAggName = "categories"; // 商品分类聚合名称 String brandAggName = "brands"; // 品牌聚合名称 // 对商品分类进行聚合 queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3")); // 对品牌进行聚合 queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId")); // 2、查询,获取结果 AggregatedPage goodsPage = (AggregatedPage) this.goodsRepository.search(queryBuilder.build()); // 3、解析查询结果 // 3.1、分页信息 Long total = goodsPage.getTotalElements(); int totalPage = (total.intValue() + request.getSize() - 1) / request.getSize(); // 3.2、商品分类的聚合结果 List> categories = getCategoryAggResult(goodsPage.getAggregation(categoryAggName)); // 3.3、品牌的聚合结果 List brands = getBrandAggResult(goodsPage.getAggregation(brandAggName)); // 根据商品分类个数判断是否需要聚合 List> specs = null; if (!CollectionUtils.isEmpty(categories) && categories.size() == 1) { // 如果商品分类只有一个才进行聚合,并根据分类与基本查询条件聚合 specs = getParamAggResult((Long) categories.get(0).get("id"), boolQueryBuilder); } // 返回结果 return new SearchResult(total, totalPage, goodsPage.getContent(), categories, brands, specs); } /** * 聚合出规格参数过滤条件 * * @param cid * @param basicQuery * @return */ private List> getParamAggResult(Long cid, QueryBuilder basicQuery) { try { // 创建自定义查询构建器 NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); // 基于基本的查询条件,聚合规格参数 queryBuilder.withQuery(basicQuery); // 查询要聚合的规格参数 List params = this.specificationClient.querySpecParam(null, cid, null, true); // 添加聚合 params.forEach(param -> { queryBuilder.addAggregation(AggregationBuilders.terms(param.getName()).field("specs." + param.getName() + ".keyword")); }); // 只需要聚合结果集,不需要查询结果集 queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{}, null)); // 执行聚合查询 AggregatedPage goodsPage = (AggregatedPage) this.goodsRepository.search(queryBuilder.build()); // 定义一个集合,收集聚合结果集 List> specs = new ArrayList<>(); // 解析聚合查询的结果集 Map aggregationMap = goodsPage.getAggregations().asMap(); for (Map.Entry entry : aggregationMap.entrySet()) { // 初始化一个map {k-规格参数名 : options-聚合的规格参数值} Map map = new HashMap<>(); // 放入规格参数名 map.put("k", entry.getKey()); // 收集桶中的key-规格参数值 List options = new ArrayList<>(); // 解析每个聚合 StringTerms terms = (StringTerms) entry.getValue(); // 遍历每个聚合中桶,把桶中key放入收集规格参数的集合中 terms.getBuckets().forEach(bucket -> { options.add(bucket.getKeyAsString()); }); map.put("options", options); specs.add(map); } return specs; } catch (Exception e) { logger.error("规格聚合出现异常:", e); return null; } } /** * 解析品牌聚合结果 * * @param aggregation * @return */ private List getBrandAggResult(Aggregation aggregation) { try { // 处理聚合结果集 LongTerms brandAgg = (LongTerms) aggregation; List bids = new ArrayList<>(); for (LongTerms.Bucket bucket : brandAgg.getBuckets()) { bids.add(bucket.getKeyAsNumber().longValue()); } // 根据id查询品牌 return this.brandClient.queryBrandByIds(bids); } catch (Exception e) { logger.error("品牌聚合出现异常:", e); return null; } } /** * 解析商品分类聚合结果 * * @param aggregation * @return */ private List> getCategoryAggResult(Aggregation aggregation) { // 处理聚合结果集 LongTerms terms = (LongTerms) aggregation; // 获取所有的分类id桶 List buckets = terms.getBuckets(); // 定义一个品牌集合,搜集所有的品牌对象 List> categories = new ArrayList<>(); List cids = new ArrayList<>(); // 解析所有的id桶,查询品牌 buckets.forEach(bucket -> { cids.add(bucket.getKeyAsNumber().longValue()); }); List names = this.categoryClient.queryNameByIds(cids); for (int i = 0; i < cids.size(); i++) { Map map = new HashMap<>(); map.put("id", cids.get(i)); map.put("name", names.get(i)); categories.add(map); } return categories; } /** * 构建基本查询条件 * * @param queryBuilder * @param request */ private void searchWithPageAndSort(NativeSearchQueryBuilder queryBuilder, SearchRequest request) { // 准备分页参数 int page = request.getPage(); int size = request.getSize(); // 1、分页 queryBuilder.withPageable(PageRequest.of(page - 1, size)); // 2、排序 String sortBy = request.getSortBy(); Boolean desc = request.getDescending(); if (StringUtils.isNotBlank(sortBy)) { // 如果不为空,则进行排序 queryBuilder.withSort(SortBuilders.fieldSort(sortBy).order(desc ? SortOrder.DESC : SortOrder.ASC)); } } /** * 构建bool查询构建器 * * @param request * @return */ private BoolQueryBuilder buildBooleanQueryBuilder(SearchRequest request) { BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); // 添加基本查询条件 boolQueryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND)); // 添加过滤条件 if (CollectionUtils.isEmpty(request.getFilter())) { return boolQueryBuilder; } for (Map.Entry entry : request.getFilter().entrySet()) { String key = entry.getKey(); // 如果过滤条件是“品牌”, 过滤的字段名:brandId if (StringUtils.equals("品牌", key)) { key = "brandId"; } else if (StringUtils.equals("分类", key)) { // 如果是“分类”,过滤字段名:cid3 key = "cid3"; } else { // 如果是规格参数名,过滤字段名:specs.key.keyword key = "specs." + key + ".keyword"; } boolQueryBuilder.filter(QueryBuilders.termQuery(key, entry.getValue())); } return boolQueryBuilder; } /** * 创建索引 * * @param id */ public void createIndex(Long id) throws IOException { Spu spu = this.goodsClient.querySpuById(id); // 构建商品 Goods goods = this.buildGoods(spu); // 保存数据到索引库 this.goodsRepository.save(goods); } /** * 删除索引 * * @param id */ public void deleteIndex(Long id) { this.goodsRepository.deleteById(id); } } ================================================ FILE: ly-search/src/main/resources/application.yml ================================================ server: port: 8083 spring: application: name: search-service data: elasticsearch: cluster-name: elasticsearch cluster-nodes: 192.168.56.101:9300 jackson: default-property-inclusion: non_null # 配置json处理时忽略空值 rabbitmq: host: 192.168.56.101 username: leyou password: 123456 virtual-host: /leyou eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka instance: lease-renewal-interval-in-seconds: 5 # 每隔5秒发送一次心跳 lease-expiration-duration-in-seconds: 10 # 10秒不发送就过期 prefer-ip-address: true ip-address: 127.0.0.1 instance-id: ${spring.application.name}:${server.port} ================================================ FILE: ly-search/src/test/java/com/leyou/client/CategoryClientTest.java ================================================ package com.leyou.client; import com.leyou.LySearchApplication; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest(classes = LySearchApplication.class) public class CategoryClientTest { @Autowired private CategoryClient categoryClient; @Test public void testQueryCategories() { List names = this.categoryClient.queryNameByIds(Arrays.asList(1L, 2L, 3L)); names.forEach(System.out::println); } } ================================================ FILE: ly-search/src/test/java/com/leyou/client/ElasticsearchTest.java ================================================ package com.leyou.client; import com.leyou.LySearchApplication; import com.leyou.item.bo.SpuBo; import com.leyou.common.pojo.PageResult; import com.leyou.service.SearchService; import com.leyou.pojo.Goods; import com.leyou.repository.GoodsRepository; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.test.context.junit4.SpringRunner; import java.util.ArrayList; import java.util.List; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-07 22:17 */ @RunWith(SpringRunner.class) @SpringBootTest(classes = LySearchApplication.class) public class ElasticsearchTest { @Autowired private GoodsRepository goodsRepository; @Autowired private ElasticsearchTemplate elasticsearchTemplate; @Autowired private SearchService searchService; @Autowired private GoodsClient goodsClient; @Test public void createIndex() { // 创建索引 this.elasticsearchTemplate.createIndex(Goods.class); // 配置映射 this.elasticsearchTemplate.putMapping(Goods.class); } /** * 把SPU变为Goods,然后写入索引库 */ @Test public void loadData() { // 创建索引 this.elasticsearchTemplate.createIndex(Goods.class); // 配置映射 this.elasticsearchTemplate.putMapping(Goods.class); int page = 1; int rows = 100; int size = 0; do { // 查询分页数据 PageResult result = this.goodsClient.querySpuByPage(page, rows, null, true); List spus = result.getItems(); size = spus.size(); // 创建Goods集合 List goodsList = new ArrayList<>(); // 遍历spu for (SpuBo spu : spus) { try { Goods goods = this.searchService.buildGoods(spu); goodsList.add(goods); } catch (Exception e) { break; } } this.goodsRepository.saveAll(goodsList); page++; } while (size == 100); } } ================================================ FILE: ly-sms-service/pom.xml ================================================ leyou com.leyou.parent 1.0.0-SNAPSHOT 4.0.0 com.leyou.sms ly-sms-service 1.0.0-SNAPSHOT 阿里大于短信服务模块 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-amqp com.aliyun aliyun-java-sdk-core 3.3.1 com.aliyun aliyun-java-sdk-dysmsapi 1.0.0 org.apache.commons commons-lang3 org.projectlombok lombok org.springframework.boot spring-boot-starter-data-redis org.springframework.boot spring-boot-configuration-processor true ================================================ FILE: ly-sms-service/src/main/java/com/leyou/sms/LySmsApplication.java ================================================ package com.leyou.sms; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-13 16:08 */ @SpringBootApplication public class LySmsApplication { public static void main(String[] args) { SpringApplication.run(LySmsApplication.class, args); } } ================================================ FILE: ly-sms-service/src/main/java/com/leyou/sms/config/SmsProperties.java ================================================ package com.leyou.sms.config; import lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; /** * @Author: TianCi.Xiong * @Description: 阿里大鱼属性类 * @Date: Created in 2019-11-13 16:10 */ @Getter @Setter @ConfigurationProperties(prefix = "leyou.sms") public class SmsProperties { // accessKeyId public String accessKeyId; // AccessKeySecret public String accessKeySecret; // 签名名称 public String signName; // 模板名称 public String verifyCodeTemplate; } ================================================ FILE: ly-sms-service/src/main/java/com/leyou/sms/listener/SmsListener.java ================================================ package com.leyou.sms.listener; import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse; import com.leyou.sms.config.SmsProperties; import com.leyou.sms.utils.SmsUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.amqp.rabbit.annotation.Exchange; import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.QueueBinding; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.stereotype.Component; import java.util.Map; /** * @Author: TianCi.Xiong * @Description: mq消息监听器 * @Date: Created in 2019-11-13 16:25 */ @Component @EnableConfigurationProperties(SmsProperties.class) public class SmsListener { @Autowired private SmsUtils smsUtils; @Autowired private SmsProperties prop; @RabbitListener(bindings = @QueueBinding( value = @Queue(value = "leyou.sms.queue", durable = "true"), exchange = @Exchange(value = "leyou.sms.exchange", ignoreDeclarationExceptions = "true"), key = {"sms.verify.code"})) public void listenSms(Map msg) throws Exception { if (msg == null || msg.size() <= 0) { // 放弃处理 return; } String phone = msg.get("phone"); String code = msg.get("code"); if (StringUtils.isBlank(phone) || StringUtils.isBlank(code)) { // 放弃处理 return; } // 发送消息 SendSmsResponse resp = this.smsUtils.sendSms(phone, code, prop.getSignName(), prop.getVerifyCodeTemplate()); } } ================================================ FILE: ly-sms-service/src/main/java/com/leyou/sms/utils/SmsUtils.java ================================================ package com.leyou.sms.utils; import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest; import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse; import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.http.MethodType; import com.aliyuncs.profile.DefaultProfile; import com.aliyuncs.profile.IClientProfile; import com.leyou.sms.config.SmsProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.stereotype.Component; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-13 16:09 */ @Component @EnableConfigurationProperties(SmsProperties.class) public class SmsUtils { @Autowired private SmsProperties prop; // 产品名称:云通信短信API产品,开发者无需替换 static final String product = "Dysmsapi"; // 产品域名,开发者无需替换 static final String domain = "dysmsapi.aliyuncs.com"; static final Logger logger = LoggerFactory.getLogger(SmsUtils.class); public SendSmsResponse sendSms(String phone, String code, String signName, String template) throws ClientException { // 可自助调整超时时间 System.setProperty("sun.net.client.defaultConnectTimeout", "10000"); System.setProperty("sun.net.client.defaultReadTimeout", "10000"); // 初始化acsClient,暂不支持region化 IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", prop.getAccessKeyId(), prop.getAccessKeySecret()); DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain); IAcsClient acsClient = new DefaultAcsClient(profile); // 组装请求对象-具体描述见控制台-文档部分内容 SendSmsRequest request = new SendSmsRequest(); request.setMethod(MethodType.POST); // 必填:待发送手机号 request.setPhoneNumbers(phone); // 必填:短信签名-可在短信控制台中找到 request.setSignName(signName); // 必填:短信模板-可在短信控制台中找到 request.setTemplateCode(template); // 可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为 request.setTemplateParam("{\"code\":\"" + code + "\"}"); // 选填-上行短信扩展码(无特殊需求用户请忽略此字段) //request.setSmsUpExtendCode("90997"); // 可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者 request.setOutId("123456"); // hint 此处可能会抛出异常,注意catch SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request); logger.info("发送短信状态:{}", sendSmsResponse.getCode()); logger.info("发送短信消息:{}", sendSmsResponse.getMessage()); return sendSmsResponse; } } ================================================ FILE: ly-sms-service/src/main/resources/application.yml ================================================ server: port: 8086 spring: application: name: sms-service rabbitmq: host: 192.168.56.101 username: leyou password: 123456 virtual-host: /leyou redis: host: 192.168.56.101 leyou: sms: accessKeyId: LTAI4FkBpYg435XnJd9MRF6v # 你自己的accessKeyId accessKeySecret: CntYYhCURpQ9ZCbyitdjED0SrDgfVB # 你自己的AccessKeySecret signName: 乐优商城 # 签名名称 verifyCodeTemplate: SMS_177355065 # 模板名称 ================================================ FILE: ly-upload/pom.xml ================================================ leyou com.leyou.parent 1.0.0-SNAPSHOT 4.0.0 com.leyou.service ly-upload 1.0.0-SNAPSHOT 图片上传模块 org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test com.github.tobato fastdfs-client org.apache.commons commons-lang3 ================================================ FILE: ly-upload/src/main/java/com/leyou/LyUploadServiceApplication.java ================================================ package com.leyou; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-02 16:18 */ @SpringBootApplication @EnableDiscoveryClient public class LyUploadServiceApplication { public static void main(String[] args) { SpringApplication.run(LyUploadServiceApplication.class, args); } } ================================================ FILE: ly-upload/src/main/java/com/leyou/config/FastClientImporter.java ================================================ package com.leyou.config; import com.github.tobato.fastdfs.FdfsClientConfig; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableMBeanExport; import org.springframework.context.annotation.Import; import org.springframework.jmx.support.RegistrationPolicy; /** * @Author: TianCi.Xiong * @Description: FastDFS配置 * @Date: Created in 2019-11-03 8:47 */ @Configuration @Import(FdfsClientConfig.class) // 解决jmx重复注册bean的问题 @EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING) public class FastClientImporter { } ================================================ FILE: ly-upload/src/main/java/com/leyou/config/GlobalCorsConfig.java ================================================ package com.leyou.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; /** * @Author: TianCi.Xiong * @Description: 跨域配置 * @Date: Created in 2019-11-02 17:22 */ @Configuration public class GlobalCorsConfig { @Bean public CorsFilter corsFilter() { //1.添加CORS配置信息 CorsConfiguration config = new CorsConfiguration(); //1) 允许的域,不要写*,否则cookie就无法使用了 config.addAllowedOrigin("http://manage.leyou.com"); config.addAllowedOrigin("http://www.leyou.com"); //2) 是否发送Cookie信息 config.setAllowCredentials(true); //3) 允许的请求方式 config.addAllowedMethod("OPTIONS"); config.addAllowedMethod("POST"); config.addAllowedHeader("*"); //2.添加映射路径,我们拦截一切请求 UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource(); configSource.registerCorsConfiguration("/**", config); //3.返回新的CorsFilter. return new CorsFilter(configSource); } } ================================================ FILE: ly-upload/src/main/java/com/leyou/controller/UploadController.java ================================================ package com.leyou.controller; import com.leyou.service.UploadService; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-02 16:19 */ @RestController @RequestMapping("upload") public class UploadController { @Autowired private UploadService uploadService; /** * 上传图片功能 * * @param file * @return */ @PostMapping("/image") public ResponseEntity uploadImage(@RequestParam("file") MultipartFile file) { // 上传图片,获得地址 String url = this.uploadService.upload(file); if (StringUtils.isBlank(url)) { // 上传失败 return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } // 上传成功,返回url return ResponseEntity.ok(url); } } ================================================ FILE: ly-upload/src/main/java/com/leyou/service/UploadService.java ================================================ package com.leyou.service; import com.github.tobato.fastdfs.domain.StorePath; import com.github.tobato.fastdfs.service.FastFileStorageClient; import com.leyou.controller.UploadController; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.util.Arrays; import java.util.List; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-02 16:20 */ @Service public class UploadService { private static final Logger logger = LoggerFactory.getLogger(UploadController.class); // 支持的文件类型 private static final List suffixes = Arrays.asList("image/png", "image/jpeg"); @Autowired FastFileStorageClient storageClient; /** * 上传图片功能 * * @param file * @return */ public String upload(MultipartFile file) { try { // 1、图片信息校验 // 1)校验文件类型 String type = file.getContentType(); if (!suffixes.contains(type)) { logger.info("上传失败,文件类型不匹配:{}", type); return null; } // 2)校验图片内容 BufferedImage image = ImageIO.read(file.getInputStream()); if (image == null) { logger.info("上传失败,文件内容不符合要求"); return null; } // 2、将图片上传到FastDFS // 2.1、获取文件后缀名 String extension = StringUtils.substringAfterLast(file.getOriginalFilename(), "."); // 2.2、上传 StorePath storePath = this.storageClient.uploadFile(file.getInputStream(), file.getSize(), extension, null); // 2.3、返回完整路径 return "http://image.leyou.com/" + storePath.getFullPath(); } catch (Exception e) { return null; } } } ================================================ FILE: ly-upload/src/main/resources/application.yml ================================================ server: port: 8082 spring: application: name: upload-service servlet: multipart: max-file-size: 5MB # 限制文件上传的大小 # Eureka eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka instance: lease-renewal-interval-in-seconds: 5 # 每隔5秒发送一次心跳 lease-expiration-duration-in-seconds: 10 # 10秒不发送就过期 prefer-ip-address: true ip-address: 127.0.0.1 instance-id: ${spring.application.name}:${server.port} fdfs: so-timeout: 1501 connect-timeout: 601 thumb-image: # 缩略图 width: 60 height: 60 tracker-list: # tracker地址 - 192.168.56.101:22122 ================================================ FILE: ly-upload/src/test/java/FdfsTest.java ================================================ import com.github.tobato.fastdfs.domain.StorePath; import com.github.tobato.fastdfs.domain.ThumbImageConfig; import com.github.tobato.fastdfs.service.FastFileStorageClient; import com.leyou.LyUploadServiceApplication; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; /** * @Author: TianCi.Xiong * @Description: FastDFS测试 * @Date: Created in 2019-11-03 8:55 */ @RunWith(SpringRunner.class) @SpringBootTest(classes = LyUploadServiceApplication.class) public class FdfsTest { @Autowired private FastFileStorageClient storageClient; @Autowired private ThumbImageConfig thumbImageConfig; @Test public void testUpload() throws FileNotFoundException { File file = new File("G:\\test\\baby.png"); // 上传并生成缩略图 StorePath storePath = this.storageClient.uploadFile(new FileInputStream(file), file.length(), "png", null); // 带分组的路径 System.out.println(storePath.getFullPath()); // 不带分组的路径 System.out.println(storePath.getPath()); } @Test public void testUploadAndCreateThumb() throws FileNotFoundException { File file = new File("G:\\test\\baby.png"); // 上传并且生成缩略图 StorePath storePath = this.storageClient.uploadImageAndCrtThumbImage(new FileInputStream(file), file.length(), "png", null); // 带分组的路径 System.out.println(storePath.getFullPath()); // 不带分组的路径 System.out.println(storePath.getPath()); // 获取缩略图路径 String path = thumbImageConfig.getThumbImagePath(storePath.getPath()); System.out.println(path); } } ================================================ FILE: ly-user/ly-user-interface/pom.xml ================================================ ly-user com.leyou.user 1.0.0-SNAPSHOT 4.0.0 com.leyou.user ly-user-interface 1.0.0-SNAPSHOT javax.persistence persistence-api 1.0 com.fasterxml.jackson.core jackson-databind org.hibernate.validator hibernate-validator org.projectlombok lombok org.springframework spring-webmvc 5.0.6.RELEASE ================================================ FILE: ly-user/ly-user-interface/src/main/java/api/UserApi.java ================================================ package api; import com.leyou.user.pojo.User; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; public interface UserApi { @GetMapping("/query") User queryUser(@RequestParam("username") String username, @RequestParam("password") String password); } ================================================ FILE: ly-user/ly-user-interface/src/main/java/com/leyou/user/pojo/User.java ================================================ package com.leyou.user.pojo; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import org.hibernate.validator.constraints.Length; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import javax.validation.constraints.Pattern; import java.util.Date; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-13 14:04 */ @Data @Table(name = "tb_user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Length(min = 4, max = 30, message = "用户名只能在4~30位之间") private String username; // 用户名 @JsonIgnore @Length(min = 4, max = 30, message = "密码只能在4~30位之间") private String password; // 密码 @Pattern(regexp = "^1[35678]\\d{9}$", message = "手机号格式不正确") private String phone; // 电话 private Date created; // 创建时间 @JsonIgnore private String salt; // 密码的盐值 } ================================================ FILE: ly-user/ly-user-service/pom.xml ================================================ ly-user com.leyou.user 1.0.0-SNAPSHOT 4.0.0 com.leyou.user ly-user-service 1.0.0-SNAPSHOT org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.boot spring-boot-starter-jdbc org.mybatis.spring.boot mybatis-spring-boot-starter tk.mybatis mapper-spring-boot-starter mysql mysql-connector-java org.springframework.boot spring-boot-starter-amqp org.springframework.boot spring-boot-starter-data-redis commons-codec commons-codec org.apache.commons commons-lang3 com.leyou.user ly-user-interface 1.0.0-SNAPSHOT com.leyou.common ly-common ${leyou.latest.version} ================================================ FILE: ly-user/ly-user-service/src/main/java/com/leyou/user/LyUserApplication.java ================================================ package com.leyou.user; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import tk.mybatis.spring.annotation.MapperScan; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-13 13:58 */ @SpringBootApplication @EnableDiscoveryClient @MapperScan("com.leyou.user.mapper") public class LyUserApplication { public static void main(String[] args) { SpringApplication.run(LyUserApplication.class, args); } } ================================================ FILE: ly-user/ly-user-service/src/main/java/com/leyou/user/controller/UserController.java ================================================ package com.leyou.user.controller; import com.leyou.user.pojo.User; import com.leyou.user.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import javax.validation.Valid; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-13 14:04 */ @Controller public class UserController { @Autowired private UserService userService; /** * 校验数据是否可用 * 实现用户数据的校验,主要包括对:手机号、用户名的唯一性校验 * * @param data * @param type * @return */ @GetMapping("/check/{data}/{type}") public ResponseEntity checkUserData(@PathVariable("data") String data, @PathVariable(value = "type") Integer type) { Boolean boo = this.userService.checkData(data, type); if (boo == null) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } return ResponseEntity.ok(boo); } /** * 发送手机验证码 * * @param phone * @return */ @PostMapping("/code") public ResponseEntity sendVerifyCode(@RequestParam("phone") String phone) { Boolean boo = this.userService.sendVerifyCode(phone); if (boo == null || !boo) { return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } return new ResponseEntity<>(HttpStatus.CREATED); } /** * 用户注册 * * @param user * @param code * @return */ @PostMapping("/register") public ResponseEntity register(@Valid User user, @RequestParam("code") String code) { Boolean boo = this.userService.register(user, code); if (boo == null || !boo) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } return new ResponseEntity<>(HttpStatus.CREATED); } /** * 根据用户名和密码查询用户 * * @param username * @param password * @return */ @GetMapping("/query") public ResponseEntity queryUser( @RequestParam("username") String username, @RequestParam("password") String password ) { User user = this.userService.queryUser(username, password); if (user == null) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } return ResponseEntity.ok(user); } } ================================================ FILE: ly-user/ly-user-service/src/main/java/com/leyou/user/mapper/UserMapper.java ================================================ package com.leyou.user.mapper; import com.leyou.user.pojo.User; import tk.mybatis.mapper.common.Mapper; public interface UserMapper extends Mapper { } ================================================ FILE: ly-user/ly-user-service/src/main/java/com/leyou/user/service/UserService.java ================================================ package com.leyou.user.service; import com.leyou.common.utils.NumberUtils; import com.leyou.user.mapper.UserMapper; import com.leyou.user.pojo.User; import com.leyou.user.utils.CodecUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.core.AmqpTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-13 14:04 */ @Service public class UserService { @Autowired private UserMapper userMapper; @Autowired private StringRedisTemplate redisTemplate; @Autowired private AmqpTemplate amqpTemplate; static final String KEY_PREFIX = "user:code:phone:"; static final Logger logger = LoggerFactory.getLogger(UserService.class); /** * 校验数据是否可用 * 实现用户数据的校验,主要包括对:手机号、用户名的唯一性校验 * * @param data * @param type * @return */ public Boolean checkData(String data, Integer type) { User record = new User(); switch (type) { case 1: record.setUsername(data); break; case 2: record.setPhone(data); break; default: return null; } return this.userMapper.selectCount(record) == 0; } /** * 发送手机验证码 * * @param phone * @return */ public Boolean sendVerifyCode(String phone) { // 生成6位验证码 String code = NumberUtils.generateCode(6); try { // 发送短信 Map msg = new HashMap<>(); msg.put("phone", phone); msg.put("code", code); // 发送消息到RabbitMQ this.amqpTemplate.convertAndSend("leyou.sms.exchange", "sms.verify.code", msg); // 将code存入redis,存储5min this.redisTemplate.opsForValue().set(KEY_PREFIX + phone, code, 5, TimeUnit.MINUTES); return true; } catch (Exception e) { logger.error("发送短信失败。phone:{}, code:{}", phone, code); return false; } } /** * 用户注册 * * @param user * @param code * @return */ public Boolean register(User user, String code) { // 校验短信验证码 String cacheCode = this.redisTemplate.opsForValue().get(KEY_PREFIX + user.getPhone()); if (!StringUtils.equals(code, cacheCode)) { return false; } // 生成盐 String salt = CodecUtils.generateSalt(); user.setSalt(salt); // 对密码加密 user.setPassword(CodecUtils.md5Hex(user.getPassword(), salt)); // 强制设置不能指定的参数为null user.setId(null); user.setCreated(new Date()); // 添加到数据库 boolean b = this.userMapper.insertSelective(user) == 1; if (b) { // 注册成功,删除redis中的记录 this.redisTemplate.delete(KEY_PREFIX + user.getPhone()); } return b; } /** * 根据用户名和密码查询用户 * * @param username * @param password * @return */ public User queryUser(String username, String password) { // 查询 User record = new User(); record.setUsername(username); User user = this.userMapper.selectOne(record); // 校验用户名 if (user == null) { return null; } // 校验密码 if (!user.getPassword().equals(CodecUtils.md5Hex(password, user.getSalt()))) { return null; } // 用户名密码都正确 return user; } } ================================================ FILE: ly-user/ly-user-service/src/main/java/com/leyou/user/utils/CodecUtils.java ================================================ package com.leyou.user.utils; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import java.util.UUID; /** * @Author: TianCi.Xiong * @Description: * @Date: Created in 2019-11-13 22:54 */ public class CodecUtils { public static String md5Hex(String data, String salt) { if (StringUtils.isBlank(salt)) { salt = data.hashCode() + ""; } return DigestUtils.md5Hex(salt + DigestUtils.md5Hex(data)); } public static String shaHex(String data, String salt) { if (StringUtils.isBlank(salt)) { salt = data.hashCode() + ""; } return DigestUtils.sha512Hex(salt + DigestUtils.sha512Hex(data)); } public static String generateSalt() { return StringUtils.replace(UUID.randomUUID().toString(), "-", ""); } } ================================================ FILE: ly-user/ly-user-service/src/main/resources/application.yml ================================================ server: port: 8085 spring: application: name: user-service datasource: url: jdbc:mysql://127.0.0.1:3306/leyou username: root password: 1234 driver-class-name: com.mysql.jdbc.Driver rabbitmq: host: 192.168.56.101 username: leyou password: 123456 virtual-host: /leyou redis: host: 192.168.56.101 eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka instance: lease-renewal-interval-in-seconds: 5 lease-expiration-duration-in-seconds: 15 mybatis: type-aliases-package: com.leyou.user.pojo ================================================ FILE: ly-user/ly-user-service/src/test/java/com/leyou/test/RedisTest.java ================================================ package com.leyou.test; import com.leyou.user.LyUserApplication; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.BoundHashOperations; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.test.context.junit4.SpringRunner; import java.util.Map; import java.util.concurrent.TimeUnit; @RunWith(SpringRunner.class) @SpringBootTest(classes = LyUserApplication.class) public class RedisTest { @Autowired private StringRedisTemplate redisTemplate; @Test public void testRedis() { // 存储数据 this.redisTemplate.opsForValue().set("key1", "value1"); // 获取数据 String val = this.redisTemplate.opsForValue().get("key1"); System.out.println("val = " + val); } @Test public void testRedis2() { // 存储数据,并指定剩余生命时间,5小时 this.redisTemplate.opsForValue().set("key2", "value2", 5, TimeUnit.HOURS); } @Test public void testHash() { BoundHashOperations hashOps = this.redisTemplate.boundHashOps("user"); // 操作hash数据 hashOps.put("name", "jack"); hashOps.put("age", "21"); // 获取单个数据 Object name = hashOps.get("name"); System.out.println("name = " + name); // 获取所有数据 Map map = hashOps.entries(); for (Map.Entry me : map.entrySet()) { System.out.println(me.getKey() + " : " + me.getValue()); } } } ================================================ FILE: ly-user/pom.xml ================================================ leyou com.leyou.parent 1.0.0-SNAPSHOT 4.0.0 com.leyou.user ly-user pom 1.0.0-SNAPSHOT 用户中心父工程 ly-user-interface ly-user-service ================================================ FILE: mvnw ================================================ #!/bin/sh # ---------------------------------------------------------------------------- # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you 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 # # https://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. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Maven2 Start Up Batch script # # Required ENV vars: # ------------------ # JAVA_HOME - location of a JDK home dir # # Optional ENV vars # ----------------- # M2_HOME - location of maven2's installed home dir # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 # MAVEN_SKIP_RC - flag to disable loading of mavenrc files # ---------------------------------------------------------------------------- if [ -z "$MAVEN_SKIP_RC" ] ; then if [ -f /etc/mavenrc ] ; then . /etc/mavenrc fi if [ -f "$HOME/.mavenrc" ] ; then . "$HOME/.mavenrc" fi fi # OS specific support. $var _must_ be set to either true or false. cygwin=false; darwin=false; mingw=false case "`uname`" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true;; Darwin*) darwin=true # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then export JAVA_HOME="`/usr/libexec/java_home`" else export JAVA_HOME="/Library/Java/Home" fi fi ;; esac if [ -z "$JAVA_HOME" ] ; then if [ -r /etc/gentoo-release ] ; then JAVA_HOME=`java-config --jre-home` fi fi if [ -z "$M2_HOME" ] ; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done saveddir=`pwd` M2_HOME=`dirname "$PRG"`/.. # make it fully qualified M2_HOME=`cd "$M2_HOME" && pwd` cd "$saveddir" # echo Using m2 at $M2_HOME fi # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin ; then [ -n "$M2_HOME" ] && M2_HOME=`cygpath --unix "$M2_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --unix "$CLASSPATH"` fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then [ -n "$M2_HOME" ] && M2_HOME="`(cd "$M2_HOME"; pwd)`" [ -n "$JAVA_HOME" ] && JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" # TODO classpath? fi if [ -z "$JAVA_HOME" ]; then javaExecutable="`which javac`" if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. readLink=`which readlink` if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then if $darwin ; then javaHome="`dirname \"$javaExecutable\"`" javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" else javaExecutable="`readlink -f \"$javaExecutable\"`" fi javaHome="`dirname \"$javaExecutable\"`" javaHome=`expr "$javaHome" : '\(.*\)/bin'` JAVA_HOME="$javaHome" export JAVA_HOME fi fi fi if [ -z "$JAVACMD" ] ; then if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi else JAVACMD="`which java`" fi fi if [ ! -x "$JAVACMD" ] ; then echo "Error: JAVA_HOME is not defined correctly." >&2 echo " We cannot execute $JAVACMD" >&2 exit 1 fi if [ -z "$JAVA_HOME" ] ; then echo "Warning: JAVA_HOME environment variable is not set." fi CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { if [ -z "$1" ] then echo "Path not specified to find_maven_basedir" return 1 fi basedir="$1" wdir="$1" while [ "$wdir" != '/' ] ; do if [ -d "$wdir"/.mvn ] ; then basedir=$wdir break fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then wdir=`cd "$wdir/.."; pwd` fi # end of workaround done echo "${basedir}" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then echo "$(tr -s '\n' ' ' < "$1")" fi } BASE_DIR=`find_maven_basedir "$(pwd)"` if [ -z "$BASE_DIR" ]; then exit 1; fi ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found .mvn/wrapper/maven-wrapper.jar" fi else if [ "$MVNW_VERBOSE" = true ]; then echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." fi jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" while IFS="=" read key value; do case "$key" in (wrapperUrl) jarUrl="$value"; break ;; esac done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" if [ "$MVNW_VERBOSE" = true ]; then echo "Downloading from: $jarUrl" fi wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" if command -v wget > /dev/null; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found wget ... using wget" fi wget "$jarUrl" -O "$wrapperJarPath" elif command -v curl > /dev/null; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found curl ... using curl" fi curl -o "$wrapperJarPath" "$jarUrl" else if [ "$MVNW_VERBOSE" = true ]; then echo "Falling back to using Java to download" fi javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" if [ -e "$javaClass" ]; then if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo " - Compiling MavenWrapperDownloader.java ..." fi # Compiling the Java class ("$JAVA_HOME/bin/javac" "$javaClass") fi if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then # Running the downloader if [ "$MVNW_VERBOSE" = true ]; then echo " - Running MavenWrapperDownloader.java ..." fi ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") fi fi fi fi ########################################################################################## # End of extension ########################################################################################## export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} if [ "$MVNW_VERBOSE" = true ]; then echo $MAVEN_PROJECTBASEDIR fi MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then [ -n "$M2_HOME" ] && M2_HOME=`cygpath --path --windows "$M2_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --windows "$CLASSPATH"` [ -n "$MAVEN_PROJECTBASEDIR" ] && MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` fi WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain exec "$JAVACMD" \ $MAVEN_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" ================================================ FILE: mvnw.cmd ================================================ @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @REM distributed with this work for additional information @REM regarding copyright ownership. The ASF licenses this file @REM to you under the Apache License, Version 2.0 (the @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM @REM https://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @REM KIND, either express or implied. See the License for the @REM specific language governing permissions and limitations @REM under the License. @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- @REM Maven2 Start Up Batch script @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars @REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files @REM ---------------------------------------------------------------------------- @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' @echo off @REM set title of command window title %0 @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @REM Execute a user defined script before this one if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre @REM check for pre script, once with legacy .bat ending and once with .cmd ending if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" :skipRcPre @setlocal set ERROR_CODE=0 @REM To isolate internal variables from possible post scripts, we use another setlocal @setlocal @REM ==== START VALIDATION ==== if not "%JAVA_HOME%" == "" goto OkJHome echo. echo Error: JAVA_HOME not found in your environment. >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error :OkJHome if exist "%JAVA_HOME%\bin\java.exe" goto init echo. echo Error: JAVA_HOME is set to an invalid directory. >&2 echo JAVA_HOME = "%JAVA_HOME%" >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error @REM ==== END VALIDATION ==== :init @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". @REM Fallback to current working directory if not found. set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir set EXEC_DIR=%CD% set WDIR=%EXEC_DIR% :findBaseDir IF EXIST "%WDIR%"\.mvn goto baseDirFound cd .. IF "%WDIR%"=="%CD%" goto baseDirNotFound set WDIR=%CD% goto findBaseDir :baseDirFound set MAVEN_PROJECTBASEDIR=%WDIR% cd "%EXEC_DIR%" goto endDetectBaseDir :baseDirNotFound set MAVEN_PROJECTBASEDIR=%EXEC_DIR% cd "%EXEC_DIR%" :endDetectBaseDir IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig @setlocal EnableExtensions EnableDelayedExpansion for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% :endReadAdditionalConfig SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @REM This allows using the maven wrapper in projects that prohibit checking in binary data. if exist %WRAPPER_JAR% ( echo Found %WRAPPER_JAR% ) else ( echo Couldn't find %WRAPPER_JAR%, downloading it ... echo Downloading from: %DOWNLOAD_URL% powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" echo Finished downloading %WRAPPER_JAR% ) @REM End of extension %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end :error set ERROR_CODE=1 :end @endlocal & set ERROR_CODE=%ERROR_CODE% if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost @REM check for post script, once with legacy .bat ending and once with .cmd ending if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" :skipRcPost @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' if "%MAVEN_BATCH_PAUSE%" == "on" pause if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% exit /B %ERROR_CODE% ================================================ FILE: pom.xml ================================================ 4.0.0 com.leyou.parent leyou 1.0.0-SNAPSHOT ly-registry ly-api-gateway ly-item ly-common ly-upload ly-search ly-goods-web ly-user ly-sms-service ly-auth ly-cart ly-order pom leyou 乐优商城 org.springframework.boot spring-boot-starter-parent 2.0.1.RELEASE UTF-8 UTF-8 1.8 Finchley.RC1 1.3.2 2.0.2 1.1.9 5.1.32 1.2.3 1.0.0-SNAPSHOT 1.26.1-RELEASE 1.16.18 2.9.6 org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import org.mybatis.spring.boot mybatis-spring-boot-starter ${mybatis.starter.version} tk.mybatis mapper-spring-boot-starter ${mapper.starter.version} com.github.pagehelper pagehelper-spring-boot-starter ${pageHelper.starter.version} mysql mysql-connector-java ${mysql.version} com.github.tobato fastdfs-client ${fastDFS.client.version} org.projectlombok lombok ${lombok.version} provided com.fasterxml.jackson.core jackson-databind ${jackson.version} org.springframework.boot spring-boot-maven-plugin spring-milestones Spring Milestones https://repo.spring.io/milestone false