前端页面开发使用vue.js
后端技术使用SpringBoot + Spring Cloud + SpringMVC + Spring Security + MongoDB + Redis + ElasticSearch + Maven + FastDFS + Docker
虚拟机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
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 搜索商品模块
整个项目使用微服务架构,并使用SpringCloud2022版本作为微服务架构总体实现技术。拆分颗粒度为接口,每个接口对应一个功能。使用Eureka作为注册中心,使用Gateway作为网关,使用Config作为分布式配置中心,使用OpenFeign进行远程调用,使用Resilience4j进行服务熔断,使用Elasticsearch提升搜索效率,缓存工具使用Redis,缓存技术使用SpringCache,数据库使用MongoDB,数据访问技术使用SpringData,图片上传下载使用FastDFS。MongoDB身为NoSQL数据库,又带有索引,本身读取性能就很高,此处可以使用Redis作为缓存工具,也可以直接从MongoDB中读取数据。本次项目使用Redis作为缓存工具,SpringCache作为缓存技术。
npm run serve
,即启动服务安装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
https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz
tar -zxvf /opt/jdk-17_linux-x64_bin.tar.gz -C /usr/local/
vim /etc/profile
,在该文件最后一行添加如下配置java export JAVA_HOME=/usr/local/jdk-17.0.7/ export PATH=$PATH:$JAVA_HOME/bin
docker run -d --name redis -p 6390:6379 redis --requirepass "123456"
docker ps -a|grep redis
docker exec -it redis bash
redis-cli -h localhost -p 6379
auth 123456
,然后就可以使用redis客户端了ctrl + p + q
拉取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
docker pull mongo:latest
docker run -itd --name mongo -p 27017:27017 mongo:latest --auth
docker exec -it mongo bash
mongo
use admin
db.createUser({ user: "root", pwd: "root", roles: [{ role: "root", db: "admin" }]})
db.auth("root", "root")
,返回1则添加成功https://s3.nosqlbooster.com/download/releasesv8/nosqlbooster4mongo-8.0.10.exe
ctrl + p + q
docker pull rabbitmq:3.9.29-management-alpine
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
docker update rabbitmq --restart=always
下载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
将图片上传到/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()})
删除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即可。
修改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
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
systemctl restart docker
docker run -p 8761:8761 --name eureka -d livegoods/eurekaserver:1.0
http://192.168.126.30:8761
创建一个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/
修改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);
}
}
修改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);
}
}
在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。
在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服务中。
在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
在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的客户端依赖 。
在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缓存的路径拼接的配置
在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);
}
}
在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);
}
}
在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);
}
}
在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);
}
}
在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>
在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);
}
}
在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
在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);
}
}
在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);
}
}
在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;
}
}
在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;
}
}
在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();
}
}
在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=北京
在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/
在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
点击商品的图片(非轮播图),即可进入商品详情页,然后点击房屋信息
在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容器中,查看所有缓存
在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);
}
}
测试
1)在虚拟机启动MongoDB、Eureka服务
2)然后启动项目的condfig9010配置中心服务,还有重启comment9005商品评论服务。
3)在Postman测试工具上面测试:http://localhost:9005/feelback?orderId=11111&comment=112222333
,测试6次(添加6条评论),用来查询商品评论时进行测试
4)在NoSQLBooster工具查看评论是否添加成功
在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条数据
https://artifacts.elastic.co/downloads/kibana/kibana-7.17.7-linux-x86_64.tar.gz
tar -zxvf /opt/kibana-7.17.7-linux-x86_64.tar.gz -C /usr/local/
cd /usr/local/kibana-7.17.7-linux-x86_64/config/
vim kibana.yml
server.host: "192.168.126.30"
elasticsearch.hosts: ["http://127.0.0.1:9200"]
chown -R es:es /usr/local/kibana-7.17.7-linux-x86_64/
su es
cd /usr/local/kibana-7.17.7-linux-x86_64/bin/
./kibana
http://192.168.126.30:5601/
在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":{}
}
}
在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个包含别墅的商品信息
在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中查看是否保存成功。
在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
输入手机号,点击发送验证码,最后从控制台拿取,再从前端输入后登录。
手机号验证码功能暂时没有开发,使用模拟测试登录即可。
登录成功后,会回到其他页面。
在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
在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)在登录后点击房屋信息,最后点击下面的预订
在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订单
在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年的商品预订按钮会变灰色,不可点击。
在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
,多访问几次即可触发访问频率限制器
在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
在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
在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=你的手机号