课程说明
- 首页功能说明
- 系统架构说明
- 实现今日佳人功能
- 实现推荐用户的列表
- 接口增加缓存功能
- 整合前端联调测试
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-serverserver.port = 18081#数据库连接信息spring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.url=jdbc:mysql://192.168.31.81:3306/mytanhua?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=falsespring.datasource.username=rootspring.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-serverdubbo.registry.address = zookeeper://192.168.31.81:2181dubbo.registry.client = zkclientdubbo.registry.timeout = 60000dubbo.consumer.timeout = 60000#开启缓存服务tanhua.cache.enable=false# Redis相关配置spring.redis.jedis.pool.max-wait = 5000msspring.redis.jedis.pool.max-Idle = 100spring.redis.jedis.pool.min-Idle = 10spring.redis.timeout = 10sspring.redis.cluster.nodes = 192.168.31.81:6379,192.168.31.81:6380,192.168.31.81:6381spring.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接口的扫描包@SpringBootApplicationpublic 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 applicationspring.application.name = xiaoha-tanhua-dubbo-service# dubbo 扫描包配置dubbo.scan.basePackages = com.tanhua.dubbo.serverdubbo.application.name = dubbo-provider-tanhua#dubbo 对外暴露的端口信息dubbo.protocol.name = dubbodubbo.protocol.port = 20880#dubbo注册中心的配置dubbo.registry.address = zookeeper://192.168.31.81:2181dubbo.registry.client = zkclientdubbo.registry.timeout = 60000dubbo.provider.host= 192.168.31.1#springboot MongoDB配置spring.data.mongodb.username=tanhuaspring.data.mongodb.password=l3SCjl0HvmSkTtiSbN0Swv40spYnHhDVspring.data.mongodb.authentication-database=adminspring.data.mongodb.database=tanhuaspring.data.mongodb.port=27017spring.data.mongodb.host=192.168.31.81
编写启动类:
package com.tanhua.dubbo.server;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic 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;@Idprivate ObjectId id; //主键id@Indexedprivate Long userId; //推荐的用户idprivate Long toUserId; //用户id@Indexedprivate 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@AllArgsConstructorpublic 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的模本类@Autowiredprivate MongoTemplate mongoTemplate;/*** 查询单个最高分数* 的被推荐用户* @param userId 被推荐用户的id* @return*/@Overridepublic RecommendUser queryWithMaxScore(Long userId) {//查询条件方法,// 通过用户id(toUserId)作为条件,//查询被推荐用户的id(userId)//查询条件相当于where toUserId = userIdQuery 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;}@Overridepublic 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)@SpringBootTestpublic class RecommendUserApiTest {@Autowiredprivate RecommendUserApi recommendUserApi;@Testpublic void findOneTest(){System.out.println("结果一:"+this.recommendUserApi.queryWithMaxScore(1L));System.out.println("结果二:"+this.recommendUserApi.queryWithMaxScore(8L));System.out.println("结果三:"+this.recommendUserApi.queryWithMaxScore(26L));}@Testpublic 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;}@Overridepublic Integer getValue() {return this.value;}@Overridepublic 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; //手机号@JsonIgnoreprivate 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@AllArgsConstructorpublic class UserInfo extends BasePojo {private Long id;private Long userId; //用户idprivate 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@AllArgsConstructorpublic class TodayBest {private Long id;private String avatar; //头像private String nickname; //昵称private String gender; //性别 man womanprivate 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层@Autowiredprivate 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);//判断todayBestif(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")@Cachepublic ResponseEntity<PageResult> queryRecommendation(@RequestHeader("Authorization") String token ,RecommendUserQueryParam queryParam){try {//调用今日佳人模块的service层,将token和请求参数传过去//返回pageResultPageResult 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层结构*/@Servicepublic class TodayBestService {//注入UserService,为了校验token@Autowiredprivate UserService userService;//注入查询推荐用户业务层@Autowiredprivate RecommendUserService recommendUserService;//从配置文件重货获取默认用户的id@Value("${tanhua.sso.default.user}")private Long defaultUser;//注入查询用户详细信息的service@Autowiredprivate UserInfoService userInfoService;public TodayBest queryTodayBest(String token) {//调用userService层将token传递过去进行校验//返回userUser user = this.userService.checkToken(token);//判断userif(null==user){return null;}//如果不为空,就查询推荐用户//将用户id传过去,返回推荐用户信息(去MongoDB里查数据)TodayBest todayBest = this.recommendUserService.queryTodayBest(user.getId());//判断结果if(todayBest==null){//如果为空的话,就给默认用户返回todayBest = new TodayBest();//将配置文件中默认的今日佳人的id封装到todayBesttodayBest.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校验tokenUser user = this.userService.checkToken(token);//判断userif(user == null){//为空则返回nullreturn 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();//判断recordsif(records==null){//如果为空则返回默认的PageResultreturn 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);//判断userInfosif(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()) {//判断idif(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@Slf4jpublic class UserService {//注入发送rest请求的模板类@Autowiredprivate 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){//如果为空直接返回nullreturn 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对象的配置类*/@Configurationpublic class RestTemplateConfig {@Beanpublic RestTemplate restTemplate(ClientHttpRequestFactory factory) {RestTemplate restTemplate = new RestTemplate(factory);// 支持中文编码restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(Charset.forName("UTF-8")));return restTemplate;}@Beanpublic ClientHttpRequestFactory simpleClientHttpRequestFactory() {SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();factory.setReadTimeout(5000);//单位为msfactory.setConnectTimeout(5000);//单位为msreturn 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层*/@Servicepublic 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;}//如果不为空则正常返回todayBestTodayBest 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*/@Servicepublic class UserInfoService {//注入mapper@Autowiredprivate 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 {@Autowiredprivate RecommendUserService recommendUserService;@Testpublic void findRecommendUser(){TodayBest todayBest = this.recommendUserService.queryTodayBest(100L);System.out.println(todayBest);}@Testpublic 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@AllArgsConstructorpublic class RecommendUserQueryParam {private Integer page = 1; //当前页数private Integer pagesize = 10; //页尺寸private String gender; //性别 man womanprivate 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@NoArgsConstructorpublic 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层@Autowiredprivate 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);//判断todayBestif(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")@Cachepublic ResponseEntity<PageResult> queryRecommendation(@RequestHeader("Authorization") String token ,RecommendUserQueryParam queryParam){try {//调用今日佳人模块的service层,将token和请求参数传过去//返回pageResultPageResult 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层结构*/@Servicepublic class TodayBestService {//注入UserService,为了校验token@Autowiredprivate UserService userService;//注入查询推荐用户业务层@Autowiredprivate RecommendUserService recommendUserService;//从配置文件重货获取默认用户的id@Value("${tanhua.sso.default.user}")private Long defaultUser;//注入查询用户详细信息的service@Autowiredprivate UserInfoService userInfoService;public TodayBest queryTodayBest(String token) {//调用userService层将token传递过去进行校验//返回userUser user = this.userService.checkToken(token);//判断userif(null==user){return null;}//如果不为空,就查询推荐用户//将用户id传过去,返回推荐用户信息(去MongoDB里查数据)TodayBest todayBest = this.recommendUserService.queryTodayBest(user.getId());//判断结果if(todayBest==null){//如果为空的话,就给默认用户返回todayBest = new TodayBest();//将配置文件中默认的今日佳人的id封装到todayBesttodayBest.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校验tokenUser user = this.userService.checkToken(token);//判断userif(user == null){//为空则返回nullreturn 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();//判断recordsif(records==null){//如果为空则返回默认的PageResultreturn 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);//判断userInfosif(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()) {//判断idif(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;}}
//RecommendUserServicepublic 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;@Componentpublic class RedisCacheInterceptor implements HandlerInterceptor {//开启redis缓存机制的配置@Value("${tanhua.cache.enable}")private Boolean enable;//redis工具类@Autowiredprivate RedisTemplate<String, String> redisTemplate;//json转换类private static final ObjectMapper MAPPER = new ObjectMapper();@Overridepublic 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());//获取tokenString token = request.getHeader("Authorization");//拼接key的规则String data = url + "_" + param + "_" + token;//加密并返回return "SERVER_CACHE_DATA_" + DigestUtils.md5Hex(data);}/*@Value("${tanhua.cache.enable}")private Boolean enable;@Autowiredprivate RedisTemplate<String, String> redisTemplate;private static final ObjectMapper MAPPER = new ObjectMapper();@Overridepublic 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 = 5000msspring.redis.jedis.pool.max-Idle = 100spring.redis.jedis.pool.min-Idle = 10spring.redis.timeout = 10sspring.redis.cluster.nodes = 192.168.31.81:6379,192.168.31.81:6380,192.168.31.81:6381spring.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容器中)*/@Configurationpublic class WebConfig implements WebMvcConfigurer {//实现webMvcConfigurer接口@Autowiredprivate RedisCacheInterceptor redisCacheInterceptor;@Overridepublic 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的拦截器*/@ControllerAdvicepublic class MyResponseBodyAdvice implements ResponseBodyAdvice {//开启redis缓存机制的配置@Value("${tanhua.cache.enable}")private Boolean enable;//json转换类private static final ObjectMapper MAPPER = new ObjectMapper();//redis工具类@Autowiredprivate RedisTemplate<String, String> redisTemplate;@Overridepublic boolean supports(MethodParameter methodParameter, Class aClass) {//只要满足redis换出开关为开启状态,是get请求,访问的controller是加有//自定义注解Cache的,就把响应结果写入redis缓存中return enable && methodParameter.hasMethodAnnotation(GetMapping.class)&& methodParameter.hasMethodAnnotation(Cache.class);}@Overridepublic 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类型的则直接强转为StringredisValue = (String)o;}else{//如果不是则将对象转换成json格式的字符串redisValue = MAPPER.writeValueAsString(o);}//处理键//将ServerHttpRequest强转为ServletServerHttpRequestServletServerHttpRequest 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、整合测试
测试时需要注意,由于用户数据较少,所以测试时需要把条件注释掉,否则查询不到数据:
效果:

