探花交友_第3章_今日佳人功能实现

文章目录

  • 探花交友_第3章_今日佳人功能实现
    • 1、首页
    • 2、系统架构
      • 2.1、nginx服务
        • 2.1.1、部署安装
        • 2.1.2、配置
        • 2.1.3、测试
      • 2.2、搭建server工程
        • 2.2.1、导入依赖
        • 2.2.2、application.properties
        • 2.2.3、ServerApplication
      • 2.3、搭建dubbo工程
        • 2.3.1、创建my-tanhua-dubbo-interface工程
        • 2.3.2、创建my-tanhua-dubbo-service工程
      • 2.4、工程结构
    • 3、今日佳人
      • 3.1、表结构
      • 3.2、编写dubbo服务
        • 3.2.1、编写接口
        • 3.2.2、编写实现
        • 3.2.3、测试
      • 3.3、实现今日佳人服务
        • 3.3.1、mock服务
        • 3.3.2、基础代码
          • 3.3.2.1、SexEnum
          • 3.3.2.2、BasePojo
          • 3.3.2.3、User
          • 3.3.2.4、UserInfo
        • 3.3.3、实现功能
          • 3.3.3.1、TodayBest
          • 3.3.3.2、TodayBestController
          • 3.3.3.3、TodayBestService
          • 3.3.3.4、UserService
          • 3.3.3.5、RecommendUserService
          • 3.3.3.6、UserInfoService
          • 3.3.3.7、UserInfoMapper
        • 3.3.4、测试
        • 3.3.5、解决MongoDB启动bug
    • 4、推荐列表
      • 4.1、mock接口
      • 4.2、查询参数对象
      • 4.3、结果对象
      • 4.4、Controller
      • 4.5、Service
      • 4.6、测试
    • 5、缓存
      • 5.1、自定义注解
      • 5.2、采用拦截器进行缓存命中
      • 4.3、响应结果写入到缓存
      • 4.4、测试
  • 首页功能说明
  • 系统架构说明
  • 实现今日佳人功能
  • 实现推荐用户的列表
  • 接口增加缓存功能
  • 整合前端联调测试

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

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.itcast.tanhua</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>my-tanhua-server</artifactId><dependencies><dependency><groupId>cn.itcast.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></dependencies>
</project>

2.2.2、application.properties

spring.application.name = itcast-tanhua-server
server.port = 18081#数据库连接信息
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.31.81:3306/mytanhua?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=root# 枚举包扫描
mybatis-plus.type-enums-package=com.tanhua.server.enums
# 表名前缀
mybatis-plus.global-config.db-config.table-prefix=tb_
# id策略为自增长
mybatis-plus.global-config.db-config.id-type=auto#dubbo注册中心配置
dubbo.application.name = itcast-tanhua-server
dubbo.registry.address = zookeeper://192.168.31.81:2181
dubbo.registry.client = zkclient
dubbo.registry.timeout = 60000
dubbo.consumer.timeout = 60000#sso系统服务地址
tanhua.sso.url=http://127.0.0.1
#默认今日佳人推荐用户
tanhua.sso.default.user=2

2.2.3、ServerApplication

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

2.3、搭建dubbo工程

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

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>my-tanhua</artifactId><groupId>cn.itcast.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.itcast.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.itcast.tanhua</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>my-tanhua-dubbo-service</artifactId><dependencies><!--引入interface依赖--><dependency><groupId>cn.itcast.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><!--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>

application.properties:

# Spring boot application
spring.application.name = itcast-tanhua-dubbo-service# dubbo 扫描包配置
dubbo.scan.basePackages = com.tanhua.dubbo.server
dubbo.application.name = dubbo-provider-tanhua#dubbo 对外暴露的端口信息
dubbo.protocol.name = dubbo
dubbo.protocol.port = 20880#dubbo注册中心的配置
dubbo.registry.address = zookeeper://192.168.31.81:2181
dubbo.registry.client = zkclient
dubbo.registry.timeout = 60000 #springboot MongoDB配置
spring.data.mongodb.username=tanhua
spring.data.mongodb.password=l3SCjl0HvmSkTtiSbN0Swv40spYnHhDV
spring.data.mongodb.authentication-database=admin
spring.data.mongodb.database=tanhua
spring.data.mongodb.port=27017
spring.data.mongodb.host=192.168.31.81

编写启动类:

package com.tanhua.dubbo.server;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class DubboApplication {public static void main(String[] args) {SpringApplication.run(DubboApplication.class, args);}
}

2.4、工程结构

最终搭建完成的效果如下:

3、今日佳人

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

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

流程:

3.1、表结构

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

已经提供的测试数据(4855条数据):

3.2、编写dubbo服务

3.2.1、编写接口

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

package com.tanhua.dubbo.server.api;import com.tanhua.dubbo.server.vo.PageInfo;
import com.tanhua.dubbo.server.pojo.RecommendUser;public interface RecommendUserApi {/*** 查询一位得分最高的推荐用户** @param userId* @return*/RecommendUser queryWithMaxScore(Long userId);/*** 按照得分倒序** @param userId* @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;@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "recommend_user")
public class RecommendUser implements java.io.Serializable{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.util.Collections;
import java.util.List;@Data
@AllArgsConstructor
public class PageInfo<T> implements java.io.Serializable {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 {@Autowiredprivate MongoTemplate mongoTemplate;@Overridepublic RecommendUser queryWithMaxScore(Long userId) {//查询得分最高的用户,按照得分倒序排序Query query = Query.query(Criteria.where("toUserId").is(userId)).with(Sort.by(Sort.Order.desc("score"))).limit(1);return this.mongoTemplate.findOne(query, RecommendUser.class);}@Overridepublic PageInfo<RecommendUser> queryPageInfo(Long userId, Integer pageNum, Integer pageSize) {//分页并且排序参数PageRequest pageRequest = PageRequest.of(pageNum - 1, pageSize, Sort.by(Sort.Order.desc("score")));//查询参数Query query = Query.query(Criteria.where("toUserId").is(userId)).with(pageRequest);List<RecommendUser> recommendUserList = this.mongoTemplate.find(query, RecommendUser.class);//暂时不提供数据总数return new PageInfo<>(0, pageNum, pageSize, recommendUserList);}
}

3.2.3、测试

package com.tanhua.dubbo.server.api;import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)
@SpringBootTest
public class TestRecommendUserApi {@Autowiredprivate RecommendUserApi recommendUserApi;@Testpublic void testQueryWithMaxScore(){System.out.println(this.recommendUserApi.queryWithMaxScore(1L));System.out.println(this.recommendUserApi.queryWithMaxScore(8L));System.out.println(this.recommendUserApi.queryWithMaxScore(26L));}@Testpublic void testQueryPageInfo(){System.out.println(this.recommendUserApi.queryPageInfo(1L,1,5));System.out.println(this.recommendUserApi.queryPageInfo(1L,2,5));System.out.println(this.recommendUserApi.queryPageInfo(1L,3,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
@AllArgsConstructor
public 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
@AllArgsConstructor
public 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.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;@RestController
@RequestMapping("tanhua")
@Slf4j
public class TodayBestController {@Autowiredprivate TodayBestService todayBestService;/*** 查询今日佳人** @param token* @return*/@GetMapping("todayBest")public ResponseEntity<TodayBest> queryTodayBest(@RequestHeader("Authorization") String token) {try {TodayBest todayBest = this.todayBestService.queryTodayBest(token);if (null != todayBest) {return ResponseEntity.ok(todayBest);}} catch (Exception e) {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.tanhua.server.pojo.User;
import com.tanhua.server.pojo.UserInfo;
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;@Service
public class TodayBestService {@Autowiredprivate UserService userService;@Autowiredprivate RecommendUserService recommendUserService;@Autowiredprivate UserInfoService userInfoService;@Value("${tanhua.sso.default.user}")private Long defaultUser;public TodayBest queryTodayBest(String token) {//校验token是否有效,通过SSO的接口进行校验User user = this.userService.queryUserByToken(token);if (null == user) {//token非法或已经过期return null;}//查询推荐用户(今日佳人)TodayBest todayBest = this.recommendUserService.queryTodayBest(user.getId());if(null == todayBest){//给出默认的推荐用户todayBest = new TodayBest();todayBest.setId(defaultUser);todayBest.setFateValue(80L); //固定值}//补全个人信息UserInfo userInfo = this.userInfoService.queryUserInfoByUserId(todayBest.getId());if(null == userInfo){return null;}todayBest.setAvatar(userInfo.getLogo());todayBest.setNickname(userInfo.getNickName());todayBest.setTags(StringUtils.split(userInfo.getTags(), ','));todayBest.setGender(userInfo.getSex().getValue() == 1 ? "man" : "woman");todayBest.setAge(userInfo.getAge());return todayBest;}
}
3.3.3.4、UserService
package com.tanhua.server.service;import com.fasterxml.jackson.databind.ObjectMapper;
import com.tanhua.server.pojo.User;
import lombok.extern.slf4j.Slf4j;
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 org.springframework.web.client.RestTemplate;@Service
@Slf4j
public class UserService {@Autowiredprivate RestTemplate restTemplate;@Value("${tanhua.sso.url}")private String ssoUrl;private static final ObjectMapper MAPPER = new ObjectMapper();/*** 通过sso的rest接口查询** @param token* @return*/public User queryUserByToken(String token) {String url = ssoUrl + "/user/" + token;try {String data = this.restTemplate.getForObject(url, String.class);if (StringUtils.isEmpty(data)) {return null;}return MAPPER.readValue(data, User.class);} catch (Exception e) {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;@Configuration
public 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.server.vo.TodayBest;
import org.springframework.stereotype.Service;/*** 负责与dubbo服务进行交互*/
@Service
public class RecommendUserService {@Reference(version = "1.0.0")private RecommendUserApi recommendUserApi;public TodayBest queryTodayBest(Long userId) {RecommendUser recommendUser = this.recommendUserApi.queryWithMaxScore(userId);if(null == recommendUser){return null;}TodayBest todayBest = new TodayBest();todayBest.setId(recommendUser.getUserId());//缘分值double score = Math.floor(recommendUser.getScore());//取整,98.2 -> 98todayBest.setFateValue(Double.valueOf(score).longValue());return todayBest;}
}
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;@Service
public class UserInfoService {@Autowiredprivate UserInfoMapper userInfoMapper;public UserInfo queryUserInfoByUserId(Long userId) {QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();queryWrapper.eq("user_id", userId);return this.userInfoMapper.selectOne(queryWrapper);}
}
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.server.service.RecommendUserService;
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;@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class TestRecommendUserApi {@Autowiredprivate RecommendUserService recommendUserService;@Testpublic void testQueryTodayBest(){TodayBest todayBest = this.recommendUserService.queryTodayBest(1L);System.out.println(todayBest);}}

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

3.3.5、解决MongoDB启动bug

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

解决:

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

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

4、推荐列表

4.1、mock接口

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

4.2、查询参数对象

package com.tanhua.server.vo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@NoArgsConstructor
@AllArgsConstructor
public class RecommendUserQueryParam {private Integer page = 1; //当前页数private Integer pagesize = 10; //页尺寸private String gender; //性别 man 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
@NoArgsConstructor
public class PageResult {private Integer counts = 0;//总记录数private Integer pagesize = 0;//页大小private Integer pages = 0;//总页数private Integer page = 0;//当前页码private List<?> items = Collections.emptyList(); //列表}

4.4、Controller

    /*** 查询推荐用户列表** @param token* @param queryParam* @return*/@GetMapping("recommendation")public ResponseEntity<PageResult> queryRecommendation(@RequestHeader("Authorization") String token,RecommendUserQueryParam queryParam){try {PageResult pageResult = this.todayBestService.queryRecommendation(token, queryParam);if (null != pageResult) {return ResponseEntity.ok(pageResult);}} catch (Exception e) {log.error("查询推荐用户列表出错~ token = " + token, e);}return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);}

4.5、Service

/*** 查询推荐用户列表** @param queryParam* @param token* @return*/public PageResult queryRecommendation(String token, RecommendUserQueryParam queryParam) {//校验token是否有效,通过SSO的接口进行校验User user = this.userService.queryUserByToken(token);if (null == user) {//token非法或已经过期return null;}PageResult pageResult = new PageResult();pageResult.setPage(queryParam.getPage());pageResult.setPagesize(queryParam.getPagesize());PageInfo<RecommendUser> pageInfo = this.recommendUserService.queryRecommendUserList(user.getId(), queryParam.getPage(), queryParam.getPagesize());List<RecommendUser> records = pageInfo.getRecords();if (CollectionUtils.isEmpty(records)) {//没有查询到推荐的用户列表return pageResult;}//填充个人信息//收集推荐用户的idSet<Long> userIds = new HashSet<>();for (RecommendUser record : records) {userIds.add(record.getUserId());}QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();//用户id参数queryWrapper.in("user_id", userIds);if (StringUtils.isNotEmpty(queryParam.getGender())) {//需要性别参数查询queryWrapper.eq("sex", StringUtils.equals(queryParam.getGender(), "man") ? 1 : 2);}if (StringUtils.isNotEmpty(queryParam.getCity())) {//需要城市参数查询queryWrapper.like("city", queryParam.getCity());}if (queryParam.getAge() != null) {//设置年龄参数,条件:小于等于queryWrapper.le("age", queryParam.getAge());}List<UserInfo> userInfoList = this.userInfoService.queryUserInfoList(queryWrapper);if(CollectionUtils.isEmpty(userInfoList)){//没有查询到用户的基本信息return pageResult;}List<TodayBest> todayBests = new ArrayList<>();for (UserInfo userInfo : userInfoList) {TodayBest todayBest = new TodayBest();todayBest.setId(userInfo.getUserId());todayBest.setAvatar(userInfo.getLogo());todayBest.setNickname(userInfo.getNickName());todayBest.setTags(StringUtils.split(userInfo.getTags(), ','));todayBest.setGender(userInfo.getSex().getValue() == 1 ? "man" : "woman");todayBest.setAge(userInfo.getAge());//缘分值for (RecommendUser record : records) {if(record.getUserId().longValue() == userInfo.getUserId().longValue()){double score = Math.floor(record.getScore());//取整,98.2 -> 98todayBest.setFateValue(Double.valueOf(score).longValue());break;}}todayBests.add(todayBest);}//按照缘分值进行倒序排序Collections.sort(todayBests, (o1, o2) -> new Long(o2.getFateValue() - o1.getFateValue()).intValue());pageResult.setItems(todayBests);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。

package com.tanhua.server.interceptor;import com.fasterxml.jackson.databind.ObjectMapper;
import com.tanhua.server.utils.Cache;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Component
public class RedisCacheInterceptor implements HandlerInterceptor {@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) {return true;}//校验handler是否是HandlerMethodif (!(handler instanceof HandlerMethod)) {return true;}//判断是否为get请求if (!((HandlerMethod) handler).hasMethodAnnotation(GetMapping.class)) {return true;}//判断是否添加了@Cache注解if (!((HandlerMethod) handler).hasMethodAnnotation(Cache.class)) {return true;}//缓存命中String redisKey = createRedisKey(request);String cacheData = this.redisTemplate.opsForValue().get(redisKey);if(StringUtils.isEmpty(cacheData)){//缓存未命中return true;}// 将data数据进行响应response.setCharacterEncoding("UTF-8");response.setContentType("application/json; charset=utf-8");response.getWriter().write(cacheData);return false;}/*** 生成redis中的key,规则:SERVER_CACHE_DATA_MD5(url + param + token)** @param request* @return*/public static String createRedisKey(HttpServletRequest request) throws Exception {String url = request.getRequestURI();String param = MAPPER.writeValueAsString(request.getParameterMap());String token = request.getHeader("Authorization");String data = url + "_" + param + "_" + token;return "SERVER_CACHE_DATA_" + DigestUtils.md5Hex(data);}
}

application.properties:

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

注册拦截器到Spring容器:

package com.tanhua.server.config;import com.tanhua.server.interceptor.RedisCacheInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class WebConfig implements 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.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.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;import java.util.concurrent.TimeUnit;@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice {@Value("${tanhua.cache.enable}")private Boolean enable;@Autowiredprivate RedisTemplate<String, String> redisTemplate;private static final ObjectMapper MAPPER = new ObjectMapper();@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {// 开关处于开启状态  是get请求  包含了@Cache注解return enable && returnType.hasMethodAnnotation(GetMapping.class)&& returnType.hasMethodAnnotation(Cache.class);}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType,ServerHttpRequest request, ServerHttpResponse response) {if (null == body) {return null;}try {String redisValue = null;if (body instanceof String) {redisValue = (String) body;} else {redisValue = MAPPER.writeValueAsString(body);}String redisKey = RedisCacheInterceptor.createRedisKey(((ServletServerHttpRequest) request).getServletRequest());Cache cache = returnType.getMethodAnnotation(Cache.class);//缓存的时间单位是秒this.redisTemplate.opsForValue().set(redisKey, redisValue, Long.valueOf(cache.time()), TimeUnit.SECONDS);} catch (Exception e) {e.printStackTrace();}return body;}
}

4.4、测试

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

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

效果:

探花交友_第3章_今日佳人功能实现相关推荐

  1. Dubbo+Flutter在线交友平台教程第三天 今日佳人功能实现

    课程说明 首页功能说明 系统架构说明 实现今日佳人功能 实现推荐用户的列表 接口增加缓存功能 整合前端联调测试 1.首页 在用户登录成功后,就会进入首页,首页中有今日佳人.推荐好友.探花.搜附近等功能 ...

  2. 探花交友_第10章_搭建后台系统(新版)

    探花交友_第10章_搭建后台系统(新版) 文章目录 探花交友_第10章_搭建后台系统(新版) 1.1 概述 1.2 API网关 1.2.1 搭建网关 依赖 引导类 跨域问题配置类 配置文件 测试 1. ...

  3. 探花交友_第9章_小视频方案(新版)

    探花交友_第9章_小视频方案(新版) 文章目录 探花交友_第9章_小视频方案(新版) 1. 我的访客 1.1 需求分析 1.1.1 功能说明 1.1.2 数据库表 1.2 记录访客数据 tanhua- ...

  4. 探花交友_第11章_数据统计与内容审核(新版)

    探花交友_第11章_数据统计与内容审核(新版) 文章目录 探花交友_第11章_数据统计与内容审核(新版) 1.用户冻结解冻 1.1 用户冻结 ManageController ManageServic ...

  5. 探花交友_第1章_项目介绍以及实现登录功能_第1节_功能介绍

    探花交友_第1章_项目介绍以及实现登录功能_第1节_功能介绍 文章目录 探花交友_第1章_项目介绍以及实现登录功能_第1节_功能介绍 1.功能介绍 1.1.功能列表 1.2.注册登录 1.3.交友 1 ...

  6. 探花交友_第10章_实现推荐功能

    探花交友_第10章_实现推荐功能 文章目录 探花交友_第10章_实现推荐功能 1.了解推荐系统 1.1.什么是推荐系统? 1.2.电商是推荐系统的先行者 1.3.推荐系统业务流程 1.4.协同过滤推荐 ...

  7. 探花交友_第2章_环境搭建(新版)

    探花交友_第2章_环境搭建(新版) 文章目录 探花交友_第2章_环境搭建(新版) 课程介绍 <探花交友> 1.项目介绍 1.1.项目背景 1.2.市场分析 1.3.目标用户群体 1.4.使 ...

  8. 探花交友_第12章_实现推荐系统(新版)

    探花交友_第12章_实现推荐系统(新版) 文章目录 探花交友_第12章_实现推荐系统(新版) 1.了解推荐系统 1.1.什么是推荐系统? 1.2.电商是推荐系统的先行者 1.3.推荐系统业务流程 1. ...

  9. 探花交友_第6章_圈子互动(新版)

    探花交友_第6章_圈子互动(新版) 文章目录 探花交友_第6章_圈子互动(新版) 课程说明 1. 动态查询 1.1 查询好友动态 1.1.1 接口文档 1.1.2 代码步骤 1.1.3 代码实现 ta ...

最新文章

  1. 2016年,你该如何在 Facebook 上做营销?
  2. macOS 的头文件隐藏这么深
  3. java jsp 文件上传_用JSP编写文件上传
  4. fileinputstream resources 读取文件_压缩20M文件从30秒到1秒,包教包会
  5. 通过Serverless技术降低微服务应用资源成本
  6. ThreadLocal知识点详解
  7. linux 复用寄存器,I/O多路复用一些概念
  8. [码海拾贝 之Perl]在字符串数组中查找特定的字符串是否存在
  9. c++ 数据类型转换: static_cast、dynamic_cast、reinterpret_cast和const_cast
  10. ffmpeg 源代码简单分析 : av_register_all()
  11. 什么叫计算机硬件特征码,获取计算机硬件特征码【上】
  12. python 单词拆音节_基于Trie树进行拆分字符串变成拼音音节(一):构建拼音音节模型...
  13. 66个求职应聘技巧性问答(一)
  14. 大学计算机基础四大专业课,《大学计算机基础》课程教学大纲.doc
  15. Intellij IDEA2017.3.5利用补丁破解
  16. C# 中的委托和事件1
  17. finalize方法的使用
  18. oracle mysql 卸载_Oracle完全卸载详解
  19. 计算机在中药材的应用,计算机在中医药中的应用
  20. 【Leetcode】1641. Count Sorted Vowel Strings

热门文章

  1. 《神经质人格》摘录(第九章)
  2. 阿里影业已完成质变,何时量变?
  3. CSS的选择器(超详细)
  4. 获取订单交易的详细信息api接口
  5. 巴拿马草帽matlab,MATLAB学习绘制图形.ppt
  6. 元旦:CS的陋室2021年热门文章小结——NLP技术、学习与成长
  7. Corel 会声会影 2023 旗舰版雨糖科技v26.0.0.163特别版新功能介绍
  8. 【JAVA秘籍功法篇-SpringBoot】SpringBoot如何集成Redis?
  9. 【Nvidia DeepStream5.0】Gst-nvtracker插件功能说明
  10. C语言程序设计实践(OJ)-递归函数与宏