课程说明
- 首页功能说明
- 系统架构说明
- 实现今日佳人功能
- 实现推荐用户的列表
- 接口增加缓存功能
- 整合前端联调测试
1、首页
在用户登录成功后,就会进入首页,首页中有今日佳人、推荐好友、探花、搜附近等功能。
2、系统架构
在开发完SSO系统中的登录功能后,接下来就需要实现其他的功能,在整体架构中,完成与APP对接的服务工程叫my-tanhua-server,真正的核心业务逻辑使用dubbo完成,其工程名叫:my-tanhua-dubbo,它们的架构示意图如下:
说明:
- 客户端APP发起请求到Nginx,在Nginx中对请求做出判断,将请求转发至sso系统或server系统。
- sso系统中,将对接第三方平台以及完成数据的缓存、消息发送、用户的注册登录功能。
- server系统为APP提供了接口服务的支撑
- 用户请求中携带的token需要到sso系统中进行校验
- 通过rpc调用dubbo中提供的服务,在dubbo服务中与MongoDB对接,完成数据的CRUD操作
- 将一些数据缓存到Redis,从而提升数据查询性能
- 用户数据的查询将基于MySQL数据库进行查询
2.1、nginx服务
2.1.1、部署安装
安装包在资料中:nginx-1.17.3.zip
安装在任意目录,通过命令:start nginx.exe 启动:
重启加载配置文件命令:nginx.exe -s reload
关掉nginx的命令:nginx -s stop
2.1.2、配置
修改conf目录下的nginx.conf文件:
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location /user/ { #请求路径中凡是以/user/开头的请求,转发到sso系统
client_max_body_size 300m; #设置最大的请求体大小,解决大文件上传不了的问题
proxy_connect_timeout 300s; #代理连接超时时间
proxy_send_timeout 300s; #代理发送数据的超时时间
proxy_read_timeout 300s; #代理读取数据的超时时间
proxy_pass http://127.0.0.1:18080; #转发请求
}
location / { #上面未匹配到的在这里处理
client_max_body_size 300m;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_pass http://127.0.0.1:18081; #转发请求到server系统
}
}
}
2.1.3、测试
2.2、搭建server工程
2.2.1、导入依赖
pom.xml文件:
<?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>my-tanhua</artifactId>
<groupId>cn.xiaoha.tanhua</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>my-tanhua-server</artifactId>
<dependencies>
<dependency>
<groupId>cn.xiaoha.tanhua</groupId>
<artifactId>my-tanhua-dubbo-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--dubbo的springboot支持-->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<!--dubbo框架-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<!--zk依赖-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
<version>1.0.2</version>
</dependency>
</dependencies>
</project>
2.2.2、application.properties
spring.application.name = itcast-tanhua-server
server.port = 18081
#数据库连接信息
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.31.81:3306/mytanhua?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
# 枚举包扫描
mybatis-plus.type-enums-package=com.tanhua.server.enums
# 表名前缀(mybatis-plus)
mybatis-plus.global-config.db-config.table-prefix=tb_
# id策略为自增长(mybatis-plus)
mybatis-plus.global-config.db-config.id-type=auto
#dubbo注册中心配置
dubbo.application.name = itcast-tanhua-server
dubbo.registry.address = zookeeper://192.168.31.81:2181
dubbo.registry.client = zkclient
dubbo.registry.timeout = 60000
dubbo.consumer.timeout = 60000
#开启缓存服务
tanhua.cache.enable=false
# Redis相关配置
spring.redis.jedis.pool.max-wait = 5000ms
spring.redis.jedis.pool.max-Idle = 100
spring.redis.jedis.pool.min-Idle = 10
spring.redis.timeout = 10s
spring.redis.cluster.nodes = 192.168.31.81:6379,192.168.31.81:6380,192.168.31.81:6381
spring.redis.cluster.max-redirects=5
#sso系统服务地址
tanhua.sso.url=http://127.0.0.1
#默认今日佳人推荐用户
tanhua.sso.default.user=2
2.2.3、ServerApplication
package com.tanhua.server;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.tanhua.server.mapper") //设置mapper接口的扫描包
@SpringBootApplication
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}
2.3、搭建dubbo工程
my-tanhua-dubbo是dubbo工程的父工程:
<?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>my-tanhua</artifactId>
<groupId>cn.xiaoha.tanhua</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>my-tanhua-dubbo</artifactId>
<packaging>pom</packaging>
<modules>
<module>my-tanhua-dubbo-interface</module>
<module>my-tanhua-dubbo-service</module>
</modules>
</project>
2.3.1、创建my-tanhua-dubbo-interface工程
该工程中定义了dubbo服务中的interface与实体对象。
<?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>my-tanhua-dubbo</artifactId>
<groupId>cn.xiaoha.tanhua</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>my-tanhua-dubbo-interface</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
</dependencies>
</project>
2.3.2、创建my-tanhua-dubbo-service工程
<?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>my-tanhua-dubbo</artifactId>
<groupId>cn.xiaoha.tanhua</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>my-tanhua-dubbo-service</artifactId>
<dependencies>
<!--引入interface依赖-->
<dependency>
<groupId>cn.xiaoha.tanhua</groupId>
<artifactId>my-tanhua-dubbo-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--dubbo的springboot支持-->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<!--dubbo框架-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
<version>1.0.2</version>
</dependency>
<!--zk依赖-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
</dependency>
<!--MongoDB相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
</dependency>
<!--其他工具包依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
</dependencies>
</project>
dubbo-service中的application.properties:
# Spring boot application
spring.application.name = xiaoha-tanhua-dubbo-service
# dubbo 扫描包配置
dubbo.scan.basePackages = com.tanhua.dubbo.server
dubbo.application.name = dubbo-provider-tanhua
#dubbo 对外暴露的端口信息
dubbo.protocol.name = dubbo
dubbo.protocol.port = 20880
#dubbo注册中心的配置
dubbo.registry.address = zookeeper://192.168.31.81:2181
dubbo.registry.client = zkclient
dubbo.registry.timeout = 60000
dubbo.provider.host= 192.168.31.1
#springboot MongoDB配置
spring.data.mongodb.username=tanhua
spring.data.mongodb.password=l3SCjl0HvmSkTtiSbN0Swv40spYnHhDV
spring.data.mongodb.authentication-database=admin
spring.data.mongodb.database=tanhua
spring.data.mongodb.port=27017
spring.data.mongodb.host=192.168.31.81
编写启动类:
package com.tanhua.dubbo.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DubboApplication {
public static void main(String[] args) {
SpringApplication.run(DubboApplication.class, args);
}
}
2.4、工程结构
最终搭建完成的效果如下:
3、今日佳人
今日佳人,会推荐缘分值最大的用户,进行展现出来。缘分值的计算是由用户的行为进行打分,如:点击、点赞、评论、学历、婚姻状态等信息组合而成的。
实现:我们先不考虑推荐的逻辑,假设现在已经有推荐的结果,我们只需要从结果中查询到缘分值最高的用户就可以了。至于推荐的逻辑以及实现,我们将后面的课程中讲解。
流程:
3.1、表结构
#表结构,表名:recommend_user
{
"userId":1001, #推荐的用户id
"toUserId":1002, #用户id
"score":90, #推荐得分
"date":"2019/1/1" #日期
}
已经提供的测试数据(4855条数据):
3.2、编写dubbo服务
3.2.1、编写接口
在my-tanhua-dubbo-interface工程中定义接口:
package com.tanhua.dubbo.server.api;
import com.tanhua.dubbo.server.pojo.RecommendUser;
import com.tanhua.dubbo.server.vo.PageInfo;
public interface RecommendUserApi {
/**
* 查询一位得分
* 最高的推荐用户
*
* @param userId 推荐用户的id
* @return 封装表数据的映射实体类
*/
RecommendUser queryWithMaxScore(Long userId);
/**
* 按照得分倒序
*
* @param userId 推荐用户的id
* @param pageNum 当前页数
* @param pageSize 当前页所显示的数据条数
* @return 封装分页查询的结果类
*/
PageInfo<RecommendUser> queryPageInfo(Long userId, Integer pageNum, Integer pageSize);
}
package com.tanhua.dubbo.server.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import java.io.Serializable;
@Data //生成javabean类set、get等方法的注解
@NoArgsConstructor //生成无参构造方法的注解
@AllArgsConstructor //生成全参构造方法的注解
@Document(collection = "recommend_user") //指定MongoDB的表
public class RecommendUser implements Serializable {
//该类实现serializable接口便于序列化通过流传输
//设置序列化的id号;
private static final long serialVersionUID = -4296017160071130962L;
@Id
private ObjectId id; //主键id
@Indexed
private Long userId; //推荐的用户id
private Long toUserId; //用户id
@Indexed
private Double score; //推荐得分
private String date; //日期
}
package com.tanhua.dubbo.server.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
@Data
@AllArgsConstructor
public class PageInfo<T> implements Serializable {
//该类实现serializable接口便于序列化通过流传输
//设置序列化的id号;
private static final long serialVersionUID = -2105385689859184204L;
/**
* 总条数
*/
private Integer total = 0;
/**
* 当前页
*/
private Integer pageNum = 0;
/**
* 一页显示的大小
*/
private Integer pageSize = 0;
/**
* 数据列表
*/
private List<T> records = Collections.emptyList();
}
3.2.2、编写实现
package com.tanhua.dubbo.server.api;
import com.alibaba.dubbo.config.annotation.Service;
import com.tanhua.dubbo.server.pojo.RecommendUser;
import com.tanhua.dubbo.server.vo.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import java.util.List;
@Service(version = "1.0.0") //指定该类是一个dubbo服务
public class RecommendUserApiImpl implements RecommendUserApi {
//注入MongoDB的模本类
@Autowired
private MongoTemplate mongoTemplate;
/**
* 查询单个最高分数
* 的被推荐用户
* @param userId 被推荐用户的id
* @return
*/
@Override
public RecommendUser queryWithMaxScore(Long userId) {
//查询条件方法,
// 通过用户id(toUserId)作为条件,
//查询被推荐用户的id(userId)
//查询条件相当于where toUserId = userId
Query queryTemp = Query.query(Criteria
.where("toUserId")
.is(userId));
// 然后再根据分数降序排序
Query query = queryTemp.with(Sort.by(Sort.Order.desc("score"))).limit(1);
//mongoTemplate提供的查询一个数据的方法
//参数一为查询条件,参数二为返回指定类的
// 字节码对象
RecommendUser one = this.mongoTemplate.findOne(query, RecommendUser.class);
return one;
}
@Override
public PageInfo<RecommendUser> queryPageInfo(Long userId, Integer pageNum, Integer pageSize) {
//进行分页处理
PageRequest page = PageRequest.of(pageNum - 1,
pageSize,
Sort.by(Sort.Order.desc("score")));
//查询条件方法,
// 通过用户id(toUserId)作为条件,
//查询被推荐用户的id(userId)
Query queryTemp = Query.query(Criteria.where("toUserId").is(userId));
Query query = queryTemp.with(page);
//mongoTemplate提供的查询一个数据的方法
//参数一为查询条件,参数二为返回指定类的
// 字节码对象
List<RecommendUser> pageInfos = this.mongoTemplate.find(query, RecommendUser.class);
return new PageInfo(0,pageNum,pageSize,pageInfos);
}
}
3.2.3、测试
注意:如果在测试时出现以下错误,将采取下面的措施。
package com.tanhua.dubbo.server.api;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class RecommendUserApiTest {
@Autowired
private RecommendUserApi recommendUserApi;
@Test
public void findOneTest(){
System.out.println("结果一:"+this.recommendUserApi.queryWithMaxScore(1L));
System.out.println("结果二:"+this.recommendUserApi.queryWithMaxScore(8L));
System.out.println("结果三:"+this.recommendUserApi.queryWithMaxScore(26L));
}
@Test
public void findPageTest(){
System.out.println(this.recommendUserApi.queryPageInfo(1L, 1, 5));
}
}
3.3、实现今日佳人服务
3.3.1、mock服务
地址:https://mock-java.itheima.net/project/35/interface/api/617
3.3.2、基础代码
3.3.2.1、SexEnum
package com.tanhua.server.enums;
import com.baomidou.mybatisplus.core.enums.IEnum;
public enum SexEnum implements IEnum<Integer> {
MAN(1,"男"),
WOMAN(2,"女"),
UNKNOWN(3,"未知");
private int value;
private String desc;
SexEnum(int value, String desc) {
this.value = value;
this.desc = desc;
}
@Override
public Integer getValue() {
return this.value;
}
@Override
public String toString() {
return this.desc;
}
}
3.3.2.2、BasePojo
package com.tanhua.server.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import java.util.Date;
public abstract class BasePojo {
@TableField(fill = FieldFill.INSERT)
private Date created;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updated;
}
3.3.2.3、User
package com.tanhua.server.pojo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class User extends BasePojo {
private Long id;
private String mobile; //手机号
@JsonIgnore
private String password; //密码,json序列化时忽略
}
3.3.2.4、UserInfo
package com.tanhua.server.pojo;
import com.tanhua.server.enums.SexEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo extends BasePojo {
private Long id;
private Long userId; //用户id
private String nickName; //昵称
private String logo; //用户头像
private String tags; //用户标签:多个用逗号分隔
private SexEnum sex; //性别
private Integer age; //年龄
private String edu; //学历
private String city; //城市
private String birthday; //生日
private String coverPic; // 封面图片
private String industry; //行业
private String income; //收入
private String marriage; //婚姻状态
}
3.3.3、实现功能
实现描述:
- 需要根据前端定义的结构定义java对象
- 根据sso系统提供的接口查询当前登录用户的信息
- 根据dubbo系统提供的服务进行查询今日佳人数据
3.3.3.1、TodayBest
package com.tanhua.server.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 返回数据结果的封装类
* 今日佳人
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TodayBest {
private Long id;
private String avatar; //头像
private String nickname; //昵称
private String gender; //性别 man woman
private Integer age; //年龄
private String[] tags; //标签
private Long fateValue; //缘分值
}
3.3.3.2、TodayBestController
package com.tanhua.server.controller;
import com.tanhua.server.service.TodayBestService;
import com.tanhua.server.utils.Cache;
import com.tanhua.server.vo.PageResult;
import com.tanhua.server.vo.RecommendUserQueryParam;
import com.tanhua.server.vo.TodayBest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 该类为今日佳人功能模块的
* Controller层结构
*/
@RestController
@RequestMapping("tanhua")
@Slf4j //打印日志的标签
public class TodayBestController {
//注入service层
@Autowired
private TodayBestService todayBestService;
/**
* 接收用户传来
* 的token的方法
*
* @param token 用户传来的token
* @return 返回今日佳人的信息数据
*/
@GetMapping("todayBest")
public ResponseEntity<TodayBest> queryTodayBest(@RequestHeader("Authorization")
String token){
try {
//调用service层将token传递过去
TodayBest todayBest= this.todayBestService.queryTodayBest(token);
//判断todayBest
if(null!=todayBest){
//如果结果不为空,直接返回数据结果
return ResponseEntity.ok(todayBest);
}
} catch (Exception e) {
//e.printStackTrace();
log.error("查询出错,",token,e);
}
//如果为空则返回空
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
/**
* 推荐列表功能模块
*
* @param token 传过来的token
* @param queryParam 请求参数封装类对象
* @return 查询出来的推荐用户列表数据
*/
@GetMapping("recommendation")
@Cache
public ResponseEntity<PageResult> queryRecommendation(@RequestHeader("Authorization") String token ,
RecommendUserQueryParam queryParam){
try {
//调用今日佳人模块的service层,将token和请求参数传过去
//返回pageResult
PageResult pageResult = this.todayBestService.queryRecommendation(token,queryParam);
//判断
if(pageResult!=null){
//如果结果不为空则,直接返回成功的数据
return ResponseEntity.ok(pageResult);
}
} catch (Exception e) {
//e.printStackTrace();
log.error("查询推荐用户列表出错~ token = " + token, e);
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
}
3.3.3.3、TodayBestService
package com.tanhua.server.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tanhua.dubbo.server.pojo.RecommendUser;
import com.tanhua.dubbo.server.vo.PageInfo;
import com.tanhua.server.pojo.User;
import com.tanhua.server.pojo.UserInfo;
import com.tanhua.server.vo.PageResult;
import com.tanhua.server.vo.RecommendUserQueryParam;
import com.tanhua.server.vo.TodayBest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.*;
/**
* 该类为今日佳人
* 功能模块的service层结构
*/
@Service
public class TodayBestService {
//注入UserService,为了校验token
@Autowired
private UserService userService;
//注入查询推荐用户业务层
@Autowired
private RecommendUserService recommendUserService;
//从配置文件重货获取默认用户的id
@Value("${tanhua.sso.default.user}")
private Long defaultUser;
//注入查询用户详细信息的service
@Autowired
private UserInfoService userInfoService;
public TodayBest queryTodayBest(String token) {
//调用userService层将token传递过去进行校验
//返回user
User user = this.userService.checkToken(token);
//判断user
if(null==user){
return null;
}
//如果不为空,就查询推荐用户
//将用户id传过去,返回推荐用户信息(去MongoDB里查数据)
TodayBest todayBest = this.recommendUserService.queryTodayBest(user.getId());
//判断结果
if(todayBest==null){
//如果为空的话,就给默认用户返回
todayBest = new TodayBest();
//将配置文件中默认的今日佳人的id封装到todayBest
todayBest.setId(defaultUser);
//设置缘分值,固定的,写死的
todayBest.setFateValue(85L);
}
//然后补全推荐用户的信息(去数据库中查)
//通过调用查询用户详细信息的service
//将推荐用户的id传递过去,返回userinfo信息
UserInfo userInfo = this.userInfoService.queryUserInfoByUserId(todayBest.getId());
//判断返回结果
if(userInfo==null){
//为空直接返回空
return null;
}
//不为空 userinfo的数据封装到todayBest中
todayBest.setAge(userInfo.getAge());
todayBest.setNickname(userInfo.getNickName());
//todayBest.setGender(userInfo.getSex().name().toLowerCase());
todayBest.setGender(userInfo.getSex().getValue() == 1 ? "man" : "woman");
todayBest.setAvatar(userInfo.getLogo());
todayBest.setTags(StringUtils.split(userInfo.getTags(),","));
//如果不为空
return todayBest;
}
/**
* 推荐列表的service层业务实现层方法
* @param token token
* @param queryParam 请求参数
* @return 查询结果
*/
public PageResult queryRecommendation(String token, RecommendUserQueryParam queryParam) {
//调用userService校验token
User user = this.userService.checkToken(token);
//判断user
if(user == null){
//为空则返回null
return null;
}
//创建默认的返回值
PageResult pageResult = new PageResult();
pageResult.setPage(queryParam.getPage());
pageResult.setPagesize(queryParam.getPagesize());
//如果不为空,就调用recommendUserService,
// 去mongodb里查询推荐用户列表
PageInfo<RecommendUser> pageInfo =this.recommendUserService.queryRecommendUserList(
user.getId(),
queryParam.getPage(),
queryParam.getPagesize()
);
//获取里面的用户数据
List<RecommendUser> records = pageInfo.getRecords();
//判断records
if(records==null){
//如果为空则返回默认的PageResult
return pageResult;
}
//如果不为空则 补全从mongodb查出来的用户列表信息
//创建map集合存储推荐用户的id,和缘分值
Map<Long,Long>idAndScore = new HashMap<>();
for (RecommendUser record : records) {
Double floor = Math.floor(record.getScore());
long score = floor.longValue();
idAndScore.put(record.getUserId(),score);
}
//创建查询条件
QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
//设置id集查询条件
wrapper.in("user_id",idAndScore.keySet());
//设置性别查询条件
//判断请求参数
if(StringUtils.isNotEmpty(queryParam.getGender())){
//如果用户传过来的请求参数中的性别不为空
//则再次判断请求参数中的性别
wrapper.eq("sex",StringUtils.equals(queryParam.getGender(),"man")?1:2);
}
//设置城市参数查询
if(StringUtils.isNotEmpty(queryParam.getCity())){
wrapper.like("city",queryParam.getCity());
}
//设置年龄查询条件
if(queryParam.getAge()!=null){
wrapper.le("age",queryParam.getAge());
}
List<UserInfo> userInfos = this.userInfoService.queryUserInfoList(wrapper);
//判断userInfos
if(userInfos==null){
return pageResult;
}
//如果不为空,则封装数据
List<TodayBest> result = new ArrayList<>();
//遍历userInfos,将userInfos添加到result中
for (UserInfo userInfo : userInfos) {
TodayBest best = new TodayBest();
best.setId(userInfo.getUserId());
best.setAvatar(userInfo.getLogo());
best.setNickname(userInfo.getNickName());
best.setTags(StringUtils.split(userInfo.getTags(),","));
best.setGender(userInfo.getSex().getValue() == 1 ? "man" : "woman");
best.setAge(userInfo.getAge());
//设置缘分值
for (Long id : idAndScore.keySet()) {
//判断id
if(id==userInfo.getUserId()){
Long score = idAndScore.get(id);
best.setFateValue(score);
break;
}
}
result.add(best);
}
//根据缘分值降序排序
Collections.sort(result, (o1, o2) -> new Long(o2.getFateValue() - o1.getFateValue()).intValue());
pageResult.setItems(result);
return pageResult;
}
}
3.3.3.4、UserService
package com.tanhua.server.service;
import com.tanhua.server.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
/**
* 该类为校验用户token的
* 业务层(service层)
*/
@Service
@Slf4j
public class UserService {
//注入发送rest请求的模板类
@Autowired
private RestTemplate restTemplate;
//读取配置文件中sso的访问路径
@Value("${tanhua.sso.url}")
private String ssoUrl;
/**
* 校验用户token的方法
* @param token 用户传过来的token
* @return 用户基本信息
*/
public User checkToken(String token) {
try {
//定义访问sso的路径
String url = ssoUrl+"/user/"+token;
User user = restTemplate.getForObject(url, User.class);
//判断
if(null==user){
//如果为空直接返回null
return null;
}
//如果不为空则将user返回
return user;
} catch (RestClientException e) {
//e.printStackTrace();
log.error("校验token出错,token = " + token, e);
}
return null;
}
}
package com.tanhua.server.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.Charset;
/**
* 该类为创建restTemplate对象的配置类
*/
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
RestTemplate restTemplate = new RestTemplate(factory);
// 支持中文编码
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(Charset.forName("UTF-8")));
return restTemplate;
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(5000);//单位为ms
factory.setConnectTimeout(5000);//单位为ms
return factory;
}
}
3.3.3.5、RecommendUserService
package com.tanhua.server.service;
import com.alibaba.dubbo.config.annotation.Reference;
import com.tanhua.dubbo.server.api.RecommendUserApi;
import com.tanhua.dubbo.server.pojo.RecommendUser;
import com.tanhua.dubbo.server.vo.PageInfo;
import com.tanhua.server.vo.TodayBest;
import org.springframework.stereotype.Service;
/**
* 该类为查询MongoDB获取推荐用户的service层
*/
@Service
public class RecommendUserService {
//注入dubbo的recommendUserService
@Reference(version = "1.0.0")
private RecommendUserApi userApi;
/**
* 从MongoDB查询推荐用户的方法
* 主要目的是将推荐用户的id和
* 缘分值拿到
*
* @param id 用户id
* @return 推荐的用户信息
*/
public TodayBest queryTodayBest(Long id) {
//调用查询单个数据的方法
RecommendUser recommendUser = this.userApi.queryWithMaxScore(id);
if(recommendUser==null){
return null;
}
//如果不为空则正常返回todayBest
TodayBest todayBest = new TodayBest();
todayBest.setId(recommendUser.getUserId());
//从查询结果的recommendUser中获取缘分值
double scoreDou=recommendUser.getScore();
Double d = Math.floor(scoreDou);
long score = d.longValue();
todayBest.setFateValue(score);
return todayBest;
}
public PageInfo<RecommendUser> queryRecommendUserList(Long id, Integer page, Integer pagesize) {
return this.userApi.queryPageInfo(id,page,pagesize);
}
}
3.3.3.6、UserInfoService
package com.tanhua.server.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tanhua.server.mapper.UserInfoMapper;
import com.tanhua.server.pojo.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 该类为从数据库中查询用户详细信息的service
*/
@Service
public class UserInfoService {
//注入mapper
@Autowired
private UserInfoMapper userInfoMapper;
/**
* 根据id查询数据
*
* @param id 用户id
* @return 用户详细信息
*/
public UserInfo queryUserInfoByUserId(Long id) {
QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
wrapper.eq("user_id",id);
UserInfo userInfo = this.userInfoMapper.selectOne(wrapper);
return userInfo;
}
public List<UserInfo> queryUserInfoList(QueryWrapper<UserInfo> wrapper) {
return this.userInfoMapper.selectList(wrapper);
}
}
3.3.3.7、UserInfoMapper
package com.tanhua.server.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tanhua.common.pojo.UserInfo;
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}
3.3.4、测试
单元测试,测试dubbo服务:
package com.tanhua.server;
import com.tanhua.dubbo.server.pojo.RecommendUser;
import com.tanhua.dubbo.server.vo.PageInfo;
import com.tanhua.server.service.RecommendUserService;
import com.tanhua.server.vo.RecommendUserQueryParam;
import com.tanhua.server.vo.TodayBest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
/**
* 该类为测试查询今日佳人的测试类
*/
@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class TestRecommendUserApi {
@Autowired
private RecommendUserService recommendUserService;
@Test
public void findRecommendUser(){
TodayBest todayBest = this.recommendUserService.queryTodayBest(100L);
System.out.println(todayBest);
}
@Test
public void test(){
RecommendUserQueryParam recommendUserQueryParam = new RecommendUserQueryParam();
recommendUserQueryParam.setPage(1);
recommendUserQueryParam.setPagesize(100);
recommendUserQueryParam.setAge(30);
recommendUserQueryParam.setCity("北京城区");
recommendUserQueryParam.setGender("man");
PageInfo<RecommendUser> recommendUserPageInfo = this.recommendUserService.queryRecommendUserList(100l, 1, 100);
List<RecommendUser> records = recommendUserPageInfo.getRecords();
System.out.println(records);
}
}
整合功能测试,需要将sso、dubbo服务启动完成后进行测试。
3.3.5、解决MongoDB启动bug
在项目中,添加了mongo的依赖的话,springboot就会自动去连接本地的mongo,由于他连接不上会导致出错。
解决:
springboot中添加排除自动配置的注解
package com.tanhua.server;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
@MapperScan("com.tanhua.server.mapper") //设置mapper接口的扫描包
@SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class}) //排除mongo的自动配置
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}
4、推荐列表
4.1、mock接口
地址:https://mock-java.itheima.net/project/35/interface/api/623
4.2、查询参数对象
package com.tanhua.server.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RecommendUserQueryParam {
private Integer page = 1; //当前页数
private Integer pagesize = 10; //页尺寸
private String gender; //性别 man woman
private String lastLogin; //近期登陆时间
private Integer age; //年龄
private String city; //居住地
private String education; //学历
}
4.3、结果对象
package com.tanhua.server.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Collections;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult {
private Integer counts = 0;//总记录数
private Integer pagesize = 0;//页大小
private Integer pages = 0;//总页数
private Integer page = 0;//当前页码
private List<?> items = Collections.emptyList(); //列表
}
4.4、Controller
package com.tanhua.server.controller;
import com.tanhua.server.service.TodayBestService;
import com.tanhua.server.utils.Cache;
import com.tanhua.server.vo.PageResult;
import com.tanhua.server.vo.RecommendUserQueryParam;
import com.tanhua.server.vo.TodayBest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 该类为今日佳人功能模块的
* Controller层结构
*/
@RestController
@RequestMapping("tanhua")
@Slf4j //打印日志的标签
public class TodayBestController {
//注入service层
@Autowired
private TodayBestService todayBestService;
/**
* 接收用户传来
* 的token的方法
*
* @param token 用户传来的token
* @return 返回今日佳人的信息数据
*/
@GetMapping("todayBest")
public ResponseEntity<TodayBest> queryTodayBest(@RequestHeader("Authorization")
String token){
try {
//调用service层将token传递过去
TodayBest todayBest= this.todayBestService.queryTodayBest(token);
//判断todayBest
if(null!=todayBest){
//如果结果不为空,直接返回数据结果
return ResponseEntity.ok(todayBest);
}
} catch (Exception e) {
//e.printStackTrace();
log.error("查询出错,",token,e);
}
//如果为空则返回空
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
/**
* 推荐列表功能模块
*
* @param token 传过来的token
* @param queryParam 请求参数封装类对象
* @return 查询出来的推荐用户列表数据
*/
@GetMapping("recommendation")
@Cache
public ResponseEntity<PageResult> queryRecommendation(@RequestHeader("Authorization") String token ,
RecommendUserQueryParam queryParam){
try {
//调用今日佳人模块的service层,将token和请求参数传过去
//返回pageResult
PageResult pageResult = this.todayBestService.queryRecommendation(token,queryParam);
//判断
if(pageResult!=null){
//如果结果不为空则,直接返回成功的数据
return ResponseEntity.ok(pageResult);
}
} catch (Exception e) {
//e.printStackTrace();
log.error("查询推荐用户列表出错~ token = " + token, e);
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
}
4.5、Service
package com.tanhua.server.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tanhua.dubbo.server.pojo.RecommendUser;
import com.tanhua.dubbo.server.vo.PageInfo;
import com.tanhua.server.pojo.User;
import com.tanhua.server.pojo.UserInfo;
import com.tanhua.server.vo.PageResult;
import com.tanhua.server.vo.RecommendUserQueryParam;
import com.tanhua.server.vo.TodayBest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.*;
/**
* 该类为今日佳人
* 功能模块的service层结构
*/
@Service
public class TodayBestService {
//注入UserService,为了校验token
@Autowired
private UserService userService;
//注入查询推荐用户业务层
@Autowired
private RecommendUserService recommendUserService;
//从配置文件重货获取默认用户的id
@Value("${tanhua.sso.default.user}")
private Long defaultUser;
//注入查询用户详细信息的service
@Autowired
private UserInfoService userInfoService;
public TodayBest queryTodayBest(String token) {
//调用userService层将token传递过去进行校验
//返回user
User user = this.userService.checkToken(token);
//判断user
if(null==user){
return null;
}
//如果不为空,就查询推荐用户
//将用户id传过去,返回推荐用户信息(去MongoDB里查数据)
TodayBest todayBest = this.recommendUserService.queryTodayBest(user.getId());
//判断结果
if(todayBest==null){
//如果为空的话,就给默认用户返回
todayBest = new TodayBest();
//将配置文件中默认的今日佳人的id封装到todayBest
todayBest.setId(defaultUser);
//设置缘分值,固定的,写死的
todayBest.setFateValue(85L);
}
//然后补全推荐用户的信息(去数据库中查)
//通过调用查询用户详细信息的service
//将推荐用户的id传递过去,返回userinfo信息
UserInfo userInfo = this.userInfoService.queryUserInfoByUserId(todayBest.getId());
//判断返回结果
if(userInfo==null){
//为空直接返回空
return null;
}
//不为空 userinfo的数据封装到todayBest中
todayBest.setAge(userInfo.getAge());
todayBest.setNickname(userInfo.getNickName());
//todayBest.setGender(userInfo.getSex().name().toLowerCase());
todayBest.setGender(userInfo.getSex().getValue() == 1 ? "man" : "woman");
todayBest.setAvatar(userInfo.getLogo());
todayBest.setTags(StringUtils.split(userInfo.getTags(),","));
//如果不为空
return todayBest;
}
/**
* 推荐列表的service层业务实现层方法
* @param token token
* @param queryParam 请求参数
* @return 查询结果
*/
public PageResult queryRecommendation(String token, RecommendUserQueryParam queryParam) {
//调用userService校验token
User user = this.userService.checkToken(token);
//判断user
if(user == null){
//为空则返回null
return null;
}
//创建默认的返回值
PageResult pageResult = new PageResult();
pageResult.setPage(queryParam.getPage());
pageResult.setPagesize(queryParam.getPagesize());
//如果不为空,就调用recommendUserService,
// 去mongodb里查询推荐用户列表
PageInfo<RecommendUser> pageInfo =this.recommendUserService.queryRecommendUserList(
user.getId(),
queryParam.getPage(),
queryParam.getPagesize()
);
//获取里面的用户数据
List<RecommendUser> records = pageInfo.getRecords();
//判断records
if(records==null){
//如果为空则返回默认的PageResult
return pageResult;
}
//如果不为空则 补全从mongodb查出来的用户列表信息
//创建map集合存储推荐用户的id,和缘分值
Map<Long,Long>idAndScore = new HashMap<>();
for (RecommendUser record : records) {
Double floor = Math.floor(record.getScore());
long score = floor.longValue();
idAndScore.put(record.getUserId(),score);
}
//创建查询条件
QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
//设置id集查询条件
wrapper.in("user_id",idAndScore.keySet());
//设置性别查询条件
//判断请求参数
if(StringUtils.isNotEmpty(queryParam.getGender())){
//如果用户传过来的请求参数中的性别不为空
//则再次判断请求参数中的性别
wrapper.eq("sex",StringUtils.equals(queryParam.getGender(),"man")?1:2);
}
//设置城市参数查询
if(StringUtils.isNotEmpty(queryParam.getCity())){
wrapper.like("city",queryParam.getCity());
}
//设置年龄查询条件
if(queryParam.getAge()!=null){
wrapper.le("age",queryParam.getAge());
}
List<UserInfo> userInfos = this.userInfoService.queryUserInfoList(wrapper);
//判断userInfos
if(userInfos==null){
return pageResult;
}
//如果不为空,则封装数据
List<TodayBest> result = new ArrayList<>();
//遍历userInfos,将userInfos添加到result中
for (UserInfo userInfo : userInfos) {
TodayBest best = new TodayBest();
best.setId(userInfo.getUserId());
best.setAvatar(userInfo.getLogo());
best.setNickname(userInfo.getNickName());
best.setTags(StringUtils.split(userInfo.getTags(),","));
best.setGender(userInfo.getSex().getValue() == 1 ? "man" : "woman");
best.setAge(userInfo.getAge());
//设置缘分值
for (Long id : idAndScore.keySet()) {
//判断id
if(id==userInfo.getUserId()){
Long score = idAndScore.get(id);
best.setFateValue(score);
break;
}
}
result.add(best);
}
//根据缘分值降序排序
Collections.sort(result, (o1, o2) -> new Long(o2.getFateValue() - o1.getFateValue()).intValue());
pageResult.setItems(result);
return pageResult;
}
}
//RecommendUserService
public PageInfo<RecommendUser> queryRecommendUserList(Long id, Integer page, Integer pagesize) {
return this.recommendUserApi.queryPageInfo(id, page, pagesize);
}
//UserInfoService
/**
* 查询用户信息列表
*
* @param queryWrapper
* @return
*/
public List<UserInfo> queryUserInfoList(QueryWrapper queryWrapper) {
return this.userInfoMapper.selectList(queryWrapper);
}
4.6、测试
5、缓存
在接口服务中,有必要对于接口进行缓存处理,尤其是GET请求,如果每个接口单独添加的话会存在很多的重复的逻辑,所以可以编写一套通用的解决方案。
实现思路:
- 通过拦截器实现对请求的拦截,在拦截器中实现缓存的命中。
- 通过ResponseBodyAdvice进行对响应的拦截,可以将数据缓存到Redis中。
- 考虑到,不能对于所有的请求都一刀切,所以需要创建@Cache注解进行标记,只有标记的Controller才进行缓存处理。
- 缓存的处理中,仅针对GET请求处理,其他的请求均不做处理。
5.1、自定义注解 (知识盲点:注解)
package com.tanhua.server.utils;
import java.lang.annotation.*;
/**
* 被标记为Cache的Controller进行缓存,
* 其他情况不进行缓存
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented //标记注解
public @interface Cache {
/**
* 缓存时间,默认为60秒
* @return
*/
String time() default "60";
}
5.2、采用拦截器进行缓存命中
编写拦截器:RedisCacheInterceptor。(知识盲点:HandlerInterceptor、@Component)
package com.tanhua.server.interceptor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tanhua.server.utils.Cache;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class RedisCacheInterceptor implements HandlerInterceptor {
//开启redis缓存机制的配置
@Value("${tanhua.cache.enable}")
private Boolean enable;
//redis工具类
@Autowired
private RedisTemplate<String, String> redisTemplate;
//json转换类
private static final ObjectMapper MAPPER = new ObjectMapper();
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
//缓存的全局开关的校验
if (!enable) {
//为enable为true就放行
return true;
}
//校验handler是否是HandlerMethod(表示为访问controller的方法)
if (!(handler instanceof HandlerMethod)) {
//不是HandlerMethod就放行
return true;
}
//判断是否为get请求
if (!((HandlerMethod) handler).hasMethodAnnotation(GetMapping.class)) {
//不是get请求就放行
return true;
}
//判断是否添加了@Cache注解
if (!((HandlerMethod) handler).hasMethodAnnotation(Cache.class)) {
//没有加Cache注解就放行
return true;
}
//没有满足以上的判断就进行缓存命中
//缓存命中
String redisKey = createRedisKey(request);
//通过键从redis中获取值
String cacheData = this.redisTemplate.opsForValue().get(redisKey);
//判断从redis中获取到的值
if(StringUtils.isEmpty(cacheData)){
//为空则表示缓存未命中,放行
return true;
}
// 如果不为空就将redis中查出的data数据进行响应
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(cacheData);
return false;
}
/**
* 该类为创建redis的key的方法 规则:SERVER_CACHE_DATA_MD5(url + param + token)
* @param request servlet的请求封装类
* @return redis的key
* @throws JsonProcessingException
*/
public static String createRedisKey(HttpServletRequest request) throws JsonProcessingException {
//获取url请求路径
String url = request.getRequestURI();
//通过json转换类将封装请求参数的map集合对象转换成json格式的字符串
String param = MAPPER.writeValueAsString(request.getParameterMap());
//获取token
String token = request.getHeader("Authorization");
//拼接key的规则
String data = url + "_" + param + "_" + token;
//加密并返回
return "SERVER_CACHE_DATA_" + DigestUtils.md5Hex(data);
}
/*@Value("${tanhua.cache.enable}")
private Boolean enable;
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final ObjectMapper MAPPER = new ObjectMapper();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//缓存的全局开关的校验
if (!enable) {
//为true表示放行
return true;
}
//校验handler是否是HandlerMethod(表示为访问controller的方法)
if (!(handler instanceof HandlerMethod)) {
return true;
}
//判断是否为get请求
if (!((HandlerMethod) handler).hasMethodAnnotation(GetMapping.class)) {
return true;
}
//判断是否添加了@Cache注解
if (!((HandlerMethod) handler).hasMethodAnnotation(Cache.class)) {
return true;
}
//缓存命中
String redisKey = createRedisKey(request);
String cacheData = this.redisTemplate.opsForValue().get(redisKey);
if(StringUtils.isEmpty(cacheData)){
//缓存未命中
return true;
}
// 将data数据进行响应
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(cacheData);
return false;
}
*//**
* 生成redis中的key,规则:SERVER_CACHE_DATA_MD5(url + param + token)
*
* @param request
* @return
*//*
public static String createRedisKey(HttpServletRequest request) throws Exception {
String url = request.getRequestURI();
String param = MAPPER.writeValueAsString(request.getParameterMap());
String token = request.getHeader("Authorization");
String data = url + "_" + param + "_" + token;
return "SERVER_CACHE_DATA_" + DigestUtils.md5Hex(data);
}*/
}
application.properties:
#是否开启数据缓存
tanhua.cache.enable=false
# Redis相关配置
spring.redis.jedis.pool.max-wait = 5000ms
spring.redis.jedis.pool.max-Idle = 100
spring.redis.jedis.pool.min-Idle = 10
spring.redis.timeout = 10s
spring.redis.cluster.nodes = 192.168.31.81:6379,192.168.31.81:6380,192.168.31.81:6381
spring.redis.cluster.max-redirects=5
注册拦截器到Spring容器:
package com.tanhua.server.config;
import com.tanhua.server.interceptor.RedisCacheInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 该类为拦截器的配置类(将我们写的RedisCacheInterceptor注入到IoC容器中)
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
//实现webMvcConfigurer接口
@Autowired
private RedisCacheInterceptor redisCacheInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//配置拦截的请求路径(拦截根路径下所有的请求)
registry.addInterceptor(this.redisCacheInterceptor).addPathPatterns("/*");
}
}
4.3、响应结果写入到缓存
使用ResponseBodyAdvice进行对响应结果处理,将结果写入到Redis中:
具体实现:
package com.tanhua.server.interceptor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tanhua.server.utils.Cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;
/**
* 该类为对响应结果进行写入redis的拦截器
*/
@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice {
//开启redis缓存机制的配置
@Value("${tanhua.cache.enable}")
private Boolean enable;
//json转换类
private static final ObjectMapper MAPPER = new ObjectMapper();
//redis工具类
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
//只要满足redis换出开关为开启状态,是get请求,访问的controller是加有
//自定义注解Cache的,就把响应结果写入redis缓存中
return enable && methodParameter.hasMethodAnnotation(GetMapping.class)
&& methodParameter.hasMethodAnnotation(Cache.class);
}
@Override
public Object beforeBodyWrite(Object o,
MethodParameter methodParameter,
MediaType mediaType,
Class aClass,
ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
//如果满足上面的三个条件就进入到这个方法中来
//先判断参数一Object是否为空
if(o==null){
//如果为空就返回空
return null;
}
//如果不为空就将结果写入到redis中
//准备数据
//处理结果数据
try {
String redisValue= null;
if(o instanceof String){
//如果o是String类型的则直接强转为String
redisValue = (String)o;
}else{
//如果不是则将对象转换成json格式的字符串
redisValue = MAPPER.writeValueAsString(o);
}
//处理键
//将ServerHttpRequest强转为ServletServerHttpRequest
ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) serverHttpRequest;
HttpServletRequest request = servletServerHttpRequest.getServletRequest();
String redisKey = RedisCacheInterceptor.createRedisKey(request);
//获取Cache注解
Cache annotation = methodParameter.getMethodAnnotation(Cache.class);
String timeStr = annotation.time();
Long time = Long.valueOf(timeStr);
//参数一为redis的key,参数二为redis的值,参数三为有效的存储时间,参数四为时间单位秒
this.redisTemplate.opsForValue().set(redisKey,redisValue,time, TimeUnit.SECONDS);
} catch (Exception e) {
//如果出了异常就不写了
e.printStackTrace();
}
return o;
}
}
4.4、测试
可以看到数据已经缓存到Redis中,并且其缓存时间也是30秒,与预期一致。
6、整合测试
测试时需要注意,由于用户数据较少,所以测试时需要把条件注释掉,否则查询不到数据:
效果: