课程说明

  • 首页功能说明
  • 系统架构说明
  • 实现今日佳人功能
  • 实现推荐用户的列表
  • 接口增加缓存功能
  • 整合前端联调测试

1、首页

在用户登录成功后,就会进入首页,首页中有今日佳人、推荐好友、探花、搜附近等功能。
1564104604248.png

2、系统架构

在开发完SSO系统中的登录功能后,接下来就需要实现其他的功能,在整体架构中,完成与APP对接的服务工程叫my-tanhua-server,真正的核心业务逻辑使用dubbo完成,其工程名叫:my-tanhua-dubbo,它们的架构示意图如下:
image-20201130102009217.png

说明:

  • 客户端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 启动:
1566999483561.png

重启加载配置文件命令:nginx.exe -s reload
1566999546104.png
关掉nginx的命令:nginx -s stop

2.1.2、配置

修改conf目录下的nginx.conf文件:

  1. #user nobody;
  2. worker_processes 1;
  3. #error_log logs/error.log;
  4. #error_log logs/error.log notice;
  5. #error_log logs/error.log info;
  6. #pid logs/nginx.pid;
  7. events {
  8. worker_connections 1024;
  9. }
  10. http {
  11. include mime.types;
  12. default_type application/octet-stream;
  13. #log_format main '$remote_addr - $remote_user [$time_local] "$request" '
  14. # '$status $body_bytes_sent "$http_referer" '
  15. # '"$http_user_agent" "$http_x_forwarded_for"';
  16. #access_log logs/access.log main;
  17. sendfile on;
  18. #tcp_nopush on;
  19. #keepalive_timeout 0;
  20. keepalive_timeout 65;
  21. #gzip on;
  22. server {
  23. listen 80;
  24. server_name localhost;
  25. #charset koi8-r;
  26. #access_log logs/host.access.log main;
  27. #error_page 404 /404.html;
  28. # redirect server error pages to the static page /50x.html
  29. #
  30. error_page 500 502 503 504 /50x.html;
  31. location = /50x.html {
  32. root html;
  33. }
  34. location /user/ { #请求路径中凡是以/user/开头的请求,转发到sso系统
  35. client_max_body_size 300m; #设置最大的请求体大小,解决大文件上传不了的问题
  36. proxy_connect_timeout 300s; #代理连接超时时间
  37. proxy_send_timeout 300s; #代理发送数据的超时时间
  38. proxy_read_timeout 300s; #代理读取数据的超时时间
  39. proxy_pass http://127.0.0.1:18080; #转发请求
  40. }
  41. location / { #上面未匹配到的在这里处理
  42. client_max_body_size 300m;
  43. proxy_connect_timeout 300s;
  44. proxy_send_timeout 300s;
  45. proxy_read_timeout 300s;
  46. proxy_pass http://127.0.0.1:18081; #转发请求到server系统
  47. }
  48. }
  49. }

2.1.3、测试

image-20201130111356817.png

2.2、搭建server工程

2.2.1、导入依赖

pom.xml文件:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>my-tanhua</artifactId>
  7. <groupId>cn.xiaoha.tanhua</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>my-tanhua-server</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>cn.xiaoha.tanhua</groupId>
  15. <artifactId>my-tanhua-dubbo-interface</artifactId>
  16. <version>1.0-SNAPSHOT</version>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework.boot</groupId>
  20. <artifactId>spring-boot-starter-web</artifactId>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.springframework.boot</groupId>
  24. <artifactId>spring-boot-starter-test</artifactId>
  25. <scope>test</scope>
  26. </dependency>
  27. <dependency>
  28. <groupId>org.springframework.boot</groupId>
  29. <artifactId>spring-boot-starter-data-redis</artifactId>
  30. </dependency>
  31. <!--dubbo的springboot支持-->
  32. <dependency>
  33. <groupId>com.alibaba.boot</groupId>
  34. <artifactId>dubbo-spring-boot-starter</artifactId>
  35. </dependency>
  36. <!--dubbo框架-->
  37. <dependency>
  38. <groupId>com.alibaba</groupId>
  39. <artifactId>dubbo</artifactId>
  40. </dependency>
  41. <!--zk依赖-->
  42. <dependency>
  43. <groupId>org.apache.zookeeper</groupId>
  44. <artifactId>zookeeper</artifactId>
  45. </dependency>
  46. <dependency>
  47. <groupId>com.github.sgroschupf</groupId>
  48. <artifactId>zkclient</artifactId>
  49. </dependency>
  50. <dependency>
  51. <groupId>org.apache.commons</groupId>
  52. <artifactId>commons-lang3</artifactId>
  53. </dependency>
  54. <dependency>
  55. <groupId>org.apache.commons</groupId>
  56. <artifactId>commons-collections4</artifactId>
  57. <version>4.4</version>
  58. </dependency>
  59. <dependency>
  60. <groupId>com.baomidou</groupId>
  61. <artifactId>mybatis-plus</artifactId>
  62. </dependency>
  63. <dependency>
  64. <groupId>com.baomidou</groupId>
  65. <artifactId>mybatis-plus-boot-starter</artifactId>
  66. </dependency>
  67. <dependency>
  68. <groupId>mysql</groupId>
  69. <artifactId>mysql-connector-java</artifactId>
  70. </dependency>
  71. <dependency>
  72. <groupId>commons-io</groupId>
  73. <artifactId>commons-io</artifactId>
  74. <version>2.6</version>
  75. </dependency>
  76. <dependency>
  77. <groupId>commons-codec</groupId>
  78. <artifactId>commons-codec</artifactId>
  79. </dependency>
  80. <dependency>
  81. <groupId>com.alibaba.spring</groupId>
  82. <artifactId>spring-context-support</artifactId>
  83. <version>1.0.2</version>
  84. </dependency>
  85. </dependencies>
  86. </project>

2.2.2、application.properties

  1. spring.application.name = itcast-tanhua-server
  2. server.port = 18081
  3. #数据库连接信息
  4. spring.datasource.driver-class-name=com.mysql.jdbc.Driver
  5. spring.datasource.url=jdbc:mysql://192.168.31.81:3306/mytanhua?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
  6. spring.datasource.username=root
  7. spring.datasource.password=root
  8. # 枚举包扫描
  9. mybatis-plus.type-enums-package=com.tanhua.server.enums
  10. # 表名前缀(mybatis-plus)
  11. mybatis-plus.global-config.db-config.table-prefix=tb_
  12. # id策略为自增长(mybatis-plus)
  13. mybatis-plus.global-config.db-config.id-type=auto
  14. #dubbo注册中心配置
  15. dubbo.application.name = itcast-tanhua-server
  16. dubbo.registry.address = zookeeper://192.168.31.81:2181
  17. dubbo.registry.client = zkclient
  18. dubbo.registry.timeout = 60000
  19. dubbo.consumer.timeout = 60000
  20. #开启缓存服务
  21. tanhua.cache.enable=false
  22. # Redis相关配置
  23. spring.redis.jedis.pool.max-wait = 5000ms
  24. spring.redis.jedis.pool.max-Idle = 100
  25. spring.redis.jedis.pool.min-Idle = 10
  26. spring.redis.timeout = 10s
  27. spring.redis.cluster.nodes = 192.168.31.81:6379,192.168.31.81:6380,192.168.31.81:6381
  28. spring.redis.cluster.max-redirects=5
  29. #sso系统服务地址
  30. tanhua.sso.url=http://127.0.0.1
  31. #默认今日佳人推荐用户
  32. tanhua.sso.default.user=2

2.2.3、ServerApplication

  1. package com.tanhua.server;
  2. import org.mybatis.spring.annotation.MapperScan;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. @MapperScan("com.tanhua.server.mapper") //设置mapper接口的扫描包
  6. @SpringBootApplication
  7. public class ServerApplication {
  8. public static void main(String[] args) {
  9. SpringApplication.run(ServerApplication.class, args);
  10. }
  11. }

2.3、搭建dubbo工程

my-tanhua-dubbo是dubbo工程的父工程:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>my-tanhua</artifactId>
  7. <groupId>cn.xiaoha.tanhua</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>my-tanhua-dubbo</artifactId>
  12. <packaging>pom</packaging>
  13. <modules>
  14. <module>my-tanhua-dubbo-interface</module>
  15. <module>my-tanhua-dubbo-service</module>
  16. </modules>
  17. </project>

2.3.1、创建my-tanhua-dubbo-interface工程

该工程中定义了dubbo服务中的interface与实体对象。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>my-tanhua-dubbo</artifactId>
  7. <groupId>cn.xiaoha.tanhua</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>my-tanhua-dubbo-interface</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>org.projectlombok</groupId>
  15. <artifactId>lombok</artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework.boot</groupId>
  19. <artifactId>spring-boot-starter-data-mongodb</artifactId>
  20. </dependency>
  21. </dependencies>
  22. </project>

2.3.2、创建my-tanhua-dubbo-service工程

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>my-tanhua-dubbo</artifactId>
  7. <groupId>cn.xiaoha.tanhua</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>my-tanhua-dubbo-service</artifactId>
  12. <dependencies>
  13. <!--引入interface依赖-->
  14. <dependency>
  15. <groupId>cn.xiaoha.tanhua</groupId>
  16. <artifactId>my-tanhua-dubbo-interface</artifactId>
  17. <version>1.0-SNAPSHOT</version>
  18. </dependency>
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-starter</artifactId>
  22. </dependency>
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-starter-test</artifactId>
  26. <scope>test</scope>
  27. </dependency>
  28. <!--dubbo的springboot支持-->
  29. <dependency>
  30. <groupId>com.alibaba.boot</groupId>
  31. <artifactId>dubbo-spring-boot-starter</artifactId>
  32. </dependency>
  33. <!--dubbo框架-->
  34. <dependency>
  35. <groupId>com.alibaba</groupId>
  36. <artifactId>dubbo</artifactId>
  37. </dependency>
  38. <dependency>
  39. <groupId>com.alibaba.spring</groupId>
  40. <artifactId>spring-context-support</artifactId>
  41. <version>1.0.2</version>
  42. </dependency>
  43. <!--zk依赖-->
  44. <dependency>
  45. <groupId>org.apache.zookeeper</groupId>
  46. <artifactId>zookeeper</artifactId>
  47. </dependency>
  48. <dependency>
  49. <groupId>com.github.sgroschupf</groupId>
  50. <artifactId>zkclient</artifactId>
  51. </dependency>
  52. <!--MongoDB相关依赖-->
  53. <dependency>
  54. <groupId>org.springframework.boot</groupId>
  55. <artifactId>spring-boot-starter-data-mongodb</artifactId>
  56. </dependency>
  57. <dependency>
  58. <groupId>org.mongodb</groupId>
  59. <artifactId>mongodb-driver-sync</artifactId>
  60. </dependency>
  61. <!--其他工具包依赖-->
  62. <dependency>
  63. <groupId>org.apache.commons</groupId>
  64. <artifactId>commons-lang3</artifactId>
  65. </dependency>
  66. <dependency>
  67. <groupId>joda-time</groupId>
  68. <artifactId>joda-time</artifactId>
  69. </dependency>
  70. <dependency>
  71. <groupId>io.netty</groupId>
  72. <artifactId>netty-all</artifactId>
  73. </dependency>
  74. </dependencies>
  75. </project>

dubbo-service中的application.properties:

  1. # Spring boot application
  2. spring.application.name = xiaoha-tanhua-dubbo-service
  3. # dubbo 扫描包配置
  4. dubbo.scan.basePackages = com.tanhua.dubbo.server
  5. dubbo.application.name = dubbo-provider-tanhua
  6. #dubbo 对外暴露的端口信息
  7. dubbo.protocol.name = dubbo
  8. dubbo.protocol.port = 20880
  9. #dubbo注册中心的配置
  10. dubbo.registry.address = zookeeper://192.168.31.81:2181
  11. dubbo.registry.client = zkclient
  12. dubbo.registry.timeout = 60000
  13. dubbo.provider.host= 192.168.31.1
  14. #springboot MongoDB配置
  15. spring.data.mongodb.username=tanhua
  16. spring.data.mongodb.password=l3SCjl0HvmSkTtiSbN0Swv40spYnHhDV
  17. spring.data.mongodb.authentication-database=admin
  18. spring.data.mongodb.database=tanhua
  19. spring.data.mongodb.port=27017
  20. spring.data.mongodb.host=192.168.31.81

编写启动类:

  1. package com.tanhua.dubbo.server;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class DubboApplication {
  6. public static void main(String[] args) {
  7. SpringApplication.run(DubboApplication.class, args);
  8. }
  9. }

2.4、工程结构

最终搭建完成的效果如下:
image-20201130120238951.png

3、今日佳人

今日佳人,会推荐缘分值最大的用户,进行展现出来。缘分值的计算是由用户的行为进行打分,如:点击、点赞、评论、学历、婚姻状态等信息组合而成的。

实现:我们先不考虑推荐的逻辑,假设现在已经有推荐的结果,我们只需要从结果中查询到缘分值最高的用户就可以了。至于推荐的逻辑以及实现,我们将后面的课程中讲解。

流程:
image-20201130150720621.png

3.1、表结构

  1. #表结构,表名:recommend_user
  2. {
  3. "userId":1001, #推荐的用户id
  4. "toUserId":1002, #用户id
  5. "score":90, #推荐得分
  6. "date":"2019/1/1" #日期
  7. }

已经提供的测试数据(4855条数据):
image-20201130151126971.png

3.2、编写dubbo服务

3.2.1、编写接口

在my-tanhua-dubbo-interface工程中定义接口:

  1. package com.tanhua.dubbo.server.api;
  2. import com.tanhua.dubbo.server.pojo.RecommendUser;
  3. import com.tanhua.dubbo.server.vo.PageInfo;
  4. public interface RecommendUserApi {
  5. /**
  6. * 查询一位得分
  7. * 最高的推荐用户
  8. *
  9. * @param userId 推荐用户的id
  10. * @return 封装表数据的映射实体类
  11. */
  12. RecommendUser queryWithMaxScore(Long userId);
  13. /**
  14. * 按照得分倒序
  15. *
  16. * @param userId 推荐用户的id
  17. * @param pageNum 当前页数
  18. * @param pageSize 当前页所显示的数据条数
  19. * @return 封装分页查询的结果类
  20. */
  21. PageInfo<RecommendUser> queryPageInfo(Long userId, Integer pageNum, Integer pageSize);
  22. }
  1. package com.tanhua.dubbo.server.pojo;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. import org.bson.types.ObjectId;
  6. import org.springframework.data.annotation.Id;
  7. import org.springframework.data.mongodb.core.index.Indexed;
  8. import org.springframework.data.mongodb.core.mapping.Document;
  9. import java.io.Serializable;
  10. @Data //生成javabean类set、get等方法的注解
  11. @NoArgsConstructor //生成无参构造方法的注解
  12. @AllArgsConstructor //生成全参构造方法的注解
  13. @Document(collection = "recommend_user") //指定MongoDB的表
  14. public class RecommendUser implements Serializable {
  15. //该类实现serializable接口便于序列化通过流传输
  16. //设置序列化的id号;
  17. private static final long serialVersionUID = -4296017160071130962L;
  18. @Id
  19. private ObjectId id; //主键id
  20. @Indexed
  21. private Long userId; //推荐的用户id
  22. private Long toUserId; //用户id
  23. @Indexed
  24. private Double score; //推荐得分
  25. private String date; //日期
  26. }
  1. package com.tanhua.dubbo.server.vo;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import java.io.Serializable;
  5. import java.util.Collections;
  6. import java.util.List;
  7. @Data
  8. @AllArgsConstructor
  9. public class PageInfo<T> implements Serializable {
  10. //该类实现serializable接口便于序列化通过流传输
  11. //设置序列化的id号;
  12. private static final long serialVersionUID = -2105385689859184204L;
  13. /**
  14. * 总条数
  15. */
  16. private Integer total = 0;
  17. /**
  18. * 当前页
  19. */
  20. private Integer pageNum = 0;
  21. /**
  22. * 一页显示的大小
  23. */
  24. private Integer pageSize = 0;
  25. /**
  26. * 数据列表
  27. */
  28. private List<T> records = Collections.emptyList();
  29. }

3.2.2、编写实现

  1. package com.tanhua.dubbo.server.api;
  2. import com.alibaba.dubbo.config.annotation.Service;
  3. import com.tanhua.dubbo.server.pojo.RecommendUser;
  4. import com.tanhua.dubbo.server.vo.PageInfo;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.data.domain.PageRequest;
  7. import org.springframework.data.domain.Sort;
  8. import org.springframework.data.mongodb.core.MongoTemplate;
  9. import org.springframework.data.mongodb.core.query.Criteria;
  10. import org.springframework.data.mongodb.core.query.Query;
  11. import java.util.List;
  12. @Service(version = "1.0.0") //指定该类是一个dubbo服务
  13. public class RecommendUserApiImpl implements RecommendUserApi {
  14. //注入MongoDB的模本类
  15. @Autowired
  16. private MongoTemplate mongoTemplate;
  17. /**
  18. * 查询单个最高分数
  19. * 的被推荐用户
  20. * @param userId 被推荐用户的id
  21. * @return
  22. */
  23. @Override
  24. public RecommendUser queryWithMaxScore(Long userId) {
  25. //查询条件方法,
  26. // 通过用户id(toUserId)作为条件,
  27. //查询被推荐用户的id(userId)
  28. //查询条件相当于where toUserId = userId
  29. Query queryTemp = Query.query(Criteria
  30. .where("toUserId")
  31. .is(userId));
  32. // 然后再根据分数降序排序
  33. Query query = queryTemp.with(Sort.by(Sort.Order.desc("score"))).limit(1);
  34. //mongoTemplate提供的查询一个数据的方法
  35. //参数一为查询条件,参数二为返回指定类的
  36. // 字节码对象
  37. RecommendUser one = this.mongoTemplate.findOne(query, RecommendUser.class);
  38. return one;
  39. }
  40. @Override
  41. public PageInfo<RecommendUser> queryPageInfo(Long userId, Integer pageNum, Integer pageSize) {
  42. //进行分页处理
  43. PageRequest page = PageRequest.of(pageNum - 1,
  44. pageSize,
  45. Sort.by(Sort.Order.desc("score")));
  46. //查询条件方法,
  47. // 通过用户id(toUserId)作为条件,
  48. //查询被推荐用户的id(userId)
  49. Query queryTemp = Query.query(Criteria.where("toUserId").is(userId));
  50. Query query = queryTemp.with(page);
  51. //mongoTemplate提供的查询一个数据的方法
  52. //参数一为查询条件,参数二为返回指定类的
  53. // 字节码对象
  54. List<RecommendUser> pageInfos = this.mongoTemplate.find(query, RecommendUser.class);
  55. return new PageInfo(0,pageNum,pageSize,pageInfos);
  56. }
  57. }

3.2.3、测试

注意:如果在测试时出现以下错误,将采取下面的措施。错误笔记.png

  1. package com.tanhua.dubbo.server.api;
  2. import org.junit.Test;
  3. import org.junit.runner.RunWith;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.boot.test.context.SpringBootTest;
  6. import org.springframework.test.context.junit4.SpringRunner;
  7. @RunWith(SpringRunner.class)
  8. @SpringBootTest
  9. public class RecommendUserApiTest {
  10. @Autowired
  11. private RecommendUserApi recommendUserApi;
  12. @Test
  13. public void findOneTest(){
  14. System.out.println("结果一:"+this.recommendUserApi.queryWithMaxScore(1L));
  15. System.out.println("结果二:"+this.recommendUserApi.queryWithMaxScore(8L));
  16. System.out.println("结果三:"+this.recommendUserApi.queryWithMaxScore(26L));
  17. }
  18. @Test
  19. public void findPageTest(){
  20. System.out.println(this.recommendUserApi.queryPageInfo(1L, 1, 5));
  21. }
  22. }

3.3、实现今日佳人服务

3.3.1、mock服务

地址:https://mock-java.itheima.net/project/35/interface/api/617
image-20201130152808705.png1566915288334.png

3.3.2、基础代码

3.3.2.1、SexEnum
  1. package com.tanhua.server.enums;
  2. import com.baomidou.mybatisplus.core.enums.IEnum;
  3. public enum SexEnum implements IEnum<Integer> {
  4. MAN(1,"男"),
  5. WOMAN(2,"女"),
  6. UNKNOWN(3,"未知");
  7. private int value;
  8. private String desc;
  9. SexEnum(int value, String desc) {
  10. this.value = value;
  11. this.desc = desc;
  12. }
  13. @Override
  14. public Integer getValue() {
  15. return this.value;
  16. }
  17. @Override
  18. public String toString() {
  19. return this.desc;
  20. }
  21. }

3.3.2.2、BasePojo
  1. package com.tanhua.server.pojo;
  2. import com.baomidou.mybatisplus.annotation.FieldFill;
  3. import com.baomidou.mybatisplus.annotation.TableField;
  4. import java.util.Date;
  5. public abstract class BasePojo {
  6. @TableField(fill = FieldFill.INSERT)
  7. private Date created;
  8. @TableField(fill = FieldFill.INSERT_UPDATE)
  9. private Date updated;
  10. }

3.3.2.3、User
  1. package com.tanhua.server.pojo;
  2. import com.fasterxml.jackson.annotation.JsonIgnore;
  3. import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
  4. import lombok.AllArgsConstructor;
  5. import lombok.Data;
  6. import lombok.NoArgsConstructor;
  7. @Data
  8. @NoArgsConstructor
  9. @AllArgsConstructor
  10. @JsonIgnoreProperties(ignoreUnknown = true)
  11. public class User extends BasePojo {
  12. private Long id;
  13. private String mobile; //手机号
  14. @JsonIgnore
  15. private String password; //密码,json序列化时忽略
  16. }

3.3.2.4、UserInfo
  1. package com.tanhua.server.pojo;
  2. import com.tanhua.server.enums.SexEnum;
  3. import lombok.AllArgsConstructor;
  4. import lombok.Data;
  5. import lombok.NoArgsConstructor;
  6. @Data
  7. @NoArgsConstructor
  8. @AllArgsConstructor
  9. public class UserInfo extends BasePojo {
  10. private Long id;
  11. private Long userId; //用户id
  12. private String nickName; //昵称
  13. private String logo; //用户头像
  14. private String tags; //用户标签:多个用逗号分隔
  15. private SexEnum sex; //性别
  16. private Integer age; //年龄
  17. private String edu; //学历
  18. private String city; //城市
  19. private String birthday; //生日
  20. private String coverPic; // 封面图片
  21. private String industry; //行业
  22. private String income; //收入
  23. private String marriage; //婚姻状态
  24. }

3.3.3、实现功能

实现描述:

  • 需要根据前端定义的结构定义java对象
  • 根据sso系统提供的接口查询当前登录用户的信息
  • 根据dubbo系统提供的服务进行查询今日佳人数据

3.3.3.1、TodayBest
  1. package com.tanhua.server.vo;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. /**
  6. * 返回数据结果的封装类
  7. * 今日佳人
  8. */
  9. @Data
  10. @NoArgsConstructor
  11. @AllArgsConstructor
  12. public class TodayBest {
  13. private Long id;
  14. private String avatar; //头像
  15. private String nickname; //昵称
  16. private String gender; //性别 man woman
  17. private Integer age; //年龄
  18. private String[] tags; //标签
  19. private Long fateValue; //缘分值
  20. }

3.3.3.2、TodayBestController
  1. package com.tanhua.server.controller;
  2. import com.tanhua.server.service.TodayBestService;
  3. import com.tanhua.server.utils.Cache;
  4. import com.tanhua.server.vo.PageResult;
  5. import com.tanhua.server.vo.RecommendUserQueryParam;
  6. import com.tanhua.server.vo.TodayBest;
  7. import lombok.extern.slf4j.Slf4j;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.http.HttpStatus;
  10. import org.springframework.http.ResponseEntity;
  11. import org.springframework.web.bind.annotation.GetMapping;
  12. import org.springframework.web.bind.annotation.RequestHeader;
  13. import org.springframework.web.bind.annotation.RequestMapping;
  14. import org.springframework.web.bind.annotation.RestController;
  15. /**
  16. * 该类为今日佳人功能模块的
  17. * Controller层结构
  18. */
  19. @RestController
  20. @RequestMapping("tanhua")
  21. @Slf4j //打印日志的标签
  22. public class TodayBestController {
  23. //注入service层
  24. @Autowired
  25. private TodayBestService todayBestService;
  26. /**
  27. * 接收用户传来
  28. * 的token的方法
  29. *
  30. * @param token 用户传来的token
  31. * @return 返回今日佳人的信息数据
  32. */
  33. @GetMapping("todayBest")
  34. public ResponseEntity<TodayBest> queryTodayBest(@RequestHeader("Authorization")
  35. String token){
  36. try {
  37. //调用service层将token传递过去
  38. TodayBest todayBest= this.todayBestService.queryTodayBest(token);
  39. //判断todayBest
  40. if(null!=todayBest){
  41. //如果结果不为空,直接返回数据结果
  42. return ResponseEntity.ok(todayBest);
  43. }
  44. } catch (Exception e) {
  45. //e.printStackTrace();
  46. log.error("查询出错,",token,e);
  47. }
  48. //如果为空则返回空
  49. return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
  50. }
  51. /**
  52. * 推荐列表功能模块
  53. *
  54. * @param token 传过来的token
  55. * @param queryParam 请求参数封装类对象
  56. * @return 查询出来的推荐用户列表数据
  57. */
  58. @GetMapping("recommendation")
  59. @Cache
  60. public ResponseEntity<PageResult> queryRecommendation(@RequestHeader("Authorization") String token ,
  61. RecommendUserQueryParam queryParam){
  62. try {
  63. //调用今日佳人模块的service层,将token和请求参数传过去
  64. //返回pageResult
  65. PageResult pageResult = this.todayBestService.queryRecommendation(token,queryParam);
  66. //判断
  67. if(pageResult!=null){
  68. //如果结果不为空则,直接返回成功的数据
  69. return ResponseEntity.ok(pageResult);
  70. }
  71. } catch (Exception e) {
  72. //e.printStackTrace();
  73. log.error("查询推荐用户列表出错~ token = " + token, e);
  74. }
  75. return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
  76. }
  77. }

3.3.3.3、TodayBestService
  1. package com.tanhua.server.service;
  2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  3. import com.tanhua.dubbo.server.pojo.RecommendUser;
  4. import com.tanhua.dubbo.server.vo.PageInfo;
  5. import com.tanhua.server.pojo.User;
  6. import com.tanhua.server.pojo.UserInfo;
  7. import com.tanhua.server.vo.PageResult;
  8. import com.tanhua.server.vo.RecommendUserQueryParam;
  9. import com.tanhua.server.vo.TodayBest;
  10. import org.apache.commons.lang3.StringUtils;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.beans.factory.annotation.Value;
  13. import org.springframework.stereotype.Service;
  14. import java.util.*;
  15. /**
  16. * 该类为今日佳人
  17. * 功能模块的service层结构
  18. */
  19. @Service
  20. public class TodayBestService {
  21. //注入UserService,为了校验token
  22. @Autowired
  23. private UserService userService;
  24. //注入查询推荐用户业务层
  25. @Autowired
  26. private RecommendUserService recommendUserService;
  27. //从配置文件重货获取默认用户的id
  28. @Value("${tanhua.sso.default.user}")
  29. private Long defaultUser;
  30. //注入查询用户详细信息的service
  31. @Autowired
  32. private UserInfoService userInfoService;
  33. public TodayBest queryTodayBest(String token) {
  34. //调用userService层将token传递过去进行校验
  35. //返回user
  36. User user = this.userService.checkToken(token);
  37. //判断user
  38. if(null==user){
  39. return null;
  40. }
  41. //如果不为空,就查询推荐用户
  42. //将用户id传过去,返回推荐用户信息(去MongoDB里查数据)
  43. TodayBest todayBest = this.recommendUserService.queryTodayBest(user.getId());
  44. //判断结果
  45. if(todayBest==null){
  46. //如果为空的话,就给默认用户返回
  47. todayBest = new TodayBest();
  48. //将配置文件中默认的今日佳人的id封装到todayBest
  49. todayBest.setId(defaultUser);
  50. //设置缘分值,固定的,写死的
  51. todayBest.setFateValue(85L);
  52. }
  53. //然后补全推荐用户的信息(去数据库中查)
  54. //通过调用查询用户详细信息的service
  55. //将推荐用户的id传递过去,返回userinfo信息
  56. UserInfo userInfo = this.userInfoService.queryUserInfoByUserId(todayBest.getId());
  57. //判断返回结果
  58. if(userInfo==null){
  59. //为空直接返回空
  60. return null;
  61. }
  62. //不为空 userinfo的数据封装到todayBest中
  63. todayBest.setAge(userInfo.getAge());
  64. todayBest.setNickname(userInfo.getNickName());
  65. //todayBest.setGender(userInfo.getSex().name().toLowerCase());
  66. todayBest.setGender(userInfo.getSex().getValue() == 1 ? "man" : "woman");
  67. todayBest.setAvatar(userInfo.getLogo());
  68. todayBest.setTags(StringUtils.split(userInfo.getTags(),","));
  69. //如果不为空
  70. return todayBest;
  71. }
  72. /**
  73. * 推荐列表的service层业务实现层方法
  74. * @param token token
  75. * @param queryParam 请求参数
  76. * @return 查询结果
  77. */
  78. public PageResult queryRecommendation(String token, RecommendUserQueryParam queryParam) {
  79. //调用userService校验token
  80. User user = this.userService.checkToken(token);
  81. //判断user
  82. if(user == null){
  83. //为空则返回null
  84. return null;
  85. }
  86. //创建默认的返回值
  87. PageResult pageResult = new PageResult();
  88. pageResult.setPage(queryParam.getPage());
  89. pageResult.setPagesize(queryParam.getPagesize());
  90. //如果不为空,就调用recommendUserService,
  91. // 去mongodb里查询推荐用户列表
  92. PageInfo<RecommendUser> pageInfo =this.recommendUserService.queryRecommendUserList(
  93. user.getId(),
  94. queryParam.getPage(),
  95. queryParam.getPagesize()
  96. );
  97. //获取里面的用户数据
  98. List<RecommendUser> records = pageInfo.getRecords();
  99. //判断records
  100. if(records==null){
  101. //如果为空则返回默认的PageResult
  102. return pageResult;
  103. }
  104. //如果不为空则 补全从mongodb查出来的用户列表信息
  105. //创建map集合存储推荐用户的id,和缘分值
  106. Map<Long,Long>idAndScore = new HashMap<>();
  107. for (RecommendUser record : records) {
  108. Double floor = Math.floor(record.getScore());
  109. long score = floor.longValue();
  110. idAndScore.put(record.getUserId(),score);
  111. }
  112. //创建查询条件
  113. QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
  114. //设置id集查询条件
  115. wrapper.in("user_id",idAndScore.keySet());
  116. //设置性别查询条件
  117. //判断请求参数
  118. if(StringUtils.isNotEmpty(queryParam.getGender())){
  119. //如果用户传过来的请求参数中的性别不为空
  120. //则再次判断请求参数中的性别
  121. wrapper.eq("sex",StringUtils.equals(queryParam.getGender(),"man")?1:2);
  122. }
  123. //设置城市参数查询
  124. if(StringUtils.isNotEmpty(queryParam.getCity())){
  125. wrapper.like("city",queryParam.getCity());
  126. }
  127. //设置年龄查询条件
  128. if(queryParam.getAge()!=null){
  129. wrapper.le("age",queryParam.getAge());
  130. }
  131. List<UserInfo> userInfos = this.userInfoService.queryUserInfoList(wrapper);
  132. //判断userInfos
  133. if(userInfos==null){
  134. return pageResult;
  135. }
  136. //如果不为空,则封装数据
  137. List<TodayBest> result = new ArrayList<>();
  138. //遍历userInfos,将userInfos添加到result中
  139. for (UserInfo userInfo : userInfos) {
  140. TodayBest best = new TodayBest();
  141. best.setId(userInfo.getUserId());
  142. best.setAvatar(userInfo.getLogo());
  143. best.setNickname(userInfo.getNickName());
  144. best.setTags(StringUtils.split(userInfo.getTags(),","));
  145. best.setGender(userInfo.getSex().getValue() == 1 ? "man" : "woman");
  146. best.setAge(userInfo.getAge());
  147. //设置缘分值
  148. for (Long id : idAndScore.keySet()) {
  149. //判断id
  150. if(id==userInfo.getUserId()){
  151. Long score = idAndScore.get(id);
  152. best.setFateValue(score);
  153. break;
  154. }
  155. }
  156. result.add(best);
  157. }
  158. //根据缘分值降序排序
  159. Collections.sort(result, (o1, o2) -> new Long(o2.getFateValue() - o1.getFateValue()).intValue());
  160. pageResult.setItems(result);
  161. return pageResult;
  162. }
  163. }

3.3.3.4、UserService
  1. package com.tanhua.server.service;
  2. import com.tanhua.server.pojo.User;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.beans.factory.annotation.Value;
  6. import org.springframework.stereotype.Service;
  7. import org.springframework.web.client.RestClientException;
  8. import org.springframework.web.client.RestTemplate;
  9. /**
  10. * 该类为校验用户token的
  11. * 业务层(service层)
  12. */
  13. @Service
  14. @Slf4j
  15. public class UserService {
  16. //注入发送rest请求的模板类
  17. @Autowired
  18. private RestTemplate restTemplate;
  19. //读取配置文件中sso的访问路径
  20. @Value("${tanhua.sso.url}")
  21. private String ssoUrl;
  22. /**
  23. * 校验用户token的方法
  24. * @param token 用户传过来的token
  25. * @return 用户基本信息
  26. */
  27. public User checkToken(String token) {
  28. try {
  29. //定义访问sso的路径
  30. String url = ssoUrl+"/user/"+token;
  31. User user = restTemplate.getForObject(url, User.class);
  32. //判断
  33. if(null==user){
  34. //如果为空直接返回null
  35. return null;
  36. }
  37. //如果不为空则将user返回
  38. return user;
  39. } catch (RestClientException e) {
  40. //e.printStackTrace();
  41. log.error("校验token出错,token = " + token, e);
  42. }
  43. return null;
  44. }
  45. }
  1. package com.tanhua.server.config;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.http.client.ClientHttpRequestFactory;
  5. import org.springframework.http.client.SimpleClientHttpRequestFactory;
  6. import org.springframework.http.converter.StringHttpMessageConverter;
  7. import org.springframework.web.client.RestTemplate;
  8. import java.nio.charset.Charset;
  9. /**
  10. * 该类为创建restTemplate对象的配置类
  11. */
  12. @Configuration
  13. public class RestTemplateConfig {
  14. @Bean
  15. public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
  16. RestTemplate restTemplate = new RestTemplate(factory);
  17. // 支持中文编码
  18. restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(Charset.forName("UTF-8")));
  19. return restTemplate;
  20. }
  21. @Bean
  22. public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
  23. SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
  24. factory.setReadTimeout(5000);//单位为ms
  25. factory.setConnectTimeout(5000);//单位为ms
  26. return factory;
  27. }
  28. }

3.3.3.5、RecommendUserService
  1. package com.tanhua.server.service;
  2. import com.alibaba.dubbo.config.annotation.Reference;
  3. import com.tanhua.dubbo.server.api.RecommendUserApi;
  4. import com.tanhua.dubbo.server.pojo.RecommendUser;
  5. import com.tanhua.dubbo.server.vo.PageInfo;
  6. import com.tanhua.server.vo.TodayBest;
  7. import org.springframework.stereotype.Service;
  8. /**
  9. * 该类为查询MongoDB获取推荐用户的service层
  10. */
  11. @Service
  12. public class RecommendUserService {
  13. //注入dubbo的recommendUserService
  14. @Reference(version = "1.0.0")
  15. private RecommendUserApi userApi;
  16. /**
  17. * 从MongoDB查询推荐用户的方法
  18. * 主要目的是将推荐用户的id和
  19. * 缘分值拿到
  20. *
  21. * @param id 用户id
  22. * @return 推荐的用户信息
  23. */
  24. public TodayBest queryTodayBest(Long id) {
  25. //调用查询单个数据的方法
  26. RecommendUser recommendUser = this.userApi.queryWithMaxScore(id);
  27. if(recommendUser==null){
  28. return null;
  29. }
  30. //如果不为空则正常返回todayBest
  31. TodayBest todayBest = new TodayBest();
  32. todayBest.setId(recommendUser.getUserId());
  33. //从查询结果的recommendUser中获取缘分值
  34. double scoreDou=recommendUser.getScore();
  35. Double d = Math.floor(scoreDou);
  36. long score = d.longValue();
  37. todayBest.setFateValue(score);
  38. return todayBest;
  39. }
  40. public PageInfo<RecommendUser> queryRecommendUserList(Long id, Integer page, Integer pagesize) {
  41. return this.userApi.queryPageInfo(id,page,pagesize);
  42. }
  43. }

3.3.3.6、UserInfoService
  1. package com.tanhua.server.service;
  2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  3. import com.tanhua.server.mapper.UserInfoMapper;
  4. import com.tanhua.server.pojo.UserInfo;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.stereotype.Service;
  7. import java.util.List;
  8. /**
  9. * 该类为从数据库中查询用户详细信息的service
  10. */
  11. @Service
  12. public class UserInfoService {
  13. //注入mapper
  14. @Autowired
  15. private UserInfoMapper userInfoMapper;
  16. /**
  17. * 根据id查询数据
  18. *
  19. * @param id 用户id
  20. * @return 用户详细信息
  21. */
  22. public UserInfo queryUserInfoByUserId(Long id) {
  23. QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
  24. wrapper.eq("user_id",id);
  25. UserInfo userInfo = this.userInfoMapper.selectOne(wrapper);
  26. return userInfo;
  27. }
  28. public List<UserInfo> queryUserInfoList(QueryWrapper<UserInfo> wrapper) {
  29. return this.userInfoMapper.selectList(wrapper);
  30. }
  31. }

3.3.3.7、UserInfoMapper
  1. package com.tanhua.server.mapper;
  2. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  3. import com.tanhua.common.pojo.UserInfo;
  4. public interface UserInfoMapper extends BaseMapper<UserInfo> {
  5. }

3.3.4、测试

单元测试,测试dubbo服务:

  1. package com.tanhua.server;
  2. import com.tanhua.dubbo.server.pojo.RecommendUser;
  3. import com.tanhua.dubbo.server.vo.PageInfo;
  4. import com.tanhua.server.service.RecommendUserService;
  5. import com.tanhua.server.vo.RecommendUserQueryParam;
  6. import com.tanhua.server.vo.TodayBest;
  7. import org.junit.Test;
  8. import org.junit.runner.RunWith;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.boot.test.context.SpringBootTest;
  11. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
  12. import java.util.List;
  13. /**
  14. * 该类为测试查询今日佳人的测试类
  15. */
  16. @SpringBootTest
  17. @RunWith(SpringJUnit4ClassRunner.class)
  18. public class TestRecommendUserApi {
  19. @Autowired
  20. private RecommendUserService recommendUserService;
  21. @Test
  22. public void findRecommendUser(){
  23. TodayBest todayBest = this.recommendUserService.queryTodayBest(100L);
  24. System.out.println(todayBest);
  25. }
  26. @Test
  27. public void test(){
  28. RecommendUserQueryParam recommendUserQueryParam = new RecommendUserQueryParam();
  29. recommendUserQueryParam.setPage(1);
  30. recommendUserQueryParam.setPagesize(100);
  31. recommendUserQueryParam.setAge(30);
  32. recommendUserQueryParam.setCity("北京城区");
  33. recommendUserQueryParam.setGender("man");
  34. PageInfo<RecommendUser> recommendUserPageInfo = this.recommendUserService.queryRecommendUserList(100l, 1, 100);
  35. List<RecommendUser> records = recommendUserPageInfo.getRecords();
  36. System.out.println(records);
  37. }
  38. }

整合功能测试,需要将sso、dubbo服务启动完成后进行测试。
image-20201130170209235.png

3.3.5、解决MongoDB启动bug

在项目中,添加了mongo的依赖的话,springboot就会自动去连接本地的mongo,由于他连接不上会导致出错。
1567411076994.png

解决:

springboot中添加排除自动配置的注解

  1. package com.tanhua.server;
  2. import org.mybatis.spring.annotation.MapperScan;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
  6. import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
  7. @MapperScan("com.tanhua.server.mapper") //设置mapper接口的扫描包
  8. @SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class}) //排除mongo的自动配置
  9. public class ServerApplication {
  10. public static void main(String[] args) {
  11. SpringApplication.run(ServerApplication.class, args);
  12. }
  13. }

4、推荐列表

1567064038029.png

4.1、mock接口

地址:https://mock-java.itheima.net/project/35/interface/api/623
image-20201130181444611.png
1567064266212.png

4.2、查询参数对象

  1. package com.tanhua.server.vo;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. @Data
  6. @NoArgsConstructor
  7. @AllArgsConstructor
  8. public class RecommendUserQueryParam {
  9. private Integer page = 1; //当前页数
  10. private Integer pagesize = 10; //页尺寸
  11. private String gender; //性别 man woman
  12. private String lastLogin; //近期登陆时间
  13. private Integer age; //年龄
  14. private String city; //居住地
  15. private String education; //学历
  16. }

4.3、结果对象

  1. package com.tanhua.server.vo;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. import java.util.Collections;
  6. import java.util.List;
  7. @Data
  8. @AllArgsConstructor
  9. @NoArgsConstructor
  10. public class PageResult {
  11. private Integer counts = 0;//总记录数
  12. private Integer pagesize = 0;//页大小
  13. private Integer pages = 0;//总页数
  14. private Integer page = 0;//当前页码
  15. private List<?> items = Collections.emptyList(); //列表
  16. }

4.4、Controller

  1. package com.tanhua.server.controller;
  2. import com.tanhua.server.service.TodayBestService;
  3. import com.tanhua.server.utils.Cache;
  4. import com.tanhua.server.vo.PageResult;
  5. import com.tanhua.server.vo.RecommendUserQueryParam;
  6. import com.tanhua.server.vo.TodayBest;
  7. import lombok.extern.slf4j.Slf4j;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.http.HttpStatus;
  10. import org.springframework.http.ResponseEntity;
  11. import org.springframework.web.bind.annotation.GetMapping;
  12. import org.springframework.web.bind.annotation.RequestHeader;
  13. import org.springframework.web.bind.annotation.RequestMapping;
  14. import org.springframework.web.bind.annotation.RestController;
  15. /**
  16. * 该类为今日佳人功能模块的
  17. * Controller层结构
  18. */
  19. @RestController
  20. @RequestMapping("tanhua")
  21. @Slf4j //打印日志的标签
  22. public class TodayBestController {
  23. //注入service层
  24. @Autowired
  25. private TodayBestService todayBestService;
  26. /**
  27. * 接收用户传来
  28. * 的token的方法
  29. *
  30. * @param token 用户传来的token
  31. * @return 返回今日佳人的信息数据
  32. */
  33. @GetMapping("todayBest")
  34. public ResponseEntity<TodayBest> queryTodayBest(@RequestHeader("Authorization")
  35. String token){
  36. try {
  37. //调用service层将token传递过去
  38. TodayBest todayBest= this.todayBestService.queryTodayBest(token);
  39. //判断todayBest
  40. if(null!=todayBest){
  41. //如果结果不为空,直接返回数据结果
  42. return ResponseEntity.ok(todayBest);
  43. }
  44. } catch (Exception e) {
  45. //e.printStackTrace();
  46. log.error("查询出错,",token,e);
  47. }
  48. //如果为空则返回空
  49. return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
  50. }
  51. /**
  52. * 推荐列表功能模块
  53. *
  54. * @param token 传过来的token
  55. * @param queryParam 请求参数封装类对象
  56. * @return 查询出来的推荐用户列表数据
  57. */
  58. @GetMapping("recommendation")
  59. @Cache
  60. public ResponseEntity<PageResult> queryRecommendation(@RequestHeader("Authorization") String token ,
  61. RecommendUserQueryParam queryParam){
  62. try {
  63. //调用今日佳人模块的service层,将token和请求参数传过去
  64. //返回pageResult
  65. PageResult pageResult = this.todayBestService.queryRecommendation(token,queryParam);
  66. //判断
  67. if(pageResult!=null){
  68. //如果结果不为空则,直接返回成功的数据
  69. return ResponseEntity.ok(pageResult);
  70. }
  71. } catch (Exception e) {
  72. //e.printStackTrace();
  73. log.error("查询推荐用户列表出错~ token = " + token, e);
  74. }
  75. return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
  76. }
  77. }

4.5、Service

  1. package com.tanhua.server.service;
  2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  3. import com.tanhua.dubbo.server.pojo.RecommendUser;
  4. import com.tanhua.dubbo.server.vo.PageInfo;
  5. import com.tanhua.server.pojo.User;
  6. import com.tanhua.server.pojo.UserInfo;
  7. import com.tanhua.server.vo.PageResult;
  8. import com.tanhua.server.vo.RecommendUserQueryParam;
  9. import com.tanhua.server.vo.TodayBest;
  10. import org.apache.commons.lang3.StringUtils;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.beans.factory.annotation.Value;
  13. import org.springframework.stereotype.Service;
  14. import java.util.*;
  15. /**
  16. * 该类为今日佳人
  17. * 功能模块的service层结构
  18. */
  19. @Service
  20. public class TodayBestService {
  21. //注入UserService,为了校验token
  22. @Autowired
  23. private UserService userService;
  24. //注入查询推荐用户业务层
  25. @Autowired
  26. private RecommendUserService recommendUserService;
  27. //从配置文件重货获取默认用户的id
  28. @Value("${tanhua.sso.default.user}")
  29. private Long defaultUser;
  30. //注入查询用户详细信息的service
  31. @Autowired
  32. private UserInfoService userInfoService;
  33. public TodayBest queryTodayBest(String token) {
  34. //调用userService层将token传递过去进行校验
  35. //返回user
  36. User user = this.userService.checkToken(token);
  37. //判断user
  38. if(null==user){
  39. return null;
  40. }
  41. //如果不为空,就查询推荐用户
  42. //将用户id传过去,返回推荐用户信息(去MongoDB里查数据)
  43. TodayBest todayBest = this.recommendUserService.queryTodayBest(user.getId());
  44. //判断结果
  45. if(todayBest==null){
  46. //如果为空的话,就给默认用户返回
  47. todayBest = new TodayBest();
  48. //将配置文件中默认的今日佳人的id封装到todayBest
  49. todayBest.setId(defaultUser);
  50. //设置缘分值,固定的,写死的
  51. todayBest.setFateValue(85L);
  52. }
  53. //然后补全推荐用户的信息(去数据库中查)
  54. //通过调用查询用户详细信息的service
  55. //将推荐用户的id传递过去,返回userinfo信息
  56. UserInfo userInfo = this.userInfoService.queryUserInfoByUserId(todayBest.getId());
  57. //判断返回结果
  58. if(userInfo==null){
  59. //为空直接返回空
  60. return null;
  61. }
  62. //不为空 userinfo的数据封装到todayBest中
  63. todayBest.setAge(userInfo.getAge());
  64. todayBest.setNickname(userInfo.getNickName());
  65. //todayBest.setGender(userInfo.getSex().name().toLowerCase());
  66. todayBest.setGender(userInfo.getSex().getValue() == 1 ? "man" : "woman");
  67. todayBest.setAvatar(userInfo.getLogo());
  68. todayBest.setTags(StringUtils.split(userInfo.getTags(),","));
  69. //如果不为空
  70. return todayBest;
  71. }
  72. /**
  73. * 推荐列表的service层业务实现层方法
  74. * @param token token
  75. * @param queryParam 请求参数
  76. * @return 查询结果
  77. */
  78. public PageResult queryRecommendation(String token, RecommendUserQueryParam queryParam) {
  79. //调用userService校验token
  80. User user = this.userService.checkToken(token);
  81. //判断user
  82. if(user == null){
  83. //为空则返回null
  84. return null;
  85. }
  86. //创建默认的返回值
  87. PageResult pageResult = new PageResult();
  88. pageResult.setPage(queryParam.getPage());
  89. pageResult.setPagesize(queryParam.getPagesize());
  90. //如果不为空,就调用recommendUserService,
  91. // 去mongodb里查询推荐用户列表
  92. PageInfo<RecommendUser> pageInfo =this.recommendUserService.queryRecommendUserList(
  93. user.getId(),
  94. queryParam.getPage(),
  95. queryParam.getPagesize()
  96. );
  97. //获取里面的用户数据
  98. List<RecommendUser> records = pageInfo.getRecords();
  99. //判断records
  100. if(records==null){
  101. //如果为空则返回默认的PageResult
  102. return pageResult;
  103. }
  104. //如果不为空则 补全从mongodb查出来的用户列表信息
  105. //创建map集合存储推荐用户的id,和缘分值
  106. Map<Long,Long>idAndScore = new HashMap<>();
  107. for (RecommendUser record : records) {
  108. Double floor = Math.floor(record.getScore());
  109. long score = floor.longValue();
  110. idAndScore.put(record.getUserId(),score);
  111. }
  112. //创建查询条件
  113. QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
  114. //设置id集查询条件
  115. wrapper.in("user_id",idAndScore.keySet());
  116. //设置性别查询条件
  117. //判断请求参数
  118. if(StringUtils.isNotEmpty(queryParam.getGender())){
  119. //如果用户传过来的请求参数中的性别不为空
  120. //则再次判断请求参数中的性别
  121. wrapper.eq("sex",StringUtils.equals(queryParam.getGender(),"man")?1:2);
  122. }
  123. //设置城市参数查询
  124. if(StringUtils.isNotEmpty(queryParam.getCity())){
  125. wrapper.like("city",queryParam.getCity());
  126. }
  127. //设置年龄查询条件
  128. if(queryParam.getAge()!=null){
  129. wrapper.le("age",queryParam.getAge());
  130. }
  131. List<UserInfo> userInfos = this.userInfoService.queryUserInfoList(wrapper);
  132. //判断userInfos
  133. if(userInfos==null){
  134. return pageResult;
  135. }
  136. //如果不为空,则封装数据
  137. List<TodayBest> result = new ArrayList<>();
  138. //遍历userInfos,将userInfos添加到result中
  139. for (UserInfo userInfo : userInfos) {
  140. TodayBest best = new TodayBest();
  141. best.setId(userInfo.getUserId());
  142. best.setAvatar(userInfo.getLogo());
  143. best.setNickname(userInfo.getNickName());
  144. best.setTags(StringUtils.split(userInfo.getTags(),","));
  145. best.setGender(userInfo.getSex().getValue() == 1 ? "man" : "woman");
  146. best.setAge(userInfo.getAge());
  147. //设置缘分值
  148. for (Long id : idAndScore.keySet()) {
  149. //判断id
  150. if(id==userInfo.getUserId()){
  151. Long score = idAndScore.get(id);
  152. best.setFateValue(score);
  153. break;
  154. }
  155. }
  156. result.add(best);
  157. }
  158. //根据缘分值降序排序
  159. Collections.sort(result, (o1, o2) -> new Long(o2.getFateValue() - o1.getFateValue()).intValue());
  160. pageResult.setItems(result);
  161. return pageResult;
  162. }
  163. }
  1. //RecommendUserService
  2. public PageInfo<RecommendUser> queryRecommendUserList(Long id, Integer page, Integer pagesize) {
  3. return this.recommendUserApi.queryPageInfo(id, page, pagesize);
  4. }
  1. //UserInfoService
  2. /**
  3. * 查询用户信息列表
  4. *
  5. * @param queryWrapper
  6. * @return
  7. */
  8. public List<UserInfo> queryUserInfoList(QueryWrapper queryWrapper) {
  9. return this.userInfoMapper.selectList(queryWrapper);
  10. }

4.6、测试

image-20201130190537686.png
image-20201130190553650.png

5、缓存

在接口服务中,有必要对于接口进行缓存处理,尤其是GET请求,如果每个接口单独添加的话会存在很多的重复的逻辑,所以可以编写一套通用的解决方案。

实现思路:

  • 通过拦截器实现对请求的拦截,在拦截器中实现缓存的命中。
  • 通过ResponseBodyAdvice进行对响应的拦截,可以将数据缓存到Redis中。
  • 考虑到,不能对于所有的请求都一刀切,所以需要创建@Cache注解进行标记,只有标记的Controller才进行缓存处理。
  • 缓存的处理中,仅针对GET请求处理,其他的请求均不做处理。

5.1、自定义注解 (知识盲点:注解)

  1. package com.tanhua.server.utils;
  2. import java.lang.annotation.*;
  3. /**
  4. * 被标记为Cache的Controller进行缓存,
  5. * 其他情况不进行缓存
  6. */
  7. @Target(ElementType.METHOD)
  8. @Retention(RetentionPolicy.RUNTIME)
  9. @Documented //标记注解
  10. public @interface Cache {
  11. /**
  12. * 缓存时间,默认为60秒
  13. * @return
  14. */
  15. String time() default "60";
  16. }

5.2、采用拦截器进行缓存命中

编写拦截器:RedisCacheInterceptor。(知识盲点:HandlerInterceptor、@Component)

  1. package com.tanhua.server.interceptor;
  2. import com.fasterxml.jackson.core.JsonProcessingException;
  3. import com.fasterxml.jackson.databind.ObjectMapper;
  4. import com.tanhua.server.utils.Cache;
  5. import org.apache.commons.codec.digest.DigestUtils;
  6. import org.apache.commons.lang3.StringUtils;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.beans.factory.annotation.Value;
  9. import org.springframework.data.redis.core.RedisTemplate;
  10. import org.springframework.stereotype.Component;
  11. import org.springframework.web.bind.annotation.GetMapping;
  12. import org.springframework.web.method.HandlerMethod;
  13. import org.springframework.web.servlet.HandlerInterceptor;
  14. import javax.servlet.http.HttpServletRequest;
  15. import javax.servlet.http.HttpServletResponse;
  16. @Component
  17. public class RedisCacheInterceptor implements HandlerInterceptor {
  18. //开启redis缓存机制的配置
  19. @Value("${tanhua.cache.enable}")
  20. private Boolean enable;
  21. //redis工具类
  22. @Autowired
  23. private RedisTemplate<String, String> redisTemplate;
  24. //json转换类
  25. private static final ObjectMapper MAPPER = new ObjectMapper();
  26. @Override
  27. public boolean preHandle(HttpServletRequest request,
  28. HttpServletResponse response,
  29. Object handler) throws Exception {
  30. //缓存的全局开关的校验
  31. if (!enable) {
  32. //为enable为true就放行
  33. return true;
  34. }
  35. //校验handler是否是HandlerMethod(表示为访问controller的方法)
  36. if (!(handler instanceof HandlerMethod)) {
  37. //不是HandlerMethod就放行
  38. return true;
  39. }
  40. //判断是否为get请求
  41. if (!((HandlerMethod) handler).hasMethodAnnotation(GetMapping.class)) {
  42. //不是get请求就放行
  43. return true;
  44. }
  45. //判断是否添加了@Cache注解
  46. if (!((HandlerMethod) handler).hasMethodAnnotation(Cache.class)) {
  47. //没有加Cache注解就放行
  48. return true;
  49. }
  50. //没有满足以上的判断就进行缓存命中
  51. //缓存命中
  52. String redisKey = createRedisKey(request);
  53. //通过键从redis中获取值
  54. String cacheData = this.redisTemplate.opsForValue().get(redisKey);
  55. //判断从redis中获取到的值
  56. if(StringUtils.isEmpty(cacheData)){
  57. //为空则表示缓存未命中,放行
  58. return true;
  59. }
  60. // 如果不为空就将redis中查出的data数据进行响应
  61. response.setCharacterEncoding("UTF-8");
  62. response.setContentType("application/json; charset=utf-8");
  63. response.getWriter().write(cacheData);
  64. return false;
  65. }
  66. /**
  67. * 该类为创建redis的key的方法 规则:SERVER_CACHE_DATA_MD5(url + param + token)
  68. * @param request servlet的请求封装类
  69. * @return redis的key
  70. * @throws JsonProcessingException
  71. */
  72. public static String createRedisKey(HttpServletRequest request) throws JsonProcessingException {
  73. //获取url请求路径
  74. String url = request.getRequestURI();
  75. //通过json转换类将封装请求参数的map集合对象转换成json格式的字符串
  76. String param = MAPPER.writeValueAsString(request.getParameterMap());
  77. //获取token
  78. String token = request.getHeader("Authorization");
  79. //拼接key的规则
  80. String data = url + "_" + param + "_" + token;
  81. //加密并返回
  82. return "SERVER_CACHE_DATA_" + DigestUtils.md5Hex(data);
  83. }
  84. /*@Value("${tanhua.cache.enable}")
  85. private Boolean enable;
  86. @Autowired
  87. private RedisTemplate<String, String> redisTemplate;
  88. private static final ObjectMapper MAPPER = new ObjectMapper();
  89. @Override
  90. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  91. //缓存的全局开关的校验
  92. if (!enable) {
  93. //为true表示放行
  94. return true;
  95. }
  96. //校验handler是否是HandlerMethod(表示为访问controller的方法)
  97. if (!(handler instanceof HandlerMethod)) {
  98. return true;
  99. }
  100. //判断是否为get请求
  101. if (!((HandlerMethod) handler).hasMethodAnnotation(GetMapping.class)) {
  102. return true;
  103. }
  104. //判断是否添加了@Cache注解
  105. if (!((HandlerMethod) handler).hasMethodAnnotation(Cache.class)) {
  106. return true;
  107. }
  108. //缓存命中
  109. String redisKey = createRedisKey(request);
  110. String cacheData = this.redisTemplate.opsForValue().get(redisKey);
  111. if(StringUtils.isEmpty(cacheData)){
  112. //缓存未命中
  113. return true;
  114. }
  115. // 将data数据进行响应
  116. response.setCharacterEncoding("UTF-8");
  117. response.setContentType("application/json; charset=utf-8");
  118. response.getWriter().write(cacheData);
  119. return false;
  120. }
  121. *//**
  122. * 生成redis中的key,规则:SERVER_CACHE_DATA_MD5(url + param + token)
  123. *
  124. * @param request
  125. * @return
  126. *//*
  127. public static String createRedisKey(HttpServletRequest request) throws Exception {
  128. String url = request.getRequestURI();
  129. String param = MAPPER.writeValueAsString(request.getParameterMap());
  130. String token = request.getHeader("Authorization");
  131. String data = url + "_" + param + "_" + token;
  132. return "SERVER_CACHE_DATA_" + DigestUtils.md5Hex(data);
  133. }*/
  134. }

application.properties:

  1. #是否开启数据缓存
  2. tanhua.cache.enable=false
  3. # Redis相关配置
  4. spring.redis.jedis.pool.max-wait = 5000ms
  5. spring.redis.jedis.pool.max-Idle = 100
  6. spring.redis.jedis.pool.min-Idle = 10
  7. spring.redis.timeout = 10s
  8. spring.redis.cluster.nodes = 192.168.31.81:6379,192.168.31.81:6380,192.168.31.81:6381
  9. spring.redis.cluster.max-redirects=5

注册拦截器到Spring容器:

  1. package com.tanhua.server.config;
  2. import com.tanhua.server.interceptor.RedisCacheInterceptor;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  6. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  7. /**
  8. * 该类为拦截器的配置类(将我们写的RedisCacheInterceptor注入到IoC容器中)
  9. */
  10. @Configuration
  11. public class WebConfig implements WebMvcConfigurer {
  12. //实现webMvcConfigurer接口
  13. @Autowired
  14. private RedisCacheInterceptor redisCacheInterceptor;
  15. @Override
  16. public void addInterceptors(InterceptorRegistry registry) {
  17. //配置拦截的请求路径(拦截根路径下所有的请求)
  18. registry.addInterceptor(this.redisCacheInterceptor).addPathPatterns("/*");
  19. }
  20. }

4.3、响应结果写入到缓存

使用ResponseBodyAdvice进行对响应结果处理,将结果写入到Redis中:

具体实现:

  1. package com.tanhua.server.interceptor;
  2. import com.fasterxml.jackson.core.JsonProcessingException;
  3. import com.fasterxml.jackson.databind.ObjectMapper;
  4. import com.tanhua.server.utils.Cache;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.beans.factory.annotation.Value;
  7. import org.springframework.core.MethodParameter;
  8. import org.springframework.data.redis.core.RedisTemplate;
  9. import org.springframework.http.MediaType;
  10. import org.springframework.http.server.ServerHttpRequest;
  11. import org.springframework.http.server.ServerHttpResponse;
  12. import org.springframework.http.server.ServletServerHttpRequest;
  13. import org.springframework.stereotype.Component;
  14. import org.springframework.web.bind.annotation.ControllerAdvice;
  15. import org.springframework.web.bind.annotation.GetMapping;
  16. import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
  17. import javax.servlet.http.HttpServletRequest;
  18. import java.util.concurrent.TimeUnit;
  19. /**
  20. * 该类为对响应结果进行写入redis的拦截器
  21. */
  22. @ControllerAdvice
  23. public class MyResponseBodyAdvice implements ResponseBodyAdvice {
  24. //开启redis缓存机制的配置
  25. @Value("${tanhua.cache.enable}")
  26. private Boolean enable;
  27. //json转换类
  28. private static final ObjectMapper MAPPER = new ObjectMapper();
  29. //redis工具类
  30. @Autowired
  31. private RedisTemplate<String, String> redisTemplate;
  32. @Override
  33. public boolean supports(MethodParameter methodParameter, Class aClass) {
  34. //只要满足redis换出开关为开启状态,是get请求,访问的controller是加有
  35. //自定义注解Cache的,就把响应结果写入redis缓存中
  36. return enable && methodParameter.hasMethodAnnotation(GetMapping.class)
  37. && methodParameter.hasMethodAnnotation(Cache.class);
  38. }
  39. @Override
  40. public Object beforeBodyWrite(Object o,
  41. MethodParameter methodParameter,
  42. MediaType mediaType,
  43. Class aClass,
  44. ServerHttpRequest serverHttpRequest,
  45. ServerHttpResponse serverHttpResponse) {
  46. //如果满足上面的三个条件就进入到这个方法中来
  47. //先判断参数一Object是否为空
  48. if(o==null){
  49. //如果为空就返回空
  50. return null;
  51. }
  52. //如果不为空就将结果写入到redis中
  53. //准备数据
  54. //处理结果数据
  55. try {
  56. String redisValue= null;
  57. if(o instanceof String){
  58. //如果o是String类型的则直接强转为String
  59. redisValue = (String)o;
  60. }else{
  61. //如果不是则将对象转换成json格式的字符串
  62. redisValue = MAPPER.writeValueAsString(o);
  63. }
  64. //处理键
  65. //将ServerHttpRequest强转为ServletServerHttpRequest
  66. ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) serverHttpRequest;
  67. HttpServletRequest request = servletServerHttpRequest.getServletRequest();
  68. String redisKey = RedisCacheInterceptor.createRedisKey(request);
  69. //获取Cache注解
  70. Cache annotation = methodParameter.getMethodAnnotation(Cache.class);
  71. String timeStr = annotation.time();
  72. Long time = Long.valueOf(timeStr);
  73. //参数一为redis的key,参数二为redis的值,参数三为有效的存储时间,参数四为时间单位秒
  74. this.redisTemplate.opsForValue().set(redisKey,redisValue,time, TimeUnit.SECONDS);
  75. } catch (Exception e) {
  76. //如果出了异常就不写了
  77. e.printStackTrace();
  78. }
  79. return o;
  80. }
  81. }

4.4、测试

image-20201130202845964.png

可以看到数据已经缓存到Redis中,并且其缓存时间也是30秒,与预期一致。
image-20201130203238548.png

6、整合测试

测试时需要注意,由于用户数据较少,所以测试时需要把条件注释掉,否则查询不到数据:
image-20201130202631311.png

效果:
image-20201130200736231.png
image-20201130202349684.png