文章目录
- 一、租房网项目的介绍
- 二、环境搭建
- 三、项目架构搭建
- 1、使用FastDFS上传图片并将保存路径存入MongoDB
- 2、使用IDEA创建父工程livegoods
- 3、创建服务注册中心模块Eureka(8761)
- 4、Eureka服务容器化
- 5、创建配置中心模块ConfigServer(9010)
- 6、创建首页轮播图模块Banner(9000)
- 7、创建预订商品模块Buyaction(9008)
- 8、创建预订商品消息消费模块Message
- 9、创建商品预订开始时间模块Buytime(9006)
- 10、创建Redis缓存服务器模块
- 11、创建商品评价模块Comment(9005)
- 12、创建公共模块Commons
- 13、创建商品详情模块Details(9004)
- 14、创建网关模块Gateway(4006)
- 15、创建热销商品模块HotProduct(9001)
- 16、创建登录模块Login(9007)
- 17、创建MongoDB数据库模块
- 18、创建用户订单模块Order(9009)
- 19、创建RabbitMQ模块
- 20、创建推荐商品模块Recommendation(9002)
- 21、创建商品搜索模块Search(9003)
- 四、基础功能构建
- 五、模块开发
- 1、Banner轮播图模块开发
- 2、HotProduct热销商品模块开发
- 3、Recommendation推荐商品模块开发
- 4、Details商品详情模块开发
- 5、Details商品详情模块添加缓存
- 6、Comment商品评论模块开发
- 7、Comment查询商品评论模块开发
- 8、安装kibana
- 9、Search查询商品模块开发
- 10、Login登录模块开发
- 11、Buyaction房屋预订模块开发
- 12、Message预订商品的消息消费者模块开发
- 13、Buytime商品预订开始时间模块开发
- 14、Resilience4j的circuitbreaker断路器和ratelimiter访问频率限制器配置
- 15、Order订单模块开发
- 16、OpenFeign调用
- 17、发送阿里云的短信验证码
- 项目的问题
一、租房网项目的介绍
1、使用的技术介绍
前端页面开发使用vue.js
后端技术使用SpringBoot + Spring Cloud + SpringMVC + Spring Security + MongoDB + Redis + ElasticSearch + Maven + FastDFS + Docker
2、使用的组件和开发工具的版本以及作用
虚拟机CentOS7
JDK17
SpringCloud 2022.0.2
用注解方式实现对Spring框架的自动配置SpringBoot 3.0.5
项目配置中心SpringCloudConfig 4.0.2
消息中间件RabbitMQ 3.9.29(带管理端)
消息驱动SpringCloudStream 4.0.2
服务注册中心Eureka 4.0.1
远程调用组件OpenFeign 4.0.2
熔断、限流组件Resilience4j 3.0.1
轻量级的控制反转(IOC)和面向切面(AOP)的容器框架SpringFramework 5.2.5
基于MVC的轻量级WEB开发框架 SpringMVCFramework 5.2.5
安全框架SpringSecurity 2.6.3
缓存服务器Redis 6.2.6
搜索服务Elasticsearch 7.17.7
图片上传服务FastDFS 1.27
数据库服务器MongoDB 5.0.5
容器虚拟化及自动部署工具Docker 1.13.1
3、项目模块结构
livegoods 父工程
livegoods_config_server 公共配置管理中心
livegoods_gateway 网关,负责服务路由转发
livegoods_banner 轮播图展示
livegoods_buyaction 预定商品并发送消息
livegoods_buyaction_message_consumer 预定后更新商品并消费信息
livegoods_buytime 查询商品预定时间
livegoods_cache_redis redis配置模块
livegoods_comment 新增或查询商品评论
livegoods_common 公共模块
livegoods_detail 查询商品详情
livegoods_hot_product 查询热销商品
livegoods_login 登录模块
livegoods_mongodb_dao mongodb连接配置
livegoods_order 查询用户订单
livegoods_recommendation 查询推荐商品
livegoods_search 搜索商品模块
4、项目总体架构
整个项目使用微服务架构,并使用SpringCloud2022版本作为微服务架构总体实现技术。拆分颗粒度为接口,每个接口对应一个功能。使用Eureka作为注册中心,使用Gateway作为网关,使用Config作为分布式配置中心,使用OpenFeign进行远程调用,使用Resilience4j进行服务熔断,使用Elasticsearch提升搜索效率,缓存工具使用Redis,缓存技术使用SpringCache,数据库使用MongoDB,数据访问技术使用SpringData,图片上传下载使用FastDFS。MongoDB身为NoSQL数据库,又带有索引,本身读取性能就很高,此处可以使用Redis作为缓存工具,也可以直接从MongoDB中读取数据。本次项目使用Redis作为缓存工具,SpringCache作为缓存技术。
二、环境搭建
1、启动前端服务
- 使用VSCODE开发工具,打开前端项目的文件夹,点击上方的Terminal→New Terminal,然后在终端输入:
npm run serve
,即启动服务
2、CentOS7各个组件的安装
2.1 安装Docker
-
安装docker:
yum -y install docker
-
找到阿里云容器镜像的地址
-
编辑docker的配置:
vim /etc/docker/daemon.json
,配置如下
java { "registry-mirrors":["https://eldcdls0.mirror.aliyuncs.com"] }
-
生效配置:
systemctl daemon-reload
-
启动docker:
systemctl start docker
-
docker开机自启动:
systemctl enable docker
2.2 安装JDK
- 下载Linux版本的JDK17:
https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz
- 将压缩包上传到/opt目录下,然后解压到/usr/local/目录:
tar -zxvf /opt/jdk-17_linux-x64_bin.tar.gz -C /usr/local/
- 将安装目录复制起来,配置JAVA环境变量:
vim /etc/profile
,在该文件最后一行添加如下配置
java export JAVA_HOME=/usr/local/jdk-17.0.7/ export PATH=$PATH:$JAVA_HOME/bin
- 生效profile配置文件:source /etc/profile
- 查看jdk的版本号:java -version
- 如果出现OpenJDK,即JDK配置没有生效,则进行如下操作:
- 查看OpenJDK相关文件:rpm -qa |grep java
- 卸载所有相关文件:rpm -e --nodeps 文件名
- 生效profile文件:source /etc/profile
- 查看jdk的版本号:java -version
2.3 安装Redis(6390)
- 拉取镜像并运行redis容器:
docker run -d --name redis -p 6390:6379 redis --requirepass "123456"
- 查看redis是否启动:
docker ps -a|grep redis
- 进入到redis容器:
docker exec -it redis bash
- 在redis容器中进入redis客户端:
redis-cli -h localhost -p 6379
- 在redis客户端中进行权限认证:
auth 123456
,然后就可以使用redis客户端了 - 退出容器:
ctrl + p + q
2.4 安装FastDFS(8888)
-
拉取FastDFS镜像:
docker pull registry.cn-beijing.aliyuncs.com/tianzuo/fastdfs
-
运行fastdfs容器:
docker run -d --restart=always --privileged=true --net=host --name=fastdfs -e IP=192.168.126.30 -e WEB_PORT=8888 -v ${HOME}/fastdfs:/var/local/fdfs registry.cn-beijing.aliyuncs.com/tianzuo/fastdfs
-
进入fastdfs容器中:
docker exec -it fastdfs bash
-
修改client.conf配置文件:
vi /etc/fdfs/client.conf
java base_path=/var/local/fdfs/storage tracker_server=192.168.126.30:22122
此时配置文件一般是没有问题的,检验一下即可。
如果不能在容器内修改,则在容器外进行修改,然后将文件拷贝到容器内。 -
上传图片测试:
/usr/bin/fdfs_upload_file /etc/fdfs/client.conf /etc/fdfs/anti-steal.jpg
/usr/bin/fdfs_upload_file是上传图片的命令,/etc/fdfs/client.conf是FastDFS服务的配置文件, /etc/fdfs/anti-steal.jpg是需要上传的图片
此时返回一个图片的路径。 -
在浏览器中访问该图片:
http://192.168.126.30:8888/group1/M00/00/00/wKh-HmRhnjGACbtyAABdreSfEnY928.jpg
2.5 安装MongoDB(27017)
- 拉取mongodb的镜像:
docker pull mongo:latest
- 运行mongo容器:
docker run -itd --name mongo -p 27017:27017 mongo:latest --auth
- 进入到Mongo容器:
docker exec -it mongo bash
- 再进入到Mongo终端:
mongo
- 使用admin数据库:
use admin
- 创建管理员账户:
db.createUser({ user: "root", pwd: "root", roles: [{ role: "root", db: "admin" }]})
role: "root"即设置为超级用户。 - 验证用户添加是否成功:
db.auth("root", "root")
,返回1则添加成功 - 下载MongoDB的可视化工具nosqlbooster:
https://s3.nosqlbooster.com/download/releasesv8/nosqlbooster4mongo-8.0.10.exe
下载后,双击可执行文件即可。 - 使用nosqlbooster连接MongoDB
- 使用刚刚创建的超级用户进行权限认证
2.6 安装RabbitMQ(15672)
- 退出Mongo容器:
ctrl + p + q
- 拉取RabbiMQ镜像:
docker pull rabbitmq:3.9.29-management-alpine
- 运行rabbitmq容器:
docker run -d -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin --name rabbitmq rabbitmq:3.9.29-management-alpine
- 浏览器访问:
http://192.168.126.30:15672
创建容器时,指定的账户和密码都是admin。可以直接登录。 - 如果需要,可以设置rabbitmq开机自启:
docker update rabbitmq --restart=always
2.7 安装Elasticsearch
-
下载Elasticsearch7.17.7版本:
https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.7-linux-x86_64.tar.gz
-
将该压缩包通过Mobax终端上传到目录/opt中,然后解压到目录/usr/local/中:
tar -zxvf /opt/elasticsearch-7.17.7-linux-x86_64.tar.gz -C /usr/local/
-
安装ik分词器到该ES服务中:
1)下载IKAnalyzer:https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.17.7/elasticsearch-analysis-ik-7.17.7.zip
2)通过mobax上传至虚拟机的目录/opt,然后进行解压缩:unzip /opt/elasticsearch-analysis-ik-7.17.7.zip -d /usr/local/elasticsearch-7.17.7/plugins/analysis-ik
-
因为ES不能以root用户运行,需要创建一个非root用户es:
useradd es
-
配置最大可创建文件数大小
1)打开系统文件:vim /etc/sysctl.conf
2)添加一下配置:vm.max_map_count=655360
,保存退出
3)在命令行使配置生效:sysctl -p
-
修改limits.conf:
vim /etc/security/limits.conf
,在末尾添加如下配置* soft nofile 65536 * hard nofile 65536 * soft nproc 4096 * hard nproc 4096
-
修改elasticsearch.yml配置文件:
vim /usr/local/elasticsearch-7.17.7/config/elasticsearch.yml
,在配置文件中直接添加如下配置,或者它是有默认的,但是被注释了,可以取消注释后再修改对应的值。node.name: node-1 network.host: 0.0.0.0 http.port: 9200 cluster.initial_master_nodes: ["node-1"]
-
让es用户取得该文件夹权限:
chown -R es:es /usr/local/elasticsearch-7.17.7/
-
切换为es用户:
su es
-
启动Elasticsearch服务
1)进入到elasticsearch的bin目录中:cd /usr/local/elasticsearch-7.17.7/bin/
2)启动Elasticsearch服务:./elasticsearch
-
在浏览器中访问:
http://192.168.126.30:9200/
三、项目架构搭建
1、使用FastDFS上传图片并将保存路径存入MongoDB
-
将图片上传到/usr/local/images目录下,再将/usr/local/images文件夹拷贝到容器中:
docker cp /usr/local/images fastdfs:/usr/local
-
进入到FastDFS容器中:
docker exec -it fastdfs bash
-
将项目图片一个一个上传FastDFS服务中:
/usr/bin/fdfs_upload_file /etc/fdfs/client.conf /usr/local/images/1.png
,最后需要把返回的图片路径保存起来group1/M00/00/00/wKh-HmRkKYqAdO94ABLGy4zg5nA331.png group1/M00/00/00/wKh-HmRkKZSAHdeaAAjIobLVxBY100.png group1/M00/00/00/wKh-HmRkKZ2AB31QAAro9yfjOVw191.png group1/M00/00/00/wKh-HmRkKaqAFvd4AAuC467ZRns688.png group1/M00/00/00/wKh-HmRkKbWAUiP3ABS0Lvm4RSc742.png group1/M00/00/00/wKh-HmRkKcCAHvxGABHVENCjbfU596.png
-
在 MongoDB中创建livegoods的数据库,创建 banner 的集合,并
录入 6条数据。ip 及端口通过yml文件进行设置。双击banner集合,执行如下命令,然后点击run按钮即可。use livegoods db.banner.insert({"url":"/group1/M00/00/00/wKh-HmRkKYqAdO94ABLGy4zg5nA331.png","createTime":new Date()}) db.banner.insert({"url":"/group1/M00/00/00/wKh-HmRkKZSAHdeaAAjIobLVxBY100.png","createTime":new Date()}) db.banner.insert({"url":"/group1/M00/00/00/wKh-HmRkKZ2AB31QAAro9yfjOVw191.png","createTime":new Date()}) db.banner.insert({"url":"/group1/M00/00/00/wKh-HmRkKaqAFvd4AAuC467ZRns688.png","createTime":new Date()}) db.banner.insert({"url":"/group1/M00/00/00/wKh-HmRkKbWAUiP3ABS0Lvm4RSc742.png","createTime":new Date()}) db.banner.insert({"url":"/group1/M00/00/00/wKh-HmRkKcCAHvxGABHVENCjbfU596.png","createTime":new Date()})
此时url的值就是刚刚上传图片到FastDFS时返回的路径。
2、使用IDEA创建父工程livegoods
-
创建父工程livegoods
-
删除livegoods父工程项目的src目录
-
修改livegoods父工程项目的POM文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zzx</groupId> <artifactId>livegoods</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <spring-boot-version>3.0.5</spring-boot-version> <cloud-version>2022.0.2</cloud-version> <lcn-version>5.0.0</lcn-version> <fastdfs-version>1.27</fastdfs-version> <commons-lang-version>3.12.0</commons-lang-version> <lombok-version>1.18.26</lombok-version> <config-client-version>4.0.0</config-client-version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot-version} </version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${cloud-version} </version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok-version} </version> </dependency> <!-- fastdfs --> <dependency> <groupId>cn.bestwu</groupId> <artifactId>fastdfs-client-java</artifactId> <version>${fastdfs-version} </version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>${commons-langversion}</version> </dependency> </dependencies> </dependencyManagement> <build> <pluginManagement> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>1.0.0</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.9.0</version> <configuration> <target>17</target> <source>17</source> </configuration> </plugin> </plugins> </pluginManagement> </build> </project>
-
开启Run Dashboard面板,打开livegoods项目的.idea文件夹下找到workspace.xml文件
添加如下代码:<component name="RunDashboard"> <option name="ruleStates"> <list> <RuleState> <option name="name" value="ConfigurationTypeDashboardGroupingRule" /> </RuleState> <RuleState> <option name="name" value="StatusDashboardGroupingRule" /> </RuleState> </list> </option> <option name="configurationTypes"> <set> <option value="SpringBootApplicationConfigurationType" /> </set> </option> </component>
然后重启IDEA即可。
3、创建服务注册中心模块Eureka(8761)
-
在livegoods父工程下,创建服务注册中心模块livegoods-eureka
-
修改livegoods-eureka模块的POM文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>livegoods</artifactId> <groupId>com.zzx</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>livegoods-eureka</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>1.0.0</version> <configuration> <imageName>livegoods/eurekaserver:1.0</imageName> <baseImage>openjdk:17</baseImage> <dockerHost>http://192.168.126.30:2375</dockerHost> <entryPoint>["java", "-jar", "/${project.build.finalName}.jar"]</entryPoint> <exposes> <expose>8761</expose> </exposes> <resources> <resources> <targetPath>/</targetPath> <directory>${project.build.directory} </directory> <include>${project.build.finalName}.jar</include> </resources> </resources> </configuration> </plugin> </plugins> </build> </project>
-
在livegoods-eureka模块的com.livegoods包下,创建主启动类EurekaServerApplication
package com.livegoods; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class,args); } }
-
在livegoods-eureka模块的resources目录下,创建application.yml文件,配置如下
server: port: 8761 spring: application: name: livegoods-eureka-server eureka: client: # 不在Eureka服务中进行注册 fetch-registry: false register-with-eureka: false
-
测试Eureka,浏览器访问:
http://localhost:8761
4、Eureka服务容器化
-
livegoods-eureka模块打成jar包,即打开maven界面,双击package,然后等待BUILDSUCCESS
-
修改docker.service文件,打开2375端口:
vim /usr/lib/systemd/system/docker.service
,修改的代码如下ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock \
- 重新载入服务:
systemctl daemon-reload
- 重启docker:
systemctl restart docker
- 双击docker build,等待BUILD SUCCESS,在刚刚开始会报错,等待一会就好了
因为在livegoods-eureka项目的pom文件中配置过虚拟机的信息,此时会将livegoods/eureka:1.0镜像保存到虚拟机中。 - 启动eureka容器:
docker run -p 8761:8761 --name eureka -d livegoods/eurekaserver:1.0
- 在浏览器中访问:
http://192.168.126.30:8761
5、创建配置中心模块ConfigServer(9010)
-
在livegoods父工程下,创建配置中心模块livegoods-config-server
-
创建一个gitee仓库,在项目文件夹下右键选择GIT BASE HERE,然后将这些命令复制执行即可。最后需要在gitee上面对应的仓库中,点击管理,然后选择开源,选择许可证后保存即可。
如果没有GIT BASE HERE这个选项则需要去安装,可以自己下载安装也可以在IDEA中进行安装
-
修改livegoods-config-server模块的POM文件,配置如下
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>livegoods</artifactId> <groupId>com.zzx</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>livegoods-config-server</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--配置中心客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> </dependencies> </project>
-
在livegoods-config-server模块的resources目录下,创建application.yml文件,配置如下
server: port: 9010 spring: application: name: livegoods-config-server cloud: config: server: git: uri: https://gitee.com/zzx0402/zfw-cloud-config.git search-paths: - livegoods label: master eureka: client: service-url: defaultZone: http://192.168.126.30:8761/eureka/ instance: prefer-ip-address: true
-
在livegoods-config-server模块的com.livegoods.config包下,创建ConfigServerApplication主启动类,代码如下
package com.livegoods.config; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; @SpringBootApplication @EnableConfigServer public class ConfigServerApplication { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication.class,args); } }
-
在gitee上面创建livegoods-dev.yml文件,然后点击编辑,即可写入。或者使用刚刚配置gitee文件夹中写完再上传到gitee中。
eureka: client: service-url: defaultZone: http://192.168.126.30:8761/eureka/ instance: prefer-ip-address: true livegoods: banner: nginx: prefix: http://192.168.126.30:8888/
6、创建首页轮播图模块Banner(9000)
-
在livegoods父工程下,创建首页轮播图模块livegoods-banner
-
修改livegoods-banner模块的POM文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>livegoods</artifactId> <groupId>com.zzx</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>livegoods-banner</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--配置中心客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--默认加载bootstrap--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <configuration> <imageName>livegoods/banner:1.0</imageName> <baseImage>openjdk:17</baseImage> <dockerHost>http://192.168.126.30:2375</dockerHost> <entryPoint>["java", "-jar", "/${project.build.finalName}.jar"] </entryPoint> <exposes> <expose>9000</expose> </exposes> <resources> <resources> <targetPath>/</targetPath> <directory>${project.build.directory} </directory> <include>${project.build.finalName}.jar</include> </resources> </resources> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.9.0</version> <configuration> <target>17</target> <source>17</source> </configuration> </plugin> </plugins> </build> </project>
-
在livegoods-banner模块的resources目录下,创建bootstrap.yml配置文件
server: port: 9000 spring: application: name: livegoods-banner profiles: active: mongodb # bannerNginx,此处添加rabbit配置,目的是自动刷新configServer的配置 cloud: config: uri: http://localhost:9010 label: master name: livegoods profile: dev # 暴露健康检查端口 management: endpoints: web: exposure: include: refresh logging: pattern: console: '%d{MM/dd HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
此时因为配置中心已经存在eureka的配置了,所以Banner等都不需要直接配置Eureka了,配置配置中心即可。
-
在livegoods-banner模块的com.livegoods.banner包下,创建主启动类BannerApplication,代码如下
package com.livegoods.banner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class BannerApplication { public static void main(String[] args) { SpringApplication.run(BannerApplication.class,args); } }
7、创建预订商品模块Buyaction(9008)
-
在livegoods父工程下,创建预订商品模块livegoods-buyaction
-
修改livegoods-buyaction模块的POM文件,配置如下
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>livegoods</artifactId> <groupId>com.zzx</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>livegoods-buyaction</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--配置中心客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <!--默认加载bootstrap--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> <!--stream依赖(rabbit)--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency> </dependencies> </project>
-
在livegoods-buyaction模块的resource目录中创建bootstrap.yml文件,代码如下
server: port: 9008 spring: application: name: livegoods-buyaction profiles: active: itemCacheName,redis,rabbit cloud: config: uri: http://localhost:9010 label: master name: livegoods profile: dev logging: pattern: console: '%d{MM/dd HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
-
在livegoods-buyaction模块的com.livegoods.buyaction包下,创建主启动类BuyactionApplication,代码如下
package com.livegoods.buyaction; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class BuyactionApplication { public static void main(String[] args) { SpringApplication.run(BuyactionApplication.class,args); } }
-
在Eureka上面查看,是否注册成功。
8、创建预订商品消息消费模块Message
-
在livegoods父工程下,创建预订商品消息消费模块livegoods-buyaction-message-consumer
-
在livegoods-buyaction-message-consumer模块的POM文件中添加如下依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>livegoods</artifactId> <groupId>com.zzx</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>livegoods-buyaction-message-consumer</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <!--配置中心客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <!--默认加载bootstrap--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> </dependencies> </project>
-
在livegoods-buyaction-message-consumer模块的resources目录中,创建bootstrap.yml文件,配置如下
spring: application: name: livegoods-message-consumer profiles: active: itemCacheName,mongodb,redis cloud: config: uri: http://localhost:9010 label: master name: rabbit profile: dev stream: bindings: # 对应的真实的 RabbitMQ Exchange livegoodsMessenger-in-0: destination: livegoodsTopic livegoodsMessenger-out-0: destination: livegoodsTopic function: definition: livegoodsMessenger
-
在livegoods-buyaction-message-consumer模块的com.livegoods.buyaction.message包下,创建主启动类MessageConsumerApplication,代码如下
package com.livegoods.buyaction.message; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class MessageConsumerApplication { public static void main(String[] args) { SpringApplication.run(MessageConsumerApplication.class,args); } }
-
因为该模块是后台处理,跟客户无关,所以不需要注册到Eureka。
9、创建商品预订开始时间模块Buytime(9006)
-
在livegoods父工程下,创建商品预订开始时间模块livegoods-buytime
-
在livegoods-buytime模块的POM文件中添加如下依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>livegoods</artifactId> <groupId>com.zzx</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>livegoods-buytime</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--配置中心客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <!--默认加载bootstrap--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> </dependencies> </project>
-
在livegoods-buytime模块中的resources目录下,创建bootstrap.yml文件,配置如下
server: port: 9006 spring: application: name: livegoods-buytime profiles: active: mongodb cloud: config: uri: http://localhost:9010 label: master name: livegoods profile: dev
-
在livegoods-buytime模块中的com.livegoods.buytime包下,创建主启动类BuytimeApplication,代码如下
package com.livegoods.buytime; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class BuytimeApplication { public static void main(String[] args) { SpringApplication.run(BuytimeApplication.class,args); } }
-
查看是否注册到Eureka服务中。
10、创建Redis缓存服务器模块
-
在livegoods父工程下,创建Redis缓存服务器模块livegoods-cache-redis
-
在livegoods-cache-redis模块的POM文件中,添加如下依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>livegoods</artifactId> <groupId>com.zzx</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>livegoods-cache-redis</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies> </project>
因为它不需要面向客户,所以也不需要注册到Eureka服务中。
-
在livegoods-cache-redis模块的resources目录中,创建application-redis.yml文件,添加如下配置
spring: data: redis: host: 192.168.126.30 password: 123456 port: 6390
11、创建商品评价模块Comment(9005)
-
在livegoods父工程下,创建商品评价模块livegoods-comment
-
在livegoods-comment模块的POM文件中,添加如下依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>livegoods</artifactId> <groupId>com.zzx</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>livegoods-comment</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--配置中心客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <!--默认加载bootstrap--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> </dependencies> </project>
-
在livegoods-comment模块的resources目录下,创建bootstrap.yml文件,配置如下
server: port: 9005 spring: application: name: livegoods-comment profiles: active: mongodb cloud: config: uri: http://localhost:9010 label: master name: livegoods profile: dev
-
在livegoods-comment模块的com.livegoods.comment包下,创建主启动类CommentApplication,代码如下
package com.livegoods.comment; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class CommentApplication { public static void main(String[] args) { SpringApplication.run(CommentApplication.class,args); } }
启动查询评论模块,因为它是面向客户的服务,需要注册到Eureka中,以及需要获取配置中心Config的配置文件,所以需要导入Eureka和Config的客户端依赖 。
12、创建公共模块Commons
-
在livegoods父工程下,创建公共模块livegoods-commons
-
在livegoods-commons模块的POM文件中,添加如下依赖和指定Maven插件
<dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.9.0</version> <configuration> <target>17</target> <source>17</source> </configuration> </plugin> </plugins> </build>
-
在livegoods-commons模块的resources目录中,创建application-bannerNginx.yml文件,添加如下配置
livegoods: banner: nginx: prefix: http://192.168.126.30:8888/
即获取轮播图图片的地址
-
在livegoods-commons模块的resources目录中,创建application-itemCacheName.yml文件,添加如下配置
livegoods: cache: names: item: prefix: com:livegoods:details suffix: getDetails
即获取redis缓存的路径拼接的配置
13、创建商品详情模块Details(9004)
-
在livegoods父工程下,创建商品详情模块livegoods-details
-
在livegoods-details模块的POM文件中,添加如下依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>livegoods</artifactId> <groupId>com.zzx</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>livegoods-details</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--配置中心客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <!--默认加载bootstrap--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> <!--openFeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--断路器--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
-
在livegoods-details模块的resources目录下,创建bootstrap.yml文件,配置如下
server: port: 9004 spring: application: name: livegoods-details profiles: active: mongodb,bannerNginx,redis cloud: config: uri: http://localhost:9010 label: master name: livegoods profile: dev
-
在livegoods-details模块的com.livegoods.details包下,创建主启动类DetailsApplication文件,代码如下
package com.livegoods.details; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DetailsApplication { public static void main(String[] args) { SpringApplication.run(DetailsApplication.class,args); } }
14、创建网关模块Gateway(4006)
-
在livegoods父工程下,创建网关模块livegoods-gateway
-
在livegoods-gateway模块的POM文件中,添加如下依赖
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!--配置中心客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <!--默认加载bootstrap--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.9.0</version> <configuration> <target>17</target> <source>17</source> </configuration> </plugin> </plugins> </build>
-
在livegoods-gateway模块的bootstrap.yml文件中,添加如下配置
server:
port: 4006
spring:
application:
name: livegoods-gateway
cloud:
gateway:
discovery:
locator:
#不开启网关Gateway的服务注册和发现的功能
enabled: false
#请求路径上的服务名称转换为小写
lower-case-service-id: true
routes:
- id: banner
uri: lb://livegoods-banner
#断言如果断言为true则匹配该路由
predicates:
- Path=/banner
- id: hotproduct
uri: lb://livegoods-hot-product
predicates:
- Path=/hotProduct
# 请求中必须包含city请求参数。参数内容不限。
- Query=city
- id: recommendation
uri: lb://livegoods-recommendation
predicates:
- Path=/recommendation
# 请求中必须包含city请求参数。参数内容不限。
- Query=city
config:
uri: http://localhost:9010
label: master
name: livegoods
profile: dev
-
在livegoods-gateway模块的com.livegoods.gateway包下,创建主启动类GatewayApplication,代码如下
package com.livegoods.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class,args); } }
15、创建热销商品模块HotProduct(9001)
-
在livegoods父工程下,创建热销商品模块livegoods-hot-product
-
在livegoods-hot-product模块的POM文件中,添加如下依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--配置中心客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <!--默认加载bootstrap--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.9.0</version> <configuration> <target>17</target> <source>17</source> </configuration> </plugin> </plugins> </build>
-
在livegoods-hot-product模块的resources目录中,创建bootstrap.yml文件,添加如下配置
server: port: 9001 spring: application: name: livegoods-hot-product profiles: active: mongodb,bannerNginx cloud: config: uri: http://localhost:9010 label: master name: livegoods profile: dev
-
在livegoods-hot-product模块的com.livegoods.hotproduct包下,创建主启动类HotProductApplication,代码如下
package com.livegoods.hotproduct; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class HotProductApplication { public static void main(String[] args) { SpringApplication.run(HotProductApplication.class,args); } }
16、创建登录模块Login(9007)
-
在livegoods父工程下,创建登录模块livegoods-login
-
在livegoods-login模块的POM文件下,添加如下依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--Eureka客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--配置中心客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <!--默认加载bootstrap--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> </dependencies>
-
在livegoods-login模块的resources目录下,创建bootstrap.yml文件,配置如下
server: port: 9007 spring: application: name: livegoods-login profiles: active: redis,mongodb cloud: config: uri: http://localhost:9010 label: master name: livegoods profile: dev
-
在livegoods-login模块的com.livegoods.login包下,创建主启动类LoginApplication文件,代码如下
package com.livegoods.login; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class LoginApplication { public static void main(String[] args) { SpringApplication.run(LoginApplication.class,args); } }
17、创建MongoDB数据库模块
-
在livegoods父工程下,创建MongoDB数据库模块livegoods-mongodb-dao
-
在livegoods-mongodb-dao模块的POM文件中,添加如下依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>RELEASE</version> <scope>test</scope> </dependency> </dependencies>
-
在livegoods-mongodb-dao模块的resources目录下,创建application-mongodb.yml文件,配置如下
spring: data: mongodb: host: 192.168.126.30 port: 27017 username: root password: root database: livegoods authentication-database: admin
-
在livegoods-details模块的POM文件中,添加如下依赖
<dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-mongodb-dao</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-cache-redis</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
18、创建用户订单模块Order(9009)
-
在livegoods父工程下,创建用户订单模块livegoods-order
-
在livegoods-order模块的POM文件中,添加如下依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--配置中心客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <!--默认加载bootstrap--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-mongodb-dao</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
-
在livegoods-order模块的resources目录下创建bootstrap.yml文件,配置如下
server: port: 9009 spring: application: name: livegoods-order profiles: active: mongodb cloud: config: uri: http://localhost:9010 label: master name: livegoods profile: dev
-
在livegoods-order模块的com.livegoods.order包下创建主启动类OrderApplication ,代码如下
package com.livegoods.order; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class,args); } }
19、创建RabbitMQ模块
-
在livegoods父工程下,创建RabbitMQ模块livegoods-rabbit-publisher
-
在livegoods-rabbit-publisher模块的POM文件中,添加如下依赖
<dependencies> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
在后面操作RabbitMQ时,直接使用Stream-Rabbit的依赖即可。
-
在livegoods-rabbit-publisher模块的resources目录下创建application-rabbit.yml文件,配置如下
spring: rabbitmq: host: 192.168.126.30 username: admin password: admin
20、创建推荐商品模块Recommendation(9002)
-
在livegoods父工程下,创建推荐商品模块livegoods-recommendation
-
在livegoods-recommendation模块的POM文件中,添加如下依赖
<dependencies> <!--配置中心客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <!--默认加载bootstrap--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-mongodb-dao</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.9.0</version> <configuration> <target>17</target> <source>17</source> </configuration> </plugin> </plugins> </build>
-
在livegoods-recommendation模块的resources目录下创建bootstrap.yml文件,配置如下
server: port: 9002 spring: application: name: livegoods-recommendation profiles: active: bannerNginx,mongodb cloud: config: uri: http://localhost:9010 label: master name: livegoods profile: dev
-
在livegoods-recommendation模块的com.livegoods.recommendation包下,创建主启动类RecommendationApplication ,代码如下
package com.livegoods.recommendation; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class RecommendationApplication { public static void main(String[] args) { SpringApplication.run(RecommendationApplication.class,args); } }
21、创建商品搜索模块Search(9003)
-
在livegoods父工程下,创建商品搜索模块livegoods-search
-
在livegoods-search模块的POM文件中,添加如下依赖
<dependencies> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-mongodb-dao</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> </dependencies>
-
在livegoods-search模块的resources目录下创建application.yml文件,配置如下
server: port: 9003 livegoods: search: # 配置是否需要初始化索引,创建和设置映射。默认为false init: enabled: true spring: profiles: active: mongodb,bannerNginx application: name: livegoods-search elasticsearch: uris: http://192.168.126.30:9200 eureka: client: service-url: defaultZone: http://192.168.126.30:8761/eureka/ instance: prefer-ip-address: true
-
在livegoods-search模块的com.livegoods.search包下,创建主启动类SearchApplication,代码如下
package com.livegoods.search; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SearchApplication { public static void main(String[] args) { SpringApplication.run(SearchApplication.class,args); } }
启动后,在Eureka注册的服务有这些
四、基础功能构建
1、公共类Commons
-
在livegoods-commons模块的com.livegoods.commons.pojo包下,创建轮播图实体类Banner
package com.livegoods.commons.pojo; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; import java.util.Date; /** * 轮播图实体 */ @Data @NoArgsConstructor @EqualsAndHashCode @ToString public class Banner { // 主键 private String id; // 图片路径 private String url; // 创建时间 private Date createTime; }
-
在livegoods-commons模块的com.livegoods.commons.pojo包下,创建商品评价实体类Comment
package com.livegoods.commons.pojo; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; /** * 商品评价实体 */ @Data @NoArgsConstructor @EqualsAndHashCode @ToString public class Comment { // 商品主键 private String itemId; // 评价的用户名或手机号 private String username; // 评价信息 private String comment; // 评分 private int star; }
-
在livegoods-commons模块的com.livegoods.commons.pojo包下,创建商品实体类Item
package com.livegoods.commons.pojo; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; import java.util.Date; import java.util.List; /** * 商品实体 */ @Data @NoArgsConstructor @EqualsAndHashCode @ToString public class Item { // 主键 private String id; // 商品标题 private String title; // 销量 private Long sales; // 是否为推荐商品 private Boolean recommendation; // 商品价格 private Long price; // 所属城市 private String city; // 租赁方式 整租,合租 private String rentType; // 房屋类型 private String houseType; // 商品图片 private List<String> imgs; // 可预订时间 private Date buytime; // 是否已出租 private Boolean isRented; /** * 房屋特性, Map集合。集合存储数据内容为: years: "建造年份",type: "房屋类型,几室几厅", * level: "所在楼层",style: "装修标准", orientation: "房屋朝向" */ private Map<String, String> info; //热门排序|权重 private Byte recoSort; private String img; public String getImg(){ return imgs.get(0); } private String link; public String getLink(){ return "/details/"+id; } private String houseType4Search; public String getHouseType4Search(){ // 楼层 | 几室几厅 - 面积 return info.get("level")+" | "+info.get("type")+" - "+houseType; } }
-
在livegoods-commons模块的com.livegoods.commons.pojo包下,创建登录日志实体类LoginLog
package com.livegoods.commons.pojo; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; import java.util.Date; /** * 登录日志实体 * 记录一个用户登录的日志数据。 * 当前系统是一个无注册逻辑的系统,用户只要提供有效的手机号,即可通过验证码登录。 */ @Data @NoArgsConstructor @EqualsAndHashCode @ToString public class LoginLog { // 日志id private String id; // 登录的用户名 private String username; // 登录方式 private String type; // 登录时间 private Date loginTime; // 是否登录成功 private Boolean isSuccess; // 日志消息 private String message; }
-
在livegoods-commons模块的com.livegoods.commons.pojo包下,创建订单实体类Order
package com.livegoods.commons.pojo; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; /** * 订单实体 */ @Data @NoArgsConstructor @EqualsAndHashCode @ToString public class Order { // 订单id private String id; // 用户名或手机号 private String username; // 商品主键 private String itemId; // 商品标题 private String title; // 房屋类型 private String houseType; // 价格 private Long price; // 图片 private String img; // 租赁方式 private String rentType; // 评论状态 0:未评论 1:已评论 private Integer commentState; }
-
在livegoods-commons模块的com.livegoods.commons.pojo包下,创建验证码实体类ValidateCode
package com.livegoods.commons.pojo; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; /** * 验证码实体 */ @Data @NoArgsConstructor @EqualsAndHashCode @ToString public class ValidateCode { // 手机号 private String phone; // 验证码 private String ValidateCode; }
-
在livegoods-commons模块的com.livegoods.commons.vo包下,创建返回结果封装类LivegoodsResult
package com.livegoods.commons.vo; import com.livegoods.commons.pojo.Item; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; import java.util.List; /** * 返回结果封装类 */ @Data @NoArgsConstructor @EqualsAndHashCode @ToString public class LivegoodsResult { // 状态码 200: 成功 , 500: 失败 private Integer status; // 返回的结果 private Object results; // 返回的消息 private String message; // 返回的数据 private Object data; // 分页返回结果,是否还有更多数据 private Boolean hasMore; // 预订时间 private Long Time; public static LivegoodsResult ok(){ LivegoodsResult livegoodsResult = new LivegoodsResult(); livegoodsResult.setStatus(200); return livegoodsResult; } public static LivegoodsResult ok(Object data){ LivegoodsResult livegoodsResult = new LivegoodsResult(); livegoodsResult.setStatus(200); livegoodsResult.setData(data); return livegoodsResult; } public static LivegoodsResult error(){ LivegoodsResult livegoodsResult = new LivegoodsResult(); livegoodsResult.setStatus(500); return livegoodsResult; } public static LivegoodsResult error(String message){ LivegoodsResult livegoodsResult = new LivegoodsResult(); livegoodsResult.setMessage(message); livegoodsResult.setStatus(500); return livegoodsResult; } }
2、Redis配置类
-
在livegoods-cache-redis模块的com.livegoods.redis.config包下,创建Redis模板配置类RedisCacheConfiguration,代码如下
package com.livegoods.redis.config; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * 定义一个父类,用于提供模板配置 */ public abstract class RedisCacheConfiguration { // protected: 同一个类中 同一个包中 不同包的子类 都可以访问,但是不同包的无关类不能访问 protected RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){ RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return redisTemplate; } }
五、模块开发
1、Banner轮播图模块开发
-
在livegoods-banner模块的POM文件中引入commons和mongodb依赖
<!-- 引入commons依赖 --> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- 引入mongodb依赖 --> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-mongodb-dao</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
-
在livegoods-banner模块的com.livegoods.banner.dao包下,创建Dao层接口BannerDao
package com.livegoods.banner.dao; import com.livegoods.commons.pojo.Banner; import org.springframework.data.mongodb.core.query.Query; import java.util.List; // Banner轮播图的数据访问对象,实现数据查询 public interface BannerDao { List<Banner> selectBanners(Query query); }
-
在livegoods-banner模块的com.livegoods.banner.dao.impl包下,创建Dao层实现类BannerDaoImpl
package com.livegoods.banner.dao.impl; import com.livegoods.banner.dao.BannerDao; import com.livegoods.commons.pojo.Banner; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Repository; import java.util.List; /** * Banner数据访问对象 * 连接MongoDB,访问banner集合。实现数据的查询 */ @Repository public class BannerDaoImpl implements BannerDao { @Autowired private MongoTemplate mongoTemplate; @Override public List<Banner> selectBanners(Query query) { List<Banner> banners = mongoTemplate.find(query, Banner.class); return banners; } }
-
在livegoods-banner模块的com.livegoods.banner.service包下,创建Service层接口BannerService
package com.livegoods.banner.service; import com.livegoods.commons.vo.LivegoodsResult; // 轮播图服务接口 public interface BannerService { // 轮播图查询 LivegoodsResult getBanners(); }
-
在livegoods-banner模块的com.livegoods.banner.service.impl包下,创建Service层的实现类BannerServiceImpl
package com.livegoods.banner.service.impl; import com.livegoods.banner.dao.BannerDao; import com.livegoods.banner.service.BannerService; import com.livegoods.commons.pojo.Banner; import com.livegoods.commons.vo.LivegoodsResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.PageRequest; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; /** * Banner轮播图服务实现类 */ @Service public class BannerServiceImpl implements BannerService { @Autowired private BannerDao bannerDao; //从配置文件中获取FastDFS服务的ip+port @Value("${livegoods.banner.nginx.prefix}") private String nginxPrefix; @Override public LivegoodsResult getBanners() { LivegoodsResult result = new LivegoodsResult(); try { Query query = new Query(); query.with(PageRequest.of(0,4)); List<Banner> banners = bannerDao.selectBanners(query); result.setStatus(200); ArrayList<String> imgList = new ArrayList<>(); for (Banner banner : banners) { //将FastDFS服务器的ip+port与图片的保存路径进行拼接 imgList.add(nginxPrefix+banner.getUrl()); } result.setResults(imgList); }catch (Exception e){ e.printStackTrace(); result.setStatus(500); result.setMessage("轮播图查询失败"); } return result; } }
-
在livegoods-banner模块的com.livegoods.banner.controller包下,创建Controller层类BannerController
package com.livegoods.banner.controller; import com.livegoods.banner.service.BannerService; import com.livegoods.commons.vo.LivegoodsResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class BannerController { @Autowired private BannerService bannerService; /** * 查询轮播图 * @return */ @RequestMapping("banner") public LivegoodsResult banner(){ return bannerService.getBanners(); } }
-
重启banner模块服务后,通过浏览器访问获取轮播图的数据:
http://localhost:9000/banner
2、HotProduct热销商品模块开发
-
在livegoods-hot-product模块的POM文件中引入commons和mongodb依赖
<!-- 引入commons依赖 --> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- 引入mongodb依赖 --> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-mongodb-dao</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
-
在livegoods-hot-product模块的com.livegoods.hotproduct.dao包下,创建热销商品数据访问接口ItemDao
package com.livegoods.hotproduct.dao; import com.livegoods.commons.pojo.Item; import org.springframework.data.mongodb.core.query.Query; import java.util.List; // 热销商品数据访问接口 public interface ItemDao { // 查询热销商品的数据访问方法,根据销量排序,查询条件为城市。 查询只要4条数据。 List<Item> getHotProduct(Query query); }
-
在livegoods-hot-product模块的com.livegoods.hotproduct.dao.impl包下,创建热销商品数据访问接口的实现类ItemDaoImpl
package com.livegoods.hotproduct.dao.impl; import com.livegoods.commons.pojo.Item; import com.livegoods.hotproduct.dao.ItemDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Repository; import java.util.List; /** * 热销商品数据访问接口的实现类 */ @Repository public class ItemDaoImpl implements ItemDao { @Autowired private MongoTemplate mongoTemplate; @Override public List<Item> getHotProduct(Query query) { List<Item> items = mongoTemplate.find(query, Item.class); return items; } }
-
在livegoods-hot-product模块的com.livegoods.hotproduct.service包下,创建热销商品服务接口HotProductService
package com.livegoods.hotproduct.service; import com.livegoods.commons.vo.LivegoodsResult; // 热销商品服务接口 public interface HotProductService { /** * 查询热销商品方法。 * 查询的返回结果,热销商品的数量必须是4。 * 查询条件所在城市的热销商品数量大于4的时候,只查 询销量排序的前4位商品。 * 如果条件所在城市的热销商品数量小于4的时候,从其 他的城市热销商品中查询销量排序靠前的补足。 * @param city 城市 * @return */ LivegoodsResult getHotProduct(String city); }
-
在livegoods-hot-product模块的com.livegoods.hotproduct.service.impl包下,创建热销商品服务接口的实现类HotProductServiceImpl
package com.livegoods.hotproduct.service.impl; import com.livegoods.commons.pojo.Item; import com.livegoods.commons.vo.LivegoodsResult; import com.livegoods.hotproduct.dao.ItemDao; import com.livegoods.hotproduct.service.HotProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Service; import java.util.*; /** * 热销商品服务的实现类 */ @Service public class HotProductServiceImpl implements HotProductService { @Autowired private ItemDao itemDao; //从配置文件中获取FastDFS服务的ip+port @Value("${livegoods.banner.nginx.prefix}") private String nginxPrefix; @Override public LivegoodsResult getHotProduct(String city) { LivegoodsResult result = new LivegoodsResult(); Query query = new Query(); query.addCriteria(Criteria.where("city").is(city)); // 排序与分页 query.with(PageRequest.of(0,4, Sort.by(Sort.Direction.DESC, "sales"))); List<Item> hotProduct = itemDao.getHotProduct(query); if(hotProduct.size() < 4){ // 查询的热销商品数量不足,需要查询其他城市的热销商品,填充到当前查询结果 Query otherQuery = new Query(); // 查询条件, 查询当前城市以外的其他城市热销商品,避免重复数据 otherQuery.addCriteria(Criteria.where("city").ne(city)); // 排序和分页 otherQuery.with(PageRequest.of(0, 4 - hotProduct.size(), Sort.by(Sort.Direction.DESC, "sales"))); List<Item> otherItems = itemDao.getHotProduct(otherQuery); // 将其他城市的热销商品数据,填充到当前城市的热销商品数据集合中。补足4条数据 hotProduct.addAll(otherItems); } // 查询结果items,理论上一定有4条数据。如果不足?可以使用托底数据填充 if(hotProduct.size() < 4){ // 如果所有的热销商品数据总计不足4条,使用托底数据填充 for(int i = hotProduct.size(); i < 4; i++){ hotProduct.add(fallbackItem()); } } // 将图片路径,从相对路径转换为绝对路径。增加Nginx地址前缀 hotProduct = this.changeImgsUrl(hotProduct); return LivegoodsResult.ok(hotProduct); } // 将集合中的每个Item类型对象的图片地址,增加前缀 private List<Item> changeImgsUrl(List<Item> items){ for(Item item : items){ List<String> newImgs = new ArrayList<>(); for(String img : item.getImgs()){ newImgs.add(nginxPrefix + img); } item.setImgs(newImgs); } return items; } /** * 数据不足的兜底方法 */ private Item fallbackItem(){ Item item = new Item(); item.setId("5ec1ec6b7f56a946fb7fdffa"); item.setCity("北京"); item.setHouseType("150㎡"); item.setImgs(Arrays.asList( "group1/M00/00/00/wKhHmRokO2AF6HCAAG4U6ny2vQ395.jpg", "group1/M00/00/00/wKh-HmRokPeASU0aAAEefOtgMqQ209.jpg", "group1/M00/00/00/wKh-HmRokQOAczvbAAHiL9U5wu0315.jpg")); item.setPrice(12000L); item.setRecommendation(true); item.setRecoSort((byte)9); item.setRentType("整租"); item.setSales(100L); item.setTitle("北京高档公寓"); Map<String, String> info = new HashMap<>(); info.put("years", "2010"); info.put("type", "3室2厅"); info.put("level", "10/18层"); info.put("style", "精装修"); info.put("orientation", "南北通透"); item.setInfo(info); return item; } }
-
在livegoods-hot-product模块的com.livegoods.hotproduct.controller包下,创建热销商品控制层类HotProductController
package com.livegoods.hotproduct.controller; import com.livegoods.commons.vo.LivegoodsResult; import com.livegoods.hotproduct.service.HotProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HotProductController{ @Autowired private HotProductService hotProductService; @GetMapping("hotProduct") public LivegoodsResult getHotProduct(String city){ LivegoodsResult hotProduct = hotProductService.getHotProduct(city); return hotProduct; } }
-
在虚拟机中上传图片11张图片。
1)将图片上传到FastDFS中的/usr/local/images/目录下,再将/usr/local/images文件夹拷贝到容器中:docker cp /usr/local/images fastdfs:/usr/local
2)进入到FastDFS容器中:docker exec -it fastdfs bash
3)将项目图片一个一个上传FastDFS服务中:/usr/bin/fdfs_upload_file /etc/fdfs/client.conf /usr/local/images/1.jpg
,最后需要把返回的图片路径保存起来,在数据库中导入。group1/M00/00/00/wKh-HmRokHSAOCETAAHhnl0byuU028.jpg group1/M00/00/00/wKh-HmRokJeASmwLAAHi9pWaKhE469.jpg group1/M00/00/00/wKh-HmRokKKALWU-AAKGJ3D_v1k273.jpg group1/M00/00/00/wKh-HmRokK6AY0HzAAJI_3pe_30643.jpg group1/M00/00/00/wKh-HmRokLmAG8tWAAJ6zxOohkc878.jpg group1/M00/00/00/wKh-HmRokMWADqQpAAI1pI2ohVU135.jpg group1/M00/00/00/wKh-HmRokNSAdqYmAAHRIjaxOGM501.jpg group1/M00/00/00/wKh-HmRokOCAFEchAAIJt8EuM8M592.jpg group1/M00/00/00/wKh-HmRokO2AF6HCAAG4U6ny2vQ395.jpg group1/M00/00/00/wKh-HmRokPeASU0aAAEefOtgMqQ209.jpg group1/M00/00/00/wKh-HmRokQOAczvbAAHiL9U5wu0315.jpg
-
在NoSQLBooster可视化工具中,对livegoods数据库右键选择Run SQL Query,将下面的数据导入进去,点击RUN执行即可。
use livegoods db.item.insertMany([{ "title": "北京老小区", "sales": 300, "recommendation": true, "recoSort": 9, "city": "北京", "price": 3000, "rentType": " 整 租 ", "houseType": "60 ㎡ ", "info": { "orientation": "朝南", "level": "6/6 层", "style": "简单装修", "type": "2 室 1 厅", "years": "2000" }, "imgs": [ "group1/M00/00/00/wKh-HmRokHSAOCETAAHhnl0byuU028.jpg", "group1/M00/00/00/wKh-HmRokJeASmwLAAHi9pWaKhE469.jpg", "group1/M00/00/00/wKh-HmRokKKALWU-AAKGJ3D_v1k273.jpg" ], "_class": "com.bjsxt.livegoods.pojo.Item", "buytime": new Date(), "isRented": false }, { "title": "北京独栋别墅", "sales": 10, "recommendation": true, "recoSort": 6, "city": "北京", "price": 30000, "rentType": " 整 租 ", "houseType": "310 ㎡ ", "info": { "orientation": "四面环海", "level": "3/3 层", "style": "豪华装修", "type": "6 室 4 厅", "years": "2013" }, "imgs": [ "group1/M00/00/00/wKh-HmRokK6AY0HzAAJI_3pe_30643.jpg", "group1/M00/00/00/wKh-HmRokLmAG8tWAAJ6zxOohkc878.jpg" ], "_class": "com.bjsxt.livegoods.pojo.Item", "buytime": new Date(), "isRented": true }, { "title": "北京联排别墅", "sales": 30, "recommendation": true, "recoSort": 12, "city": "北京", "price": 21000, "rentType": "整租", "houseType": "230 ㎡", "info": { "orientation": "南北通透", "level": "2/2 层", "style": "精装修", "type": "5 室 3 厅", "years": "2007" }, "imgs": [ "group1/M00/00/00/wKh-HmRokMWADqQpAAI1pI2ohVU135.jpg", "group1/M00/00/00/wKh-HmRokNSAdqYmAAHRIjaxOGM501.jpg", "group1/M00/00/00/wKh-HmRokOCAFEchAAIJt8EuM8M592.jpg" ], "_class": "com.bjsxt.livegoods.pojo.Item", "buytime": new Date(), "isRented": true }, { "title": "北京普通公寓", "sales": 100, "recommendation": true, "recoSort": 9, "city": "北京", "price": 12000, "rentType": "整租", "houseType": "150 ㎡", "info": { "orientation": "南北通透", "level": "10/18 层", "style": "精装修", "type": "3 室 2 厅", "years": "2010" }, "imgs": [ "group1/M00/00/00/wKh-HmRokO2AF6HCAAG4U6ny2vQ395.jpg", "group1/M00/00/00/wKh-HmRokPeASU0aAAEefOtgMqQ209.jpg", "group1/M00/00/00/wKh-HmRokQOAczvbAAHiL9U5wu0315.jpg" ], "_class": "com.bjsxt.livegoods.pojo.Item", "buytime": new Date(), "isRented": true } ])
-
重启HotProduct模块后,在浏览器访问:
http://localhost:9001/hotproduct?city=北京
3、Recommendation推荐商品模块开发
-
在livegoods-recommendation模块的com.livegoods.recommendation.dao包下,创建dao层接口ItemDao
package com.livegoods.recommendation.dao; import com.livegoods.commons.pojo.Item; import org.springframework.data.mongodb.core.query.Query; import java.util.List; //商品数据访问接口 public interface ItemDao { //查询热门推荐商品 List<Item> selectRecommendation(Query query); }
-
在livegoods-recommendation模块的com.livegoods.recommendation.dao.impl包下,创建dao层接口的实现类ItemDaoImpl
package com.livegoods.recommendation.dao.impl; import com.livegoods.commons.pojo.Item; import com.livegoods.recommendation.dao.ItemDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Repository; import java.util.List; /** * 热门推荐商品数据访问的实现类 */ @Repository public class ItemDaoImpl implements ItemDao { @Autowired private MongoTemplate mongoTemplate; /** * 查询商品 * @param query * @return */ @Override public List<Item> selectRecommendation(Query query) { List<Item> items = mongoTemplate.find(query, Item.class); return items; } }
-
在livegoods-recommendation模块的com.livegoods.recommendation.service包下,创建service层接口RecommendationService
package com.livegoods.recommendation.service; import com.livegoods.commons.vo.LivegoodsResult; // 热门推荐服务接口 public interface RecommendationService { /** * 查询热门推荐商品信息,查询条件是所在城市 * @param city 城市 * @return */ LivegoodsResult getRecommendation(String city); }
-
在livegoods-recommendation模块的com.livegoods.recommendation.service.impl包下,创建service层接口的实现类RecommendationServiceImpl
package com.livegoods.recommendation.service.impl; import com.livegoods.commons.pojo.Item; import com.livegoods.commons.vo.LivegoodsResult; import com.livegoods.recommendation.dao.ItemDao; import com.livegoods.recommendation.service.RecommendationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.PageRequest; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Service; import java.util.*; /** * 热门推荐服务实现 */ @Service public class RecommendationServiceImpl implements RecommendationService { @Autowired private ItemDao itemDao; //从配置文件中获取FastDFS服务的ip+port @Value("${livegoods.banner.nginx.prefix}") private String nginxPrefix; /** * 查询条件是:城市 = 方法参数 and 是否 推荐 = true * @param city 城市 * @return */ @Override public LivegoodsResult getRecommendation(String city) { // 查询条件 Query query = new Query(); Criteria criteria = new Criteria(); criteria.andOperator(Criteria.where("city").is(city),Criteria.where("recommendation").is(true)); query.addCriteria(criteria); // 分页 query.with(PageRequest.of(0,4)); List<Item> items = itemDao.selectRecommendation(query); // 判断数量是否达标 if(items.size()<4){ Query query1 = new Query(); Criteria criteria1 = new Criteria(); criteria1.andOperator(Criteria.where("city").ne(city),Criteria.where("recommendation").is(true)); query1.addCriteria(criteria1); // 分页 query1.with(PageRequest.of(0,4-items.size())); List<Item> items1 = itemDao.selectRecommendation(query1); items.addAll(items1); } if(items.size()<4){ for (int i = items.size(); i < 4; i++) { items.add(fallbackItem()); } } // 把图片URL地址,转换成完整路径。 items = this.changeImgs(items); return LivegoodsResult.ok(items); } private List<Item> changeImgs(List<Item> items) { for(Item item : items){ List<String> newImgs = new ArrayList<>(); for(String img : item.getImgs()){ newImgs.add(nginxPrefix + img); } item.setImgs(newImgs); } return items; } /** * 数据不足的兜底方法 */ private Item fallbackItem(){ Item item = new Item(); item.setId("5ec1ec6b7f56a946fb7fdffa"); item.setCity("北京"); item.setHouseType("150㎡"); item.setImgs(Arrays.asList( "group1/M00/00/00/wKhHmRokO2AF6HCAAG4U6ny2vQ395.jpg", "group1/M00/00/00/wKh-HmRokPeASU0aAAEefOtgMqQ209.jpg", "group1/M00/00/00/wKh-HmRokQOAczvbAAHiL9U5wu0315.jpg")); item.setPrice(12000L); item.setRecommendation(true); item.setRecoSort((byte)9); item.setRentType("整租"); item.setSales(100L); item.setTitle("北京高档公寓"); Map<String, String> info = new HashMap<>(); info.put("years", "2010"); info.put("type", "3室2厅"); info.put("level", "10/18层"); info.put("style", "精装修"); info.put("orientation", "南北通透"); item.setInfo(info); return item; } }
-
在livegoods-recommendation模块的com.livegoods.recommendation.controller包下,创建controller层类RecommendationController
package com.livegoods.recommendation.controller; import com.livegoods.commons.vo.LivegoodsResult; import com.livegoods.recommendation.service.RecommendationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class RecommendationController { @Autowired private RecommendationService recommendationService; @RequestMapping("recommendation") public LivegoodsResult getRecommendation(String city){ LivegoodsResult recommendation = recommendationService.getRecommendation(city); return recommendation; } }
-
测试
1)在虚拟机启动MongoDB、Eureka的服务
2)然后启动项目的condfig9010配置中心服务,还有recommendation9002推荐商品服务。
3)在浏览器访问:http://localhost:9002/recommendation?city=北京
-
整合测试
1)在虚拟机启动MongoDB、Eureka的服务
2)然后启动项目的condfig9010配置中心服务,hotProduct9001热销商品服务,banener9000轮播图服务,recommendation9002推荐商品服务以及gateway4006网关服务。
3)最后启动Vue前端项目,在VSCODE的终端,进入到项目目录下启动:npm run serve
4)在浏览器中访问:http://localhost/
4、Details商品详情模块开发
-
在livegoods-details模块的com.livegoods.details.dao包下,创建商品详情数据访问层接口ItemDao
package com.livegoods.details.dao; import com.livegoods.commons.pojo.Item; // 查询商品详情数据访问接口 public interface ItemDao { Item findItemById(String id); }
-
在livegoods-details模块的com.livegoods.details.dao.impl包下,创建商品详情数据访问层实现类ItemDaoImpl
package com.livegoods.details.dao.impl; import com.livegoods.commons.pojo.Item; import com.livegoods.details.dao.ItemDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Repository; // 查询商品详情数据访问实现 @Repository public class ItemDaoImpl implements ItemDao { @Autowired private MongoTemplate mongoTemplate; /** * 根据id查询商品 * @param id * @return */ @Override public Item findItemById(String id) { return mongoTemplate.findById(id,Item.class); } }
-
在livegoods-details模块的com.livegoods.details.service包下,创建商品详情服务访问层接口DetailsService
package com.livegoods.details.service; import com.livegoods.commons.pojo.Item; // 商品详情服务接口 public interface DetailsService { // 根据id查询商品 Item getDetails(String id); }
-
在livegoods-details模块的com.livegoods.details.service.impl包下,创建商品详情服务访问层实现类DetailsServiceImpl
package com.livegoods.details.service.impl; import com.livegoods.commons.pojo.Item; import com.livegoods.details.dao.ItemDao; import com.livegoods.details.service.DetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Service; import java.util.ArrayList; // 商品详情服务实现 @Service public class DetailsServiceImpl implements DetailsService { @Autowired private ItemDao itemDao; //从配置文件中获取FastDFS服务的ip+port @Value("${livegoods.banner.nginx.prefix}") private String nginxPrefix; /** * 根据id查询商品 * 需要将商品中的图片地址,从相对路径修改为绝对路径 * @param id * @return */ @Override public Item getDetails(String id) { // 根据id查询商品 Item item = itemDao.findItemById(id); // 把图片的相对路径改为绝对路径 ArrayList<String> imgs = new ArrayList<>(); for (String img : item.getImgs()) { imgs.add(nginxPrefix+img); } //将图片的绝对路径设置到item中 item.setImgs(imgs); return item; } }
-
在livegoods-details模块的com.livegoods.details.controller包下,创建商品详情控制层类DetailsController
package com.livegoods.details.controller; import com.livegoods.commons.pojo.Item; import com.livegoods.details.service.DetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class DetailsController { @Autowired private DetailsService detailsService; @RequestMapping("/details") public Item getDetails(String id){ return detailsService.getDetails(id); } }
-
在livegoods-gateway模块的bootstrap.yml配置文件中,添加商品详情details路由
- id: details uri: lb://livegoods-details predicates: - Path=/details # 请求中必须包含city请求参数。参数内容不限。 - Query=id
-
测试
1)在虚拟机启动MongoDB、Eureka的服务
2)然后启动项目的condfig9010配置中心服务,gateway4006网关服务,还有details9004商品详情服务。
3)在浏览器访问:http://localhost
点击商品的图片(非轮播图),即可进入商品详情页,然后点击房屋信息
5、Details商品详情模块添加缓存
-
在livegoods-cache-redis模块的com.livegoods.redis.config包下的RedisCacheConfiguration中,添加cachaManager方法,即添加Redis的缓存管理器
protected CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory){ org.springframework.data.redis.cache.RedisCacheConfiguration configuration = org.springframework.data.redis.cache.RedisCacheConfiguration.defaultCacheConfig(); // 默认超时时间 configuration = configuration.entryTtl(Duration.ofMinutes(30L)) // 不缓存空数据 .disableCachingNullValues() // key的序列化器 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) // value的序列化器 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory)).cacheDefaults(configuration).build(); }
-
在livegoods-details模块的com.livegoods.details.config包下,添加配置类DetailsConfiguration
package com.livegoods.details.config; import com.livegoods.redis.config.RedisCacheConfiguration; import org.springframework.cache.CacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; // 商品详情配置类型 @Configuration public class DetailsConfiguration extends RedisCacheConfiguration { /** * 创建缓存管理器,即直接使用刚刚配置的CacheManager * @param redisConnectionFactory Redis连接工厂 * @return */ @Bean public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory){ return super.cacheManager(redisConnectionFactory); } }
-
在livegoods-recommendation模块的com.livegoods.recommendation.service.impl包下的RecommendationServiceImpl类中的getDetails方法上方添加@Cacheable注解
@Cacheable(cacheNames = "com:livegoods:details",key="'getDetails('+#id+')'")
-
在livegoods-recommendation模块的com.livegoods.details包下的DetailsApplication主启动类上,添加开启缓存的注解
@EnableCaching
-
测试
1)在虚拟机启动MongoDB、Eureka和Redis的服务
2)然后启动项目的condfig9010配置中心服务,gateway4006网关服务,还有重启details9004商品详情服务。
3)在浏览器访问:http://localhost
点击商品的图片(非轮播图),即可进入商品详情页
4)在redis容器中,查看所有缓存
6、Comment商品评论模块开发
-
在livegoods-comment模块的POM文件中引入commons和mongodb依赖
<!-- 引入MongoDB模块依赖 --> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-mongodb-dao</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- 引入公共模块commons依赖 --> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
-
在livegoods-comment模块的com.livegoods.comment.dao包下,创建订单数据层接口OrderDao
package com.livegoods.comment.dao; // 订单数据访问接口 public interface OrderDao { void updateCommentState(String orderId,int commentState); }
-
在livegoods-comment模块的com.livegoods.comment.dao.impl包下,创建订单数据层的实现类OrderDaoImpl
package com.livegoods.comment.dao.impl; import com.livegoods.comment.dao.OrderDao; import com.livegoods.commons.pojo.Order; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Repository; // 订单数据访问层的实现类 @Repository public class OrderDaoImpl implements OrderDao { @Autowired private MongoTemplate mongoTemplate; /** * 更新订单评论状态 * @param orderId * @param commentState */ @Override public void updateCommentState(String orderId, int commentState) { Query query = new Query(); query.addCriteria(Criteria.where("_id").is(orderId)); Update update = Update.update("commentState", commentState); mongoTemplate.updateFirst(query, update, Order.class); } }
-
在livegoods-comment模块的com.livegoods.comment.dao包下,创建商品评论数据层接口CommentDao
package com.livegoods.comment.dao; import com.livegoods.commons.pojo.Comment; //商品评论数据访问接口 public interface CommentDao { //新增评论 void save(Comment comment); }
-
在livegoods-comment模块的com.livegoods.comment.dao.impl包下,创建商品评论数据层的实现类CommentDaoImpl
package com.livegoods.comment.dao.impl; import com.livegoods.comment.dao.CommentDao; import com.livegoods.commons.pojo.Comment; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.stereotype.Repository; // 商品评论数据层的实现类 @Repository public class CommentDaoImpl implements CommentDao { @Autowired private MongoTemplate mongoTemplate; /** * 新增商品评论 * @param comment */ @Override public void save(Comment comment) { mongoTemplate.save(comment); } }
-
在livegoods-comment模块的com.livegoods.comment.service包下,创建商品评论服务层接口CommentService
package com.livegoods.comment.service; import com.livegoods.commons.vo.LivegoodsResult; // 商品评论服务接口 public interface CommentService { LivegoodsResult feelback(String orderId,String comment); }
-
在livegoods-comment模块的com.livegoods.comment.service包下,创建商品评论服务层接口CommentService
package com.livegoods.comment.service; import com.livegoods.commons.vo.LivegoodsResult; // 商品评论服务接口 public interface CommentService { LivegoodsResult feelback(String orderId,String comment); }
-
在livegoods-comment模块的com.livegoods.comment.service.impl包下,创建商品评论服务层实现类CommentServiceImpl
package com.livegoods.comment.service.impl; import com.livegoods.comment.dao.CommentDao; import com.livegoods.comment.dao.OrderDao; import com.livegoods.comment.service.CommentService; import com.livegoods.commons.pojo.Comment; import com.livegoods.commons.pojo.Order; import com.livegoods.commons.vo.LivegoodsResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; // 商品评论服务层的实现类 @Service public class CommentServiceImpl implements CommentService { @Autowired private CommentDao commentDao; @Autowired private OrderDao orderDao; /** * 新增商品评论 * @param orderId 订单主键 * @param comment 评论内容 * @return */ @Override public LivegoodsResult feelback(String orderId, String comment) { try{ // 根据订单id查询订单数据 // 未开发Order订单模块,先进行注释 // Order order = orderDao.findById(orderId); // 未开发Order订单模块,先使用模拟数据进行测试,后面删除 Order order = new Order(); order.setUsername("13719244228"); order.setItemId("6468bbeb0334ebcb2e78a51a"); // 创建评论对象 Comment commentObject = new Comment(); commentObject.setUsername(order.getUsername()); commentObject.setComment(comment); commentObject.setItemId(order.getItemId()); commentObject.setStar(3); // 添加评论 commentDao.save(commentObject); //更新订单里面的评论状态 设置为已评论 orderDao.updateCommentState(orderId,1); return LivegoodsResult.ok(); }catch (Exception e){ e.printStackTrace(); return LivegoodsResult.error(); } } }
-
在livegoods-comment模块的com.livegoods.comment.controller包下,创建商品评论控制层类CommentController
package com.livegoods.comment.controller; import com.livegoods.comment.service.CommentService; import com.livegoods.commons.vo.LivegoodsResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; // 商品评论控制层 @RestController public class CommentController { @Autowired private CommentService commentService; /** * 新增评论 * @param orderId * @param comment * @return */ @PostMapping("/feelback") public LivegoodsResult feelback(String orderId,String comment){ return commentService.feelback(orderId,comment); } }
-
在NoSQLBooster工具的livegoods数据库上创建集合comment
-
测试
1)在虚拟机启动MongoDB、Eureka服务
2)然后启动项目的condfig9010配置中心服务,还有重启comment9005商品评论服务。
3)在Postman测试工具上面测试:http://localhost:9005/feelback?orderId=11111&comment=112222333
,测试6次(添加6条评论),用来查询商品评论时进行测试
4)在NoSQLBooster工具查看评论是否添加成功
7、Comment查询商品评论模块开发
-
在livegoods-comment模块的com.livegoods.comment.dao包下的接口CommentDao下,添加分页查询评论方法
// 分页查询评论数据 List<Comment> findCommentsByPage(Query query);
-
在livegoods-comment模块的com.livegoods.comment.dao.impl包下的类CommentDaoImpl下,添加分页查询评论方法
// 分页查询商品评论 @Override public List<Comment> findCommentsByPage(Query query) { return mongoTemplate.find(query, Comment.class); }
-
在livegoods-comment模块的com.livegoods.comment.service包下的接口CommentService下,添加分页查询评论方法
//分页查询商品的评论 LivegoodsResult findCommentsByItemId(String itemId,int page,int rows);
-
在livegoods-comment模块的com.livegoods.comment.service.impl包下的类CommentServiceImpl下,添加分页查询评论方法
/** * 分页查询商品评论 * @param itemId * @param page * @param rows * @return */ @Override public LivegoodsResult findCommentsByItemId(String itemId, int page, int rows) { Query query = new Query(); query.addCriteria(Criteria.where("itemId").is(itemId)); query.with(PageRequest.of(page*rows,rows)); // 分页查询 List<Comment> commentsByPage = commentDao.findCommentsByPage(query); // 评论数 long count = commentsByPage==null?0:commentsByPage.size(); for (Comment comment : commentsByPage) { String username = comment.getUsername().replaceAll("(\\d{3})(\\d{4})(\\d{4})", "$1****$2"); comment.setUsername(username); } LivegoodsResult result = LivegoodsResult.ok(commentsByPage); // 计算总页数 long totalPages = (count % rows == 0) ? (count / rows) : (count / rows + 1); // 判断是否还有更多数据 if((page+1)<totalPages){ result.setHasMore(true); }else { result.setHasMore(false); } return result; }
-
在livegoods-comment模块的com.livegoods.comment.controller包下的类CommentController下,添加分页查询评论方法
/** * 分页查询商品评论 * @param itemId * @param page * @param rows * @return */ @GetMapping("/comment") public LivegoodsResult getCommentsByItemId(@RequestParam(value = "id") String itemId,int page,@RequestParam(defaultValue = "5")int rows){ return commentService.findCommentsByItemId(itemId,page,rows); }
-
在livegoods-gateway模块的bootstrap.yml文件下,添加comment路由
# 商品评论路由 - id: comment uri: lb://livegoods-comment predicates: - Path=/comment # 请求中必须包含city请求参数。参数内容不限。 - Query=id - Query=page
-
测试
1)在虚拟机启动MongoDB、Eureka服务
2)然后启动项目的condfig9010配置中心服务,重启gateway4006网关服务,还有重启comment9005商品评论服务。
3)在浏览器上访问:http://localhost/details/6468bbeb0334ebcb2e78a51a
,查询商品评论时每次只显示最多5条数据
8、安装kibana
- 在Elasticsearch官网下载linux版的kibana:
https://artifacts.elastic.co/downloads/kibana/kibana-7.17.7-linux-x86_64.tar.gz
- 在目录/opt中上传该压缩包,然后解压缩到目录/usr/local/中:
tar -zxvf /opt/kibana-7.17.7-linux-x86_64.tar.gz -C /usr/local/
- 修改配置文件
1)进入到config目录下:cd /usr/local/kibana-7.17.7-linux-x86_64/config/
2)修改配置文件:vim kibana.yml
3)添加kibana主机ip:server.host: "192.168.126.30"
添加Elasticsearch路径:elasticsearch.hosts: ["http://127.0.0.1:9200"]
- 给es用户设置kibana目录权限:
chown -R es:es /usr/local/kibana-7.17.7-linux-x86_64/
- 切换到es用户:
su es
- 进入到kibana的bin目录:
cd /usr/local/kibana-7.17.7-linux-x86_64/bin/
- 确保启动Elasticsearch后(需要切换为es用户后再启动ES服务),再启动kibana:
./kibana
- 在浏览器中访问kibana:
http://192.168.126.30:5601/
如果google浏览器版本太低的话是访问不到这个页面的
在kibana页面中选择Management,再选择Index Management,即可查看Elasticsearch索引信息
9、Search查询商品模块开发
9.1 Search查询商品模块的ES批量新增测试
-
在livegoods-search模块的com.livegoods.search.pojo包下,创建ES实体类Item4ES
package com.livegoods.search.pojo; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; 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; @NoArgsConstructor @Data @ToString @EqualsAndHashCode @Document(indexName = "livegoods-item") public class Item4ES { @Id private String id; // 出租类型,不分词 @Field(type = FieldType.Keyword) private String rentType; // 出租价格,不分词 @Field(type = FieldType.Keyword) private String price; // 房屋类型,最细粒度分词 @Field(type = FieldType.Text,analyzer = "ik_max_word") private String houseType; // 房屋图片,不分词 @Field(type = FieldType.Keyword) private String img; // 商品标题,最细粒度分词 @Field(type = FieldType.Text,analyzer = "ik_max_word") private String title; // 商品城市,不分词 @Field(type = FieldType.Keyword) private String city; }
-
在livegoods-search模块的com.livegoods.search.dao包下,创建ES数据访问层接口ItemDao4ES
package com.livegoods.search.dao; import com.livegoods.search.pojo.Item4ES; import java.util.List; public interface ItemDao4ES { // 批量添加数据到ES void batchIndex(List<Item4ES> items); }
-
在livegoods-search模块的com.livegoods.search.dao.impl包下,创建ES数据访问层实现类ItemDao4ESImpl
package com.livegoods.search.dao.impl; import com.livegoods.search.dao.ItemDao4ES; import com.livegoods.search.pojo.Item4ES; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.domain.PageRequest; import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.SearchHit; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.query.*; import org.springframework.stereotype.Repository; import java.util.ArrayList; import java.util.List; @Repository public class ItemDao4ESImpl implements ItemDao4ES { @Autowired private ElasticsearchOperations operations; @Value("${livegoods.search.init.enabled}") private boolean initEnabled = false; /** * 批量添加数据到ES * @param items */ @Override public void batchIndex(List<Item4ES> items) { // 判断是否需要初始化ES索引 if(initEnabled){ initIndex(); } ArrayList<IndexQuery> list = new ArrayList<>(); items.forEach(item4ES -> { list.add(new IndexQueryBuilder().withObject(item4ES).build()); }); // 批量插入数据 operations.bulkIndex(list, Item4ES.class); } //初始化索引 private void initIndex() { IndexOperations indexOps = operations.indexOps(Item4ES.class); if (indexOps.exists()){ indexOps.delete(); } indexOps.create(); indexOps.refresh(); indexOps.putMapping(indexOps.createMapping()); }
-
在livegoods-search模块的Test目录的com.livegoods包下,创建ES测试类TestSearch,添加ES批量新增的测试方法testInitES
package com.livegoods; import com.livegoods.commons.pojo.Item; import com.livegoods.search.SearchApplication; import com.livegoods.search.dao.ItemDao4ES; import com.livegoods.search.pojo.Item4ES; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.test.context.junit4.SpringRunner; import java.util.ArrayList; import java.util.List; @SpringBootTest(classes = SearchApplication.class) @RunWith(SpringRunner.class) public class TestSearch { @Autowired private MongoTemplate mongoTemplate; @Autowired private ItemDao4ES itemDao4ES; //从配置文件中获取FastDFS服务的ip+port @Value("${livegoods.banner.nginx.prefix}") private String nginxPrefix; @Test public void testInitES(){ List<Item> items = mongoTemplate.findAll(Item.class); ArrayList<Item4ES> list = new ArrayList<>(); items.forEach(item4ES -> { Item4ES item = new Item4ES(); item.setId(item4ES.getId()); item.setCity(item4ES.getCity()); item.setHouseType(item4ES.getHouseType()); item.setImg(nginxPrefix+item4ES.getImg()); item.setPrice(String.valueOf(item4ES.getPrice())); item.setTitle(item4ES.getTitle()); item.setRentType(item4ES.getRentType()); list.add(item); }); itemDao4ES.batchIndex(list); } }
-
测试
1)在虚拟机启动elasticsearch、kibana、mongodb、eureka服务
2)启动search9003服务
3)运行该测试方法
4)访问kibana:192.168.126.30:5601
5)在kibana中,点击左上角的功能模块,选择stack management,再选择Index Management,即可看到该索引
6)在kibana中,点击左上角的功能模块,选择Dev Tools,再输入搜索的语句,最后点击运行按钮即可显示所有数据GET /livegoods-item/_search { "query":{ "match_all":{} } }
9.2 Search查询商品模块的使用ik分词器进行分页查询
-
在livegoods-search模块的com.livegoods.search.dao包下的ItemDao4ES接口中,添加分页查询方法queryForPage
// 分页查询 List<Item4ES> queryForPage(String city, String content, int page, int rows);
-
在livegoods-search模块的com.livegoods.search.dao.impl包下的ItemDao4ESImpl类中,添加分页查询方法queryForPage
/** * 分页搜索 * @param city 城市 * @param content 搜索关键字, 在title商品标题字段中匹配 * @param page 页码, 从0开始的 * @param rows 查询行数 * @return */ @Override public List<Item4ES> queryForPage(String city, String content, int page, int rows) { // 创建搜索条件集合 Criteria criteria = new Criteria().and(new Criteria("city").is(city)).subCriteria( // 标题搜索 new Criteria().or("title").is(content) // 房屋类型 .or("houseType").is(content) // 租赁类型 .or("rentType").is(content)); // 创建搜索条件对象 Query query = new CriteriaQuery(criteria).setPageable(PageRequest.of(page * rows, rows)); // 搜索 SearchHits<Item4ES> result = operations.search(query, Item4ES.class); List<SearchHit<Item4ES>> searchHits = result.getSearchHits(); ArrayList<Item4ES> list = new ArrayList<>(); for (SearchHit<Item4ES> searchHit : searchHits) { // 创建Item4ES对象并构建数据 Item4ES item4ES = new Item4ES(); // 商品ID item4ES.setId(searchHit.getContent().getId()); // 商品标题 item4ES.setTitle(searchHit.getContent().getTitle()); // 租赁方式 item4ES.setRentType(searchHit.getContent().getRentType()); // 商品价格 item4ES.setPrice(searchHit.getContent().getPrice()); // 商品城市 item4ES.setCity(searchHit.getContent().getCity()); // 房屋类型 item4ES.setHouseType(searchHit.getContent().getHouseType()); // 商品图片 item4ES.setImg(searchHit.getContent().getImg()); list.add(item4ES); } return list; }
-
在livegoods-search模块的com.livegoods.search.service包下的接口SearchService中,添加分页查询方法search
package com.livegoods.search.service; import com.livegoods.commons.vo.LivegoodsResult; // 搜索服务接口 public interface SearchService { // 搜索服务方法 LivegoodsResult search(String city,String content,int page,int rows); }
-
在livegoods-search模块的com.livegoods.search.service.impl包下的SearchServiceImpl类中,添加分页查询方法search
package com.livegoods.search.service.impl; import com.livegoods.commons.vo.LivegoodsResult; import com.livegoods.search.dao.ItemDao4ES; import com.livegoods.search.pojo.Item4ES; import com.livegoods.search.service.SearchService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; // 搜索服务的实现类 @Service public class SearchServiceImpl implements SearchService { @Autowired private ItemDao4ES itemDao4ES; /** * 搜索商品逻辑 * @param city 城市 * @param content 搜索关键字 * @param page 第几页, 从0开始 * @param rows 每页查询多少行 * @return */ @Override public LivegoodsResult search(String city, String content, int page, int rows) { List<Item4ES> item4ES = itemDao4ES.queryForPage(city, content, page, rows); return LivegoodsResult.ok(item4ES); } }
-
在livegoods-search模块的com.livegoods.search.controller包下的SearchController 类中,添加分页查询方法search
package com.livegoods.search.controller; import com.livegoods.commons.vo.LivegoodsResult; import com.livegoods.search.service.SearchService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class SearchController { @Autowired private SearchService searchService; /** * 搜索商品 * @param city * @param content 搜索的内容 * @param page * @param rows * @return */ @RequestMapping("/search") public LivegoodsResult search(String city, String content, int page, @RequestParam(defaultValue = "4") int rows){ LivegoodsResult search = searchService.search(city, content, page, rows); return search; } }
-
在livegoods-gateway模块的bootstrap.yml文件下添加search模块的路由
# 搜索商品路由 - id: search uri: lb://livegoods-search predicates: - Path=/search # 请求中必须包含city请求参数。参数内容不限。 - Query=city - Query=content - Query=page
-
测试
1)在虚拟机启动elasticsearch、kibana、mongodb、eureka服务
2)启动config9010、重启gateway4006、banner9000、comment9005、details9004、hotProduct9001、recommendation9002以及重启搜索模块search9003
3)启动前端vue代码,在终端下执行:npm run serve
4)在浏览器访问:localhost
5)在前端页面搜索框搜索别墅,就会出现2个包含别墅的商品信息
10、Login登录模块开发
10.1 Login登录模块的验证码接口开发
-
在livegoods-login模块的POM文件中,添加commons和redis模块依赖
<!-- commons模块依赖 --> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- redis模块依赖 --> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-cache-redis</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- mongodb模块依赖 --> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-mongodb-dao</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
-
在livegoods-login模块的com.livegoods.login.config包下,创建Login模块的Redis配置类,即引用redis模块的配置
package com.livegoods.login.config; import com.livegoods.redis.config.RedisCacheConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; /** * Login 配置Redis相关内容 */ @Configuration public class LoginRedisConfiguration extends RedisCacheConfiguration { /** * 创建缓存管理器 * @param redisConnectionFactory Redis连接工厂 * @return */ @Bean public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){ return super.redisTemplate(redisConnectionFactory); } }
-
在livegoods-login模块的com.livegoods.login.dao包下,创建验证码数据层接口ValidateCodeDao
package com.livegoods.login.dao; import com.livegoods.commons.pojo.ValidateCode; // 验证码数据层接口 public interface ValidateCodeDao { // 保存验证码到redis void set(String key,Object value); // 根据手机号取出验证码 用于跟用户输入的验证码比对 ValidateCode get(String key); // 删除验证码 Boolean delete(String key); }
-
在livegoods-login模块的com.livegoods.login.dao.impl包下,创建验证码数据层实现类ValidateCodeDaoImpl
package com.livegoods.login.dao.impl; import com.livegoods.commons.pojo.ValidateCode; import com.livegoods.login.dao.ValidateCodeDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Repository; import java.time.Duration; // 验证码数据层实现类 @Repository public class ValidateCodeDaoImpl implements ValidateCodeDao { @Autowired private RedisTemplate<String,Object> redisTemplate; /** * 保存验证码到redis 2分钟 * @param key * @param value */ @Override public void set(String key, Object value) { redisTemplate.opsForValue().set(key,value, Duration.ofMinutes(2L)); } /** * 在redis中根据key获取value * @param key * @return */ @Override public ValidateCode get(String key) { ValidateCode validateCode = (ValidateCode) redisTemplate.opsForValue().get(key); return validateCode; } /** * 在redis中根据key删除value * @param key * @return */ @Override public Boolean delete(String key) { Boolean flag = redisTemplate.delete(key); return flag; } }
-
在livegoods-login模块的com.livegoods.login.service包下,创建登录服务层接口LoginService
package com.livegoods.login.service; import com.livegoods.commons.vo.LivegoodsResult; // 用户登录服务接口 public interface LoginService { // 发送验证码 LivegoodsResult sendyzm(String phone); }
-
在livegoods-login模块的com.livegoods.login.service.impl包下,创建登录服务层实现类LoginServiceImpl
package com.livegoods.login.service.impl; import com.livegoods.commons.pojo.ValidateCode; import com.livegoods.commons.vo.LivegoodsResult; import com.livegoods.login.dao.ValidateCodeDao; import com.livegoods.login.service.LoginService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Random; // 用户登录服务实现类 @Service public class LoginServiceImpl implements LoginService { @Autowired private ValidateCodeDao validateCodeDao; /** * 发送验证码到手机号 * @param phone * @return */ @Override public LivegoodsResult sendyzm(String phone) { //查找phone对应的验证码 ValidateCode validateCode = validateCodeDao.get(phone); if(validateCode!=null){ return LivegoodsResult.ok("验证码已经发送,请勿重复申请"); } StringBuilder stringBuilder = new StringBuilder(); Random r = new Random(); // 生成4位数的验证码 for (int i = 0; i < 4; i++) { stringBuilder.append(r.nextInt(10)); } String validateCode_1 = stringBuilder.toString(); // 创建验证码对象 ValidateCode code = new ValidateCode(); code.setPhone(phone); code.setValidateCode(validateCode_1); // 将验证码对象保存到redis中 validateCodeDao.set(phone,code); //返回结果 LivegoodsResult result = LivegoodsResult.ok(); result.setMessage("验证码发送成功"); System.out.println("手机号:"+phone+",验证码:"+validateCode_1); return result; } }
-
在livegoods-login模块的com.livegoods.login.controller包下,创建登录控制层类LoginController
package com.livegoods.login.controller; import com.livegoods.commons.vo.LivegoodsResult; import com.livegoods.login.service.LoginService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class LoginController { @Autowired private LoginService loginService; /** * 发送验证码到手机号 * @param phone * @return */ @PostMapping("/sendyzm") public LivegoodsResult sendyzm(String phone) { return loginService.sendyzm(phone); } }
-
测试
1)在虚拟机启动eureka、redis服务
2)启动config9010、login9007服务
3)在Postman测试工具发送Post请求:localhost:9007/sendyzm?phone=13719244228
4)在redis中查看是否保存成功。
10.2 Login登录模块的登录接口开发
-
在livegoods-login模块的com.livegoods.login.dao包下,创建登录数据层接口LoginLogDao
package com.livegoods.login.dao; import com.livegoods.commons.pojo.LoginLog; // 登录数据层接口 public interface LoginLogDao { // 保存日志 void insertLoginLog(LoginLog loginLog); }
-
在livegoods-login模块的com.livegoods.login.dao.impl包下,创建登录数据层实现类LoginLogDaoImpl
package com.livegoods.login.dao.impl; import com.livegoods.commons.pojo.LoginLog; import com.livegoods.login.dao.LoginLogDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.stereotype.Repository; // 登录数据层实现类 @Repository public class LoginLogDaoImpl implements LoginLogDao { @Autowired private MongoTemplate mongoTemplate; /** * 保存登录日志 * @param loginLog */ @Override public void insertLoginLog(LoginLog loginLog) { mongoTemplate.save(loginLog); } }
即报错登录日志到MongoDB中。
-
在livegoods-login模块的com.livegoods.login.service包下的LoginService接口中,添加登录方法login
// 登录 LivegoodsResult login(String username,String validateCode);
-
在livegoods-login模块的com.livegoods.login.service.impl包下的LoginServiceImpl类中,添加登录方法login
/** * 登录 * @param username * @param validateCode * @return */ @Override public LivegoodsResult login(String username, String validateCode) { // 1.创建登录日志对象 LoginLog loginLog = new LoginLog(); loginLog.setUsername(username); loginLog.setLoginTime(new Date()); // 1-代表验证码登录 loginLog.setType("1"); // 2.从redis中获取对应用户的验证码 ValidateCode validateCode2 = validateCodeDao.get(username); // 2.1 判断验证码是否为空,校验不通过时保存日志 if(StringUtils.isEmpty(validateCode2.getValidateCode())){ loginLog.setIsSuccess(false); loginLog.setMessage("验证码不存在或已过期"); loginLogDao.insertLoginLog(loginLog); return LivegoodsResult.error("验证码已过期"); } // 2.2 判断验证码与redis中存储的是否一致,校验不通过时保存日志 if(!validateCode.equals(validateCode2.getValidateCode())){ loginLog.setIsSuccess(false); loginLog.setMessage("验证码错误"); loginLogDao.insertLoginLog(loginLog); return LivegoodsResult.error("验证码错误"); } // 3.保存日志 loginLog.setIsSuccess(true); loginLog.setMessage("登录成功"); loginLogDao.insertLoginLog(loginLog); // 4.从redis中删除验证码 validateCodeDao.delete(username); // 5.返回LivegoodsResult对象,提示登录成功 return LivegoodsResult.ok("登录成功"); }
-
在livegoods-login模块的com.livegoods.login.controller包下的LoginController类中,添加登录方法login
/** * 登录 * @param username * @param password * @return */ @PostMapping("/login") public LivegoodsResult login(String username,String password) { return loginService.login(username,password); }
-
在livegoods-gateway模块的bootstrap.yml文件中,添加login和sendyzm路由
# 登录路由 - id: login uri: lb://livegoods-login predicates: - Path=/login # 验证码路由 - id: sendyzm uri: lb://livegoods-login predicates: - Path=/sendyzm
-
测试
1)在虚拟机启动eureka、redis、mongodb服务
2)启动config9010、重启gateway4006、banner9000、comment9005、details9004、hotProduct9001、recommendation9002以及search9003以及重启login9007服务
3)在VSCODE启动前端项目,在终端执行:npm run serve
4)在浏览器访问:http://localhost/login
输入手机号,点击发送验证码,最后从控制台拿取,再从前端输入后登录。
手机号验证码功能暂时没有开发,使用模拟测试登录即可。
登录成功后,会回到其他页面。
10.3 Login登录模块引入SpringSecurity
-
在livegoods-login模块的POM文件中,添加SpringSecurity依赖
<!-- SpringSecurity依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
-
在livegoods-login模块的com.livegoods.login.service包下,创建Security身份认证结果处理器类MyAuthenticationService
package com.livegoods.login.service; import com.fasterxml.jackson.databind.ObjectMapper; import com.livegoods.commons.vo.LivegoodsResult; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Service; import java.io.IOException; @Service public class MyAuthenticationService implements AuthenticationFailureHandler, AuthenticationSuccessHandler { @Autowired private ObjectMapper objectMapper; @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { System.out.println("登陆失败后续处理......."); LivegoodsResult error = LivegoodsResult.error(); response.setContentType("application/json;charset=UTF-8"); // 将LivegoodsResult对象转换成String response.getWriter().write(objectMapper.writeValueAsString(error)); } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println("登陆成功后续处理......."); LivegoodsResult result = LivegoodsResult.ok(); response.setContentType("application/json;charset=UTF-8"); result.setMessage("登录成功"); // 将LivegoodsResult对象转换成String response.getWriter().write(objectMapper.writeValueAsString(result)); } }
-
在livegoods-login模块的com.livegoods.login.service包下,创建Security的用户登录逻辑类MyUserDetailsService
package com.livegoods.login.service; import com.livegoods.commons.pojo.ValidateCode; import com.livegoods.login.dao.ValidateCodeDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.ArrayList; @Service public class MyUserDetailsService implements UserDetailsService { @Autowired private ValidateCodeDao validateCodeDao; /** * 自定义登录逻辑 * @param username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { ValidateCode validateCode = validateCodeDao.get(username); String password = ""; if(validateCode != null){ password = validateCode.getValidateCode(); } // 用户权限集合 ArrayList<GrantedAuthority> authorities = new ArrayList<>(); UserDetails user = new User(username, // noop-不加密,bcrypt--加密 "{noop}" + password, // 启动用户 true, // 用户永不过期 true, // 凭证永不过期 true, // 锁定用户 true, // 权限集合 authorities); return user; } }
-
在livegoods-login模块的com.livegoods.login.config包下,创建Security配置类SecurityConfig
package com.livegoods.login.config; import com.livegoods.login.service.MyAuthenticationService; import com.livegoods.login.service.MyUserDetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @Configuration @EnableWebSecurity public class SecurityConfig { @Autowired private MyAuthenticationService myAuthenticationService; @Autowired private MyUserDetailsService myUserDetailsService; // 安全过滤器链 @Bean public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(authorize -> // 对sendyzm放行 authorize.requestMatchers("/sendyzm").permitAll().anyRequest().authenticated()) .formLogin() // 登录处理url .loginProcessingUrl("/login") // 登录成功后转发 .successForwardUrl("/details") // 登录成功的处理器 .successHandler(myAuthenticationService) // 登录失败的处理器 .failureHandler(myAuthenticationService) // 记住密码 .and().rememberMe() // token验证保存时间 2周 .tokenValiditySeconds(1209600) // 设置 .userDetailsService(myUserDetailsService); // 关闭crsf防护 http.csrf().disable(); // 设置允许跨域 http.cors().configurationSource(corsConfigurationSource()); return http.build(); } /** * 跨域配置 * @return */ public CorsConfigurationSource corsConfigurationSource(){ CorsConfiguration corsConfiguration = new CorsConfiguration(); // 允许跨域的站点 corsConfiguration.addAllowedOrigin("*"); // 允许跨域的方法 corsConfiguration.addAllowedMethod("*"); // 允许跨域的请求头 corsConfiguration.addAllowedHeader("*"); // 设置对所有的url请求生效 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**",corsConfiguration); return source; } }
-
在livegoods-login模块的package com.livegoods.login.controller包下的登录控制层类LoginController中,添加获取当前用户名方法getCurrentUser
@GetMapping("/getLoginUser") public UserDetails getCurrentUser(@AuthenticationPrincipal UserDetails userDetails){ return userDetails; }
-
测试
1)在虚拟机启动eureka、redis、mongodb服务
2)启动config9010、重启gateway4006、banner9000、comment9005、details9004、hotProduct9001、recommendation9002以及search9003以及重启login9007服务
3)在VSCODE启动前端项目,在终端执行:npm run serve
4)在浏览器访问:http://localhost/login
输入手机号,点击发送验证码,最后从控制台拿取,再从前端输入后登录。
手机号验证码功能暂时没有开发,使用模拟测试登录即可。
但是这个功能可以实现账号密码登录,只是没有实现账号密码登录的页面,但是可以自己从redis中添加缓存,然后点击发送验证码,登录按钮激活了,此时拿出在redis中的数据进行校验
5)登录完成后,在浏览器访问:http://localhost:9007/getLoginUser
11、Buyaction房屋预订模块开发
-
在livegoods-buyaction模块的POM文件中,添加commons、redis、rabbit模块的依赖
<!-- commons模块依赖 --> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- redis模块依赖 --> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-cache-redis</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- rabbit配置模块依赖 --> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-rabbit-publisher</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
-
在livegoods-buyaction模块的com.livegoods.buyaction.config包下,创建buyaction的Redis配置类BuyactionRedisConfiguration
package com.livegoods.buyaction.config; import com.livegoods.redis.config.RedisCacheConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; @Configuration public class BuyactionRedisConfiguration extends RedisCacheConfiguration { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { return super.redisTemplate(redisConnectionFactory); } }
-
在livegoods-buyaction模块的com.livegoods.buyaction.dao包下,创建商品数据层接口ItemDao
package com.livegoods.buyaction.dao; import com.livegoods.commons.pojo.Item; // 商品数据层接口 public interface ItemDao { // 根据商品的key值到redis中查询商品的详情 Item get(String key); }
-
在livegoods-buyaction模块的com.livegoods.buyaction.dao.impl包下,创建商品数据层实现类ItemDaoImpl
package com.livegoods.buyaction.dao.impl; import com.livegoods.buyaction.dao.ItemDao; import com.livegoods.commons.pojo.Item; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Repository; // 商品数据层实现类 @Repository public class ItemDaoImpl implements ItemDao { @Autowired private RedisTemplate redisTemplate; /** * 根据商品的key值到redis中查询商品的详情 * @param key * @return */ @Override public Item get(String key) { Item item = (Item)redisTemplate.opsForValue().get(key); return item; } }
-
在livegoods-commons模块的com.livegoods.commons.message包下,创建实体类LivegoodsBuyMessage
package com.livegoods.commons.message; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; import java.io.Serializable; @Data @EqualsAndHashCode @ToString @NoArgsConstructor public class LivegoodsBuyMessage implements Serializable { // 商品id private String itemId; // 预订商品的用户名 private String username; }
-
在livegoods-buyaction模块的com.livegoods.buyaction.service包下,创建预订商品服务层接口BuyactionService
package com.livegoods.buyaction.service; import com.livegoods.commons.vo.LivegoodsResult; // 预订商品服务层接口 public interface BuyactionService { /** * 预订商品服务方法 * @param id * @param user * @return */ LivegoodsResult buyaction(String id,String user); }
-
在livegoods-buyaction模块的com.livegoods.buyaction.service.impl包下,创建预订商品服务层实现类BuyactionServiceImpl
package com.livegoods.buyaction.service.impl; import com.livegoods.buyaction.dao.ItemDao; import com.livegoods.buyaction.service.BuyactionService; import com.livegoods.commons.message.LivegoodsBuyMessage; import com.livegoods.commons.pojo.Item; import com.livegoods.commons.vo.LivegoodsResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.stream.function.StreamBridge; import org.springframework.stereotype.Service; // 预订商品服务层实现类 @Service public class BuyactionServiceImpl implements BuyactionService { @Autowired private ItemDao itemDao; @Value("${livegoods.cache.names.item.prefix}") private String itemPrefix; @Value("${livegoods.cache.names.item.suffix}") private String itemSuffix; @Autowired private StreamBridge streamBridge; @Override public LivegoodsResult buyaction(String id, String user) { // 1.访问redis,查询商品详情是否可以预订 String key = itemPrefix + "::" + itemSuffix + "(" + id + ")"; Item item = itemDao.get(key); if(item.getIsRented()){ // 房屋已经被预订了 LivegoodsResult error = LivegoodsResult.error("手慢了,已经被预订了"); return error; } // 2.构建一个消息对象,发送消息到mq,并等待消费者响应 LivegoodsBuyMessage message = new LivegoodsBuyMessage(); message.setItemId(id); message.setUsername(user); boolean sendResult = streamBridge.send("livegoodsMessenger-out-0", message); // 3.根据消息消费者响应结果,返回操作结果 if(sendResult){ // 发送成功,消息消费完成 LivegoodsResult ok = LivegoodsResult.ok(); ok.setMessage("预订成功"); return ok; }else{ LivegoodsResult result = LivegoodsResult.error("手慢了,已经被预订了"); return result; } } }
-
在livegoods-buyaction模块的com.livegoods.buyaction.controller包下,创建预订商品控制层类BuyactionController
package com.livegoods.buyaction.controller; import com.livegoods.buyaction.service.BuyactionService; import com.livegoods.commons.vo.LivegoodsResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class BuyactionController { @Autowired private BuyactionService buyactionService; /** * 预订商品 * @param id * @param user * @return */ @GetMapping("/buyaction") public LivegoodsResult buyaction(String id,String user){ return buyactionService.buyaction(id,user); } }
-
在livegoods-gateway模块的bootstrap.yml文件中,添加预订商品buyaction的路由
# 预订商品路由 - id: buyaction uri: lb://livegoods-buyaction predicates: - Path=/buyaction
-
在livegoods-buyaction模块的bootstrap.yml文件中,添加stream-rabbit的配置
spring: cloud stream: bindings: # 消费者绑定名称,livegoodsMessenger是自定义的,in-消费者 0-固定写法 livegoodsMessenger-in-0: # RabbitMQ中真实存在的交换机 destination: livegoodsTopic # 生产者绑定名称,livegoodsMessenger是自定义的,out-生产者 0-固定写法 livegoodsMessenger-out-0: # RabbitMQ中真实存在的交换机 destination: livegoodsTopic function: # 定义消费者,多个需使用,隔开 definition: livegoodsMessenger
-
测试
1)在虚拟机启动eureka、redis、mongodb、rabbitmq服务
2)启动config9010、重启gateway4006、banner9000、comment9005、details9004、hotProduct9001、recommendation9002、search9003、login9007服务以及重启buyaction9008服务
3)在VSCODE启动前端项目,在终端执行:npm run serve
4)在浏览器访问:http://localhost/login
5)在登录后点击房屋信息,最后点击下面的预订
12、Message预订商品的消息消费者模块开发
-
在livegoods-buyaction-message-consumer模块的POM文件中,添加如下模块的依赖
<dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-mongodb-dao</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-cache-redis</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
-
在gitee中的zfw-cloud-config仓库中创建rabbit-dev.yml文件,添加如下配置
spring: rabbitmq: host: 192.168.126.30 username: admin password: admin
-
在livegoods-buyaction-message-consumer模块的com.livegoods.buyaction.message.config包下,创建消息消费者的Redis配置类BuyactionMessageRedisConfiguration
package com.livegoods.buyaction.message.config; import com.livegoods.redis.config.RedisCacheConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; @Configuration public class BuyactionMessageRedisConfiguration extends RedisCacheConfiguration { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { return super.redisTemplate(redisConnectionFactory); } }
-
在livegoods-buyaction-message-consumer模块的com.livegoods.buyaction.message.dao包下,创建商品数据层接口ItemDao
package com.livegoods.buyaction.message.dao; // 商品数据层接口 public interface ItemDao { //更新商品数据,是否已出租 long update(String id,Boolean rented); }
-
在livegoods-buyaction-message-consumer模块的com.livegoods.buyaction.message.dao.impl包下,创建商品数据层实现类ItemDaoImpl
package com.livegoods.buyaction.message.dao.impl; import com.livegoods.buyaction.message.dao.ItemDao; import com.livegoods.commons.pojo.Item; import com.mongodb.client.result.UpdateResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Repository; // 商品数据层实现类 @Repository public class ItemDaoImpl implements ItemDao { @Autowired private MongoTemplate mongoTemplate; /** * 更新商品数据,是否已出租 * @param id * @param rented * @return */ @Override public long update(String id, Boolean rented) { Query query = new Query(); // 创建查询对象 query.addCriteria(Criteria.where("id").is(id)); // 设置更新属性 Update isRented = Update.update("isRented", rented); // 执行更新操作 UpdateResult updateResult = mongoTemplate.updateFirst(query, isRented, Item.class); return updateResult.getModifiedCount(); } }
-
在livegoods-buyaction-message-consumer模块的com.livegoods.buyaction.message.dao包下,创建订单数据层接口OrderDao
package com.livegoods.buyaction.message.dao; import com.livegoods.commons.pojo.Order; // 订单数据层接口 public interface OrderDao { // 保存订单数据 void save(Order order); }
-
在livegoods-buyaction-message-consumer模块的com.livegoods.buyaction.message.dao.impl包下,创建订单数据层实现类OrderDaoImpl
package com.livegoods.buyaction.message.dao.impl; import com.livegoods.buyaction.message.dao.OrderDao; import com.livegoods.commons.pojo.Order; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.stereotype.Repository; // 订单数据层实现类 @Repository public class OrderDaoImpl implements OrderDao { @Autowired private MongoTemplate mongoTemplate; /** * 保存订单数据 * @param order */ @Override public void save(Order order) { mongoTemplate.save(order); } }
-
在livegoods-buyaction-message-consumer模块的com.livegoods.buyaction.message.redisdao包下,创建商品redis数据层接口ItemDao4Redis
package com.livegoods.buyaction.message.redisdao; import com.livegoods.commons.pojo.Item; // 商品redis数据层接口 public interface ItemDao4Redis { // 根据key值在redis中查询缓存的商品 Item get(String key); // 根据key值插入value值,如果key重复则覆盖,即覆盖商品的redis数据 Boolean set(String key,Object value); }
-
在livegoods-buyaction-message-consumer模块的com.livegoods.buyaction.message.redisdao.impl包下,创建商品redis数据层实现类ItemDao4RedisImpl
package com.livegoods.buyaction.message.redisdao.impl; import com.livegoods.buyaction.message.redisdao.ItemDao4Redis; import com.livegoods.commons.pojo.Item; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.SessionCallback; import org.springframework.stereotype.Repository; import java.util.List; // 商品redis数据层实现类 @Repository public class ItemDao4RedisImpl implements ItemDao4Redis { @Autowired private RedisTemplate redisTemplate; /** * 根据key值在redis中查询缓存的商品 * @param key * @return */ @Override public Item get(String key) { return (Item) redisTemplate.opsForValue().get(key); } /** * 当前的方法,需要原子操作。要在更新之前,先查询一下key对应的商品, * 是否可以预定。如果可预定,则更新,如果不可预定,则忽略本次操作。 * @param key * @param value */ @Override public Boolean set(final String key,final Object value) { try { redisTemplate.setEnableTransactionSupport(true); // 执行多次操作,且操作是原子性操作的时候,可以通过回调逻辑,实现。 List<Object> result = (List<Object>) redisTemplate.execute(new SessionCallback<List<Object>>() { public List<Object> execute(RedisOperations redisOperations) throws DataAccessException { Item item = (Item) redisOperations.opsForValue().get(key); if (item.getIsRented()) { // 已出租,考虑并发环境。 return null; } // 开启事务, 通知redis后续的多条命令,是一个原子操作。 redisOperations.multi(); // 未出租, 可以更新数据 redisOperations.opsForValue().set(key, value); // 相当于是提交事务,通知redis,前置的命令都运行,如果有任何命令出错,回滚,没有出错提交。 return redisOperations.exec(); } }); // 关闭事务 redisTemplate.setEnableTransactionSupport(false); if (null == result) { // 原子操作失败, 更新失败 return false; } return true; }catch(Exception e){ // 发生任何异常,redisTemplate都会回滚事务,释放资源,但是不处理异常。 // 如果此处没有异常处理逻辑,异常会抛到主线程(main方法)、直到虚拟机为止。 e.printStackTrace(); return false; } } }
-
在livegoods-buyaction-message-consumer模块的com.livegoods.buyaction.message.service包下,创建预订商品消息消费者服务层接口BuyactionService
package com.livegoods.buyaction.message.service; // 预订商品服务层接口 public interface BuyactionService { /** * 预订商品 * @param id 商品主键 * @param user 用户手机号 * @return */ Boolean buyaction(String id,String user); }
-
在livegoods-buyaction-message-consumer模块的com.livegoods.buyaction.message.service.impl包下,创建预订商品消息消费者服务层实现类BuyactionServiceImpl
package com.livegoods.buyaction.message.service.impl; import com.livegoods.buyaction.message.dao.ItemDao; import com.livegoods.buyaction.message.dao.OrderDao; import com.livegoods.buyaction.message.redisdao.ItemDao4Redis; import com.livegoods.buyaction.message.service.BuyactionService; import com.livegoods.commons.pojo.Item; import com.livegoods.commons.pojo.Order; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; // 预订商品服务层接口的实现类 @Service public class BuyactionServiceImpl implements BuyactionService { @Autowired private ItemDao itemDao; @Autowired private OrderDao orderDao; @Autowired private ItemDao4Redis itemDao4Redis; @Value("${livegoods.cache.names.item.prefix}") private String itemPrefix; @Value("${livegoods.cache.names.item.suffix}") private String itemSuffix; /** * 预订商品,修改redis的对应商品信息,以及保存订单信息到MongoDB * @param id 商品主键 * @param user 用户手机号 * @return */ @Override public Boolean buyaction(String id, String user) { // 1.根据key从redis中获取商品信息 String key = itemPrefix + "::" + itemSuffix + "(" + id + ")"; Item item = itemDao4Redis.get(key); // 2.设置商品对象的isRented属性 item.setIsRented(true);//设置为已出租 // 3.更新商品数据到redis中 Boolean isUpdate = itemDao4Redis.set(key, item); // 4.更新成功则生成order订单数据 if(isUpdate){ long rows = itemDao.update(id,true); if(rows==1){ Order order = new Order(); order.setCommentState(0); order.setHouseType(item.getHouseType4Search()); order.setImg(item.getImg()); order.setPrice(item.getPrice()); order.setUsername(user); order.setItemId(item.getId()); order.setRentType(item.getRentType()); order.setTitle(item.getTitle()); // 5.保存订单 orderDao.save(order); return true; } return false; }else { return false; } } }
-
在livegoods-buyaction-message-consumer模块的com.livegoods.buyaction.message.listener包下,创建预订商品消息消费者类LivegoodsBuyactionMessageConsumer
package com.livegoods.buyaction.message.listener; import com.livegoods.buyaction.message.service.BuyactionService; import com.livegoods.commons.message.LivegoodsBuyMessage; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import java.util.function.Consumer; @Component @Slf4j public class LivegoodsBuyactionMessageConsumer { @Autowired private BuyactionService buyactionService; /** * 消息消费者方法 * @return */ @Bean public Consumer<LivegoodsBuyMessage> livegoodsMessenger(){ return message->{ // 商品id String itemId = message.getItemId(); // 预订商品的用户名 String username = message.getUsername(); Boolean result = buyactionService.buyaction(itemId, username); log.info("消息消费结果:"+result); }; } }
-
测试
1)在虚拟机启动eureka、redis、mongodb、rabbitmq服务
2)启动config9010、重启gateway4006、banner9000、comment9005、details9004、hotProduct9001、recommendation9002、search9003、login9007、buyaction9008以及buyaction-message-consumer服务
3)在VSCODE启动前端项目,在终端执行:npm run serve
4)在浏览器访问:http://localhost/login
5)在登录后点击房屋信息,最后点击下面的预订,预订成功后,Redis中对应商品isRented属性会变成true,MongoDB中的Item集合中的对应商品的isRented属性会变成true,以及生成order订单
13、Buytime商品预订开始时间模块开发
-
在livegoods-buytime模块的POM文件中,添加commons和mongodb模块依赖
<!-- commons模块依赖 --> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- mongodb模块依赖 --> <dependency> <groupId>com.zzx</groupId> <artifactId>livegoods-mongodb-dao</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
-
在livegoods-buytime模块的com.livegoods.buytime.dao包下,创建商品数据层接口ItemDao
package com.livegoods.buytime.dao; import com.livegoods.commons.pojo.Item; // 商品数据层接口 public interface ItemDao { // 根据商品id查询商品 Item findById(String id); }
-
在livegoods-buytime模块的com.livegoods.buytime.dao.impl包下,创建商品数据层接口的实现类ItemDaoImpl
package com.livegoods.buytime.dao.impl; import com.livegoods.buytime.dao.ItemDao; import com.livegoods.commons.pojo.Item; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.stereotype.Repository; // 商品数据层接口的实现类 @Repository public class ItemDaoImpl implements ItemDao { @Autowired private MongoTemplate mongoTemplate; /** * 根据商品id查询商品 * @param id * @return */ @Override public Item findById(String id) { return mongoTemplate.findById(id,Item.class); } }
-
在livegoods-buytime模块的com.livegoods.buytime.service包下,创建商品预定开始时间服务层接口BuytimeService
package com.livegoods.buytime.service; import com.livegoods.commons.vo.LivegoodsResult; // 商品预定开始时间服务层接口 public interface BuytimeService { // 根据商品id查询商品预定时间 LivegoodsResult getBuyTimeById(String id); }
-
在livegoods-buytime模块的com.livegoods.buytime.service.impl包下,创建商品预定开始时间服务层接口的实现类BuytimeServiceImpl
package com.livegoods.buytime.service.impl; import com.livegoods.buytime.dao.ItemDao; import com.livegoods.buytime.service.BuytimeService; import com.livegoods.commons.pojo.Item; import com.livegoods.commons.vo.LivegoodsResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; // 商品预定开始时间服务层接口的实现类 @Service public class BuytimeServiceImpl implements BuytimeService { @Autowired private ItemDao itemDao; @Override public LivegoodsResult getBuyTimeById(String id) { Item item = itemDao.findById(id); LivegoodsResult result = LivegoodsResult.ok(); // 设置商品预订开始时间 result.setTime(item.getBuytime().getTime()); return result; } }
-
在livegoods-buytime模块的com.livegoods.buytime.controller包下,创建商品预定开始时间控制层类BuytimeController
package com.livegoods.buytime.controller; import com.livegoods.buytime.service.BuytimeService; import com.livegoods.commons.vo.LivegoodsResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class BuytimeController { @Autowired private BuytimeService buytimeService; /** * 获取商品预订开始时间 * @param id * @return */ @GetMapping("/buytime") public LivegoodsResult getBuytime(String id){ return buytimeService.getBuyTimeById(id); } }
-
在livegoods-gateway模块的bootstrap.yml文件中,添加预订商品开始时间路由
# 预订商品开始时间路由 - id: buytime uri: lb://livegoods-buytime predicates: - Path=/buytime - Query=id
-
测试
1)在虚拟机启动eureka、redis、mongodb、rabbitmq服务
2)启动config9010、重启gateway4006、banner9000、comment9005、details9004、hotProduct9001、recommendation9002、search9003、login9007、buyaction9008、buyaction-message-consumer以及buytime9006服务
3)在VSCODE启动前端项目,在终端执行:npm run serve
4)修改MongoDB中某个商品的购买开始时间为2024年
5)在浏览器访问:http://localhost/login
6)在登录后点击房屋信息,最后点击下面的预订,等一会,刚刚在mongodb修改到2024年的商品预订按钮会变灰色,不可点击。
14、Resilience4j的circuitbreaker断路器和ratelimiter访问频率限制器配置
-
在livegoods-details模块的POM文件下,添加如下依赖
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency>
-
在livegoods-details模块的com.livegoods.details.exception包下,创建如下Exception类RecordFailurePredicate
package com.livegoods.details.exception; import java.util.function.Predicate; public class RecordFailurePredicate implements Predicate<Throwable> { @Override public boolean test(Throwable throwable) { if (throwable.getCause() instanceof BusinessException) return false; else return true; } }
-
在livegoods-details模块的com.livegoods.details.exception包下,创建如下Exception类BusinessException
package com.livegoods.details.exception; import org.apache.log4j.spi.ErrorCode; /** * 自定义的异常类型 **/ public class BusinessException extends RuntimeException { private ErrorCode errorCode; public BusinessException(ErrorCode errorCode) { super(); this.errorCode = errorCode; } public BusinessException() { super(); } public void setErrorCode(ErrorCode errorCode) { this.errorCode = errorCode; } public ErrorCode getErrorCode() { return errorCode; } }
-
在livegoods-details模块的bootstrap.yml文件中添加如下配置
# 断路器 即当失败率达到指定阈值则开启断路器并进行服务降级 resilience4j.circuitbreaker: configs: default: registerHealthIndicator: true #配置滑动窗口的大小。 slidingWindowSize: 10 #断路器计算失败率或慢调用率之前所需的最小调用数(每个滑动窗口周期)。 minimumNumberOfCalls: 5 #断路器在半开状态下允许通过的调用次数。 permittedNumberOfCallsInHalfOpenState: 3 #如果设置为true,则意味着断路器将自动从开启状态过渡到半开状态,并且不需要调用来触发转换。 #创建一个线程来监视断路器的所有实例,以便在WaitDurationInOpenstate之后将它们转换为半开状态。 #但是,如果设置为false,则只有在发出调用时才会转换到半开, #即使在waitDurationInOpenState之后也是如此。这里的优点是没有线程监视所有断路器的状态。 automaticTransitionFromOpenToHalfOpenEnabled: true #断路器从开启过渡到半开应等待的时间。 waitDurationInOpenState: 5s #以百分比配置失败率阈值。当失败率等于或大于阈值时,断路器状态并关闭变为开启,并进行服务降级。 failureRateThreshold: 50 #表示重试事件缓冲区大小 eventConsumerBufferSize: 10 #记录为失败并因此增加失败率的异常列表。除非通过ignoreExceptions显式忽略,否则与列表中某个匹配或继承的异常都将被视为失败 recordExceptions: - org.springframework.web.client.HttpServerErrorException - java.util.concurrent.TimeoutException - java.io.IOException ignoreExceptions: - com.livegoods.details.exception.BusinessException shared: slidingWindowSize: 100 permittedNumberOfCallsInHalfOpenState: 30 waitDurationInOpenState: 1s failureRateThreshold: 50 eventConsumerBufferSize: 10 ignoreExceptions: - com.livegoods.details.exception.BusinessException instances: backendA: baseConfig: default backendB: registerHealthIndicator: true slidingWindowSize: 10 minimumNumberOfCalls: 10 permittedNumberOfCallsInHalfOpenState: 3 waitDurationInOpenState: 5s failureRateThreshold: 50 eventConsumerBufferSize: 10 recordFailurePredicate: com.livegoods.details.exception.RecordFailurePredicate # 请求频率限制器 resilience4j.ratelimiter: configs: default: registerHealthIndicator: false #一个周期内最大允许访问次数 1次/秒 limitForPeriod: 1 # 限流器每隔limitRefreshPeriod刷新一次,将允许处理的最大请求数量重置为limitForPeriod limitRefreshPeriod: 1s # 线程等待权限的默认等待时间 即此时每秒1个,如果一秒内有一个通过,则后续的请求需要排队,超过timeoutDuration时间,则进行超时处理 timeoutDuration: 0 # 消费者事件的缓冲数 eventConsumerBufferSize: 100 instances: backendA: baseConfig: default backendB: limitForPeriod: 1 limitRefreshPeriod: 1s timeoutDuration: 3s
-
在livegoods-details模块的com.livegoods.details.service.impl包中的DetailsServiceImpl类的getDetails方法上添加如下注释,以及常量BACKEND_B
private static final String BACKEND_B = "backendB"; /** * 根据id查询商品 * 需要将商品中的图片地址,从相对路径修改为绝对路径 * @param id * @return */ @Override @CircuitBreaker(name = BACKEND_B) @RateLimiter(name = BACKEND_B) // @Cacheable(cacheNames = "com:livegoods:details",key="'getDetails('+#id+')'")
-
在livegoods-details模块的com.livegoods.details.controller包中的DetailsController类中,添加testDetails方法
@GetMapping("/testDetails") public LivegoodsResult testDetails(){ ArrayList<String> idList = new ArrayList<>(); idList.add("6468bbeb0334ebcb2e78a51a"); idList.add("6468bbeb0334ebcb2e78a519"); idList.add("6468bbeb0334ebcb2e78a518"); idList.add("6468bbeb0334ebcb2e78a517"); idList.forEach(s->{ Item item = detailsService.getDetails(s); }); return LivegoodsResult.ok(); }
-
测试
1)在虚拟机启动eureka、redis、mongodb、rabbitmq服务
2)启动config9010、重启gateway4006、banner9000、comment9005、hotProduct9001、recommendation9002、search9003、login9007、buyaction9008、buyaction-message-consumer、buytime9006以及重启details9004服务
3)在浏览器上访问:http://localhost:9004/testDetails
,多访问几次即可触发访问频率限制器
15、Order订单模块开发
-
在livegoods-order模块的com.livegoods.order.dao包中,创建订单数据层接口OrderDao
package com.livegoods.order.dao; import com.livegoods.commons.pojo.Order; import java.util.List; // 订单数据层接口 public interface OrderDao { // 根据手机号查询订单数据 List<Order> getOrders(String user); }
-
在livegoods-order模块的com.livegoods.order.dao.impl包中,创建订单数据层接口的实现类OrderDaoImpl
package com.livegoods.order.dao.impl; import com.livegoods.commons.pojo.Order; import com.livegoods.order.dao.OrderDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Repository; import java.util.List; @Repository // 订单数据层接口的实现类 public class OrderDaoImpl implements OrderDao { @Autowired private MongoTemplate mongoTemplate; /** * 根据手机号查询订单数据 * @param user 手机号 * @return */ @Override public List<Order> getOrders(String user) { Query query = new Query(); query.addCriteria(Criteria.where("username").is(user)); List<Order> orders = mongoTemplate.find(query, Order.class); return orders; } }
-
在livegoods-order模块的com.livegoods.order.service包中,创建订单服务层接口OrderService
package com.livegoods.order.service; import com.livegoods.commons.pojo.Order; import java.util.List; // 订单服务层接口 public interface OrderService { // 根据手机号查询订单数据 List<Order> getOrders(String user); }
-
在livegoods-order模块的com.livegoods.order.service.impl包中,创建订单服务层接口的实现类OrderServiceImpl
package com.livegoods.order.service.impl; import com.livegoods.commons.pojo.Order; import com.livegoods.order.dao.OrderDao; import com.livegoods.order.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; // 订单服务层接口的实现类 @Service public class OrderServiceImpl implements OrderService { @Autowired private OrderDao orderDao; /** * 根据手机号查询订单数据 * @param user * @return */ @Override public List<Order> getOrders(String user) { List<Order> orders = orderDao.getOrders(user); return orders; } }
-
在livegoods-order模块的com.livegoods.order.controller包中,创建订单控制层类OrderController
package com.livegoods.order.controller; import com.livegoods.commons.pojo.Order; import com.livegoods.order.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class OrderController { @Autowired private OrderService orderService; /** * 根据手机号查询订单集合 * @param user * @return */ @GetMapping("/order") public List<Order> getOrders(String user){ return orderService.getOrders(user); } }
-
测试
1)在虚拟机启动eureka、mongodb服务
2)启动config9010、order9009服务
3)在Postman测试工具上测试:localhost:9009/order?user=13719244228
16、OpenFeign调用
-
在livegoods-details模块的com.livegoods.details.service包中,创建调用远程Order服务的OpenFeign接口OrderServiceFeignClient
package com.livegoods.details.service; import com.livegoods.commons.pojo.Order; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; @FeignClient(value = "livegoods-order") public interface OrderServiceFeignClient { /** * 远程调用order模块的获取订单集合方法 * @param user * @return */ @GetMapping("/order") public List<Order> getOrders(@RequestParam("user") String user); }
-
在livegoods-details模块的com.livegoods.controller包中的DetailsController类中添加远程调用order的方法selectOrder,并注入OrderServiceFeignClient
@Autowired(required = false) private OrderServiceFeignClient orderServiceFeignClient; /** * 远程调用订单查询方法 * @param user * @return */ @GetMapping("/details/order") public List<Order> selectOrder(String user){ return orderServiceFeignClient.getOrders(user); }
-
测试
1)在虚拟机启动eureka、mongodb服务
2)启动config9010、order9009、details9004服务
3)在Postman测试工具上测试:localhost:9004/details/order?user=13719244228
17、发送阿里云的短信验证码
-
在livegoods-login模块的com.livegoods.login.utils包下,创建短信发送工具类SMSUtils
package com.livegoods.login.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; /** * 短信发送工具类 * */ public class SMSUtils { // public static final String VALIDATE_CODE = "SMS_460945002";//发送短信验证码 public static final String ORDER_NOTICE = "SMS_460935004";//体检预约成功通知 // 我的短信验证码 public static final String VALIDATE_CODE = "SMS_461035181";//发送短信验证码 // public static final String VALIDATE_CODE = "SMS_279525106"; /** * 发送短信 * @param phoneNumbers * @param param * @throws ClientException */ public static Boolean sendShortMessage(String templateCode,String phoneNumbers,Integer param) throws ClientException{ // 设置超时时间-可自行调整 System.setProperty("sun.net.client.defaultConnectTimeout", "10000"); System.setProperty("sun.net.client.defaultReadTimeout", "10000"); // 初始化ascClient需要的几个参数 final String product = "Dysmsapi";// 短信API产品名称(短信产品名固定,无需修改) final String domain = "dysmsapi.aliyuncs.com";// 短信API产品域名(接口地址固定,无需修改) // 替换成你的AK final String accessKeyId = "";// 你的accessKeyId,参考本文档步骤2 final String accessKeySecret = "";// 你的accessKeySecret,参考本文档步骤2 // 初始化ascClient,暂时不支持多region(请勿修改) IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret); DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain); IAcsClient acsClient = new DefaultAcsClient(profile); // 组装请求对象 SendSmsRequest request = new SendSmsRequest(); // 使用post提交 request.setMethod(MethodType.POST); // 必填:待发送手机号。支持以逗号分隔的形式进行批量调用,批量上限为1000个手机号码,批量调用相对于单条调用及时性稍有延迟,验证码类型的短信推荐使用单条调用的方式 request.setPhoneNumbers(phoneNumbers); // 必填:短信签名-可在短信控制台中找到 request.setSignName("帅得真的是无敌了的博客"); // 必填:短信模板-可在短信控制台中找到 request.setTemplateCode(templateCode); // 可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为 // 友情提示:如果JSON中需要带换行符,请参照标准的JSON协议对换行符的要求,比如短信内容中包含\r\n的情况在JSON中需要表示成\\r\\n,否则会导致JSON在服务端解析失败 request.setTemplateParam("{\"code\":\""+param+"\"}"); // 可选-上行短信扩展码(扩展码字段控制在7位或以下,无特殊需求用户请忽略此字段) // request.setSmsUpExtendCode("90997"); // 可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者 // request.setOutId("yourOutId"); // 请求失败这里会抛ClientException异常 SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request); if (sendSmsResponse.getCode() != null && sendSmsResponse.getCode().equals("OK")) { // 请求成功 System.out.println("请求成功"); return true; }else { System.out.println("请求失败"); return false; } } }
-
在livegoods-login模块的com.livegoods.login.service.impl包下的LoginServiceImpl类中,修改发送短信验证码方法sendyzm
@Override public LivegoodsResult sendyzm(String phone) { //查找phone对应的验证码 ValidateCode validateCode = validateCodeDao.get(phone); if(validateCode!=null){ LivegoodsResult ok = LivegoodsResult.ok(); ok.setMessage("验证码已经发送,请勿重复申请"); return ok; } StringBuilder stringBuilder = new StringBuilder(); Random r = new Random(); // 生成4位数的验证码 for (int i = 0; i < 4; i++) { stringBuilder.append(r.nextInt(9)+1); } String validateCode_1 = stringBuilder.toString(); // 创建验证码对象 ValidateCode code = new ValidateCode(); code.setPhone(phone); code.setValidateCode(validateCode_1); // 将验证码对象保存到redis中 validateCodeDao.set(phone,code); //返回结果 try { Boolean flag = SMSUtils.sendShortMessage(SMSUtils.VALIDATE_CODE, phone, Integer.parseInt(validateCode_1)); if(flag) { LivegoodsResult result = LivegoodsResult.ok(); result.setMessage("验证码发送成功"); System.out.println("手机号:" + phone + ",验证码:" + validateCode_1); return result; }else{ validateCodeDao.delete(phone); LivegoodsResult result = LivegoodsResult.error("验证码发送失败"); return result; } } catch (ClientException e) { e.printStackTrace(); validateCodeDao.delete(phone); LivegoodsResult result = LivegoodsResult.error("验证码发送失败"); return result; } }
-
测试
1)在虚拟机启动eureka、redis服务
2)启动config9010、login9007服务
3)在Postman测试工具上测试:localhost:9004/details/order?user=你的手机号
项目的问题
- 预订商品时,redis还未改变数据时,直接显示预订成功,消息消费者消费时,如果商品已经出售会不会出问题。并且后续如果消费失败怎么办。是否需要在消息消费者方法返回的结果那里进行处理。
- 登录后,回退到首页时,重新预订又要登录。
- 项目启动后,在详情页的房屋信息中,预订按钮默认是可以点击的,然后预订时间还没到,就可以点击,等一会后,数据加载过来才设置为不可点击。