实现首页功能

首页

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

今日佳人

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

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

流程:

部署MongoDB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#拉取镜像
docker pull mongo:4.0.3

#创建容器
docker create --name mongodb --restart=always -p 27017:27017 -v mongodb:/data/db mongo:4.0.3

#启动容器
docker start mongodb

#进入容器
docker exec -it mongodb /bin/bash

#使用MongoDB客户端进行操作
mongo
> show dbs #查询所有的数据库
admin 0.000GB
config 0.000GB
local 0.000GB

通过Robo进行查询:

表结构设计

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

在MongoDB中只存储用户的id数据,其他的数据需要通过接口查询。

1
2
3
4
5
6
7
8
9
//生成数据代码
@Test
public void testMongoDBData() {
for (int i = 2; i < 100; i++) {
int score = RandomUtils.nextInt(30, 99);
System.out.println("db.recommend_user.insert({\"userId\":" + i +
",\"toUserId\":1,\"score\":"+score+",\"date\":\"2019/1/1\"})");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#构造一些测试数据
use tanhua

db.recommend_user.insert({"userId":2,"toUserId":1,"score":31,"date":"2019/1/1"})
db.recommend_user.insert({"userId":3,"toUserId":1,"score":54,"date":"2019/1/1"})
db.recommend_user.insert({"userId":4,"toUserId":1,"score":78,"date":"2019/1/1"})
db.recommend_user.insert({"userId":5,"toUserId":1,"score":57,"date":"2019/1/1"})
db.recommend_user.insert({"userId":6,"toUserId":1,"score":79,"date":"2019/1/1"})
db.recommend_user.insert({"userId":7,"toUserId":1,"score":31,"date":"2019/1/1"})
db.recommend_user.insert({"userId":8,"toUserId":1,"score":39,"date":"2019/1/1"})
db.recommend_user.insert({"userId":9,"toUserId":1,"score":68,"date":"2019/1/1"})
db.recommend_user.insert({"userId":10,"toUserId":1,"score":58,"date":"2019/1/1"})
db.recommend_user.insert({"userId":11,"toUserId":1,"score":47,"date":"2019/1/1"})
db.recommend_user.insert({"userId":12,"toUserId":1,"score":91,"date":"2019/1/1"})
db.recommend_user.insert({"userId":13,"toUserId":1,"score":47,"date":"2019/1/1"})
db.recommend_user.insert({"userId":14,"toUserId":1,"score":83,"date":"2019/1/1"})
db.recommend_user.insert({"userId":15,"toUserId":1,"score":81,"date":"2019/1/1"})
db.recommend_user.insert({"userId":16,"toUserId":1,"score":96,"date":"2019/1/1"})
db.recommend_user.insert({"userId":17,"toUserId":1,"score":38,"date":"2019/1/1"})
db.recommend_user.insert({"userId":18,"toUserId":1,"score":91,"date":"2019/1/1"})
db.recommend_user.insert({"userId":19,"toUserId":1,"score":39,"date":"2019/1/1"})
db.recommend_user.insert({"userId":20,"toUserId":1,"score":35,"date":"2019/1/1"})
db.recommend_user.insert({"userId":21,"toUserId":1,"score":74,"date":"2019/1/1"})
db.recommend_user.insert({"userId":22,"toUserId":1,"score":33,"date":"2019/1/1"})
db.recommend_user.insert({"userId":23,"toUserId":1,"score":77,"date":"2019/1/1"})
db.recommend_user.insert({"userId":24,"toUserId":1,"score":74,"date":"2019/1/1"})
db.recommend_user.insert({"userId":25,"toUserId":1,"score":49,"date":"2019/1/1"})
db.recommend_user.insert({"userId":26,"toUserId":1,"score":63,"date":"2019/1/1"})
db.recommend_user.insert({"userId":27,"toUserId":1,"score":63,"date":"2019/1/1"})
db.recommend_user.insert({"userId":28,"toUserId":1,"score":33,"date":"2019/1/1"})
db.recommend_user.insert({"userId":29,"toUserId":1,"score":32,"date":"2019/1/1"})
db.recommend_user.insert({"userId":30,"toUserId":1,"score":65,"date":"2019/1/1"})
db.recommend_user.insert({"userId":31,"toUserId":1,"score":73,"date":"2019/1/1"})
db.recommend_user.insert({"userId":32,"toUserId":1,"score":78,"date":"2019/1/1"})
db.recommend_user.insert({"userId":33,"toUserId":1,"score":78,"date":"2019/1/1"})
db.recommend_user.insert({"userId":34,"toUserId":1,"score":98,"date":"2019/1/1"})
db.recommend_user.insert({"userId":35,"toUserId":1,"score":65,"date":"2019/1/1"})
db.recommend_user.insert({"userId":36,"toUserId":1,"score":64,"date":"2019/1/1"})
db.recommend_user.insert({"userId":37,"toUserId":1,"score":79,"date":"2019/1/1"})
db.recommend_user.insert({"userId":38,"toUserId":1,"score":85,"date":"2019/1/1"})
db.recommend_user.insert({"userId":39,"toUserId":1,"score":97,"date":"2019/1/1"})
db.recommend_user.insert({"userId":40,"toUserId":1,"score":79,"date":"2019/1/1"})
db.recommend_user.insert({"userId":41,"toUserId":1,"score":62,"date":"2019/1/1"})
db.recommend_user.insert({"userId":42,"toUserId":1,"score":38,"date":"2019/1/1"})
db.recommend_user.insert({"userId":43,"toUserId":1,"score":56,"date":"2019/1/1"})
db.recommend_user.insert({"userId":44,"toUserId":1,"score":82,"date":"2019/1/1"})
db.recommend_user.insert({"userId":45,"toUserId":1,"score":36,"date":"2019/1/1"})
db.recommend_user.insert({"userId":46,"toUserId":1,"score":82,"date":"2019/1/1"})
db.recommend_user.insert({"userId":47,"toUserId":1,"score":58,"date":"2019/1/1"})
db.recommend_user.insert({"userId":48,"toUserId":1,"score":53,"date":"2019/1/1"})
db.recommend_user.insert({"userId":49,"toUserId":1,"score":53,"date":"2019/1/1"})
db.recommend_user.insert({"userId":50,"toUserId":1,"score":90,"date":"2019/1/1"})
db.recommend_user.insert({"userId":51,"toUserId":1,"score":79,"date":"2019/1/1"})
db.recommend_user.insert({"userId":52,"toUserId":1,"score":40,"date":"2019/1/1"})
db.recommend_user.insert({"userId":53,"toUserId":1,"score":36,"date":"2019/1/1"})
db.recommend_user.insert({"userId":54,"toUserId":1,"score":32,"date":"2019/1/1"})
db.recommend_user.insert({"userId":55,"toUserId":1,"score":54,"date":"2019/1/1"})
db.recommend_user.insert({"userId":56,"toUserId":1,"score":31,"date":"2019/1/1"})
db.recommend_user.insert({"userId":57,"toUserId":1,"score":92,"date":"2019/1/1"})
db.recommend_user.insert({"userId":58,"toUserId":1,"score":58,"date":"2019/1/1"})
db.recommend_user.insert({"userId":59,"toUserId":1,"score":70,"date":"2019/1/1"})
db.recommend_user.insert({"userId":60,"toUserId":1,"score":53,"date":"2019/1/1"})
db.recommend_user.insert({"userId":61,"toUserId":1,"score":97,"date":"2019/1/1"})
db.recommend_user.insert({"userId":62,"toUserId":1,"score":89,"date":"2019/1/1"})
db.recommend_user.insert({"userId":63,"toUserId":1,"score":63,"date":"2019/1/1"})
db.recommend_user.insert({"userId":64,"toUserId":1,"score":51,"date":"2019/1/1"})
db.recommend_user.insert({"userId":65,"toUserId":1,"score":43,"date":"2019/1/1"})
db.recommend_user.insert({"userId":66,"toUserId":1,"score":65,"date":"2019/1/1"})
db.recommend_user.insert({"userId":67,"toUserId":1,"score":45,"date":"2019/1/1"})
db.recommend_user.insert({"userId":68,"toUserId":1,"score":31,"date":"2019/1/1"})
db.recommend_user.insert({"userId":69,"toUserId":1,"score":40,"date":"2019/1/1"})
db.recommend_user.insert({"userId":70,"toUserId":1,"score":37,"date":"2019/1/1"})
db.recommend_user.insert({"userId":71,"toUserId":1,"score":42,"date":"2019/1/1"})
db.recommend_user.insert({"userId":72,"toUserId":1,"score":56,"date":"2019/1/1"})
db.recommend_user.insert({"userId":73,"toUserId":1,"score":59,"date":"2019/1/1"})
db.recommend_user.insert({"userId":74,"toUserId":1,"score":39,"date":"2019/1/1"})
db.recommend_user.insert({"userId":75,"toUserId":1,"score":81,"date":"2019/1/1"})
db.recommend_user.insert({"userId":76,"toUserId":1,"score":85,"date":"2019/1/1"})
db.recommend_user.insert({"userId":77,"toUserId":1,"score":59,"date":"2019/1/1"})
db.recommend_user.insert({"userId":78,"toUserId":1,"score":41,"date":"2019/1/1"})
db.recommend_user.insert({"userId":79,"toUserId":1,"score":39,"date":"2019/1/1"})
db.recommend_user.insert({"userId":80,"toUserId":1,"score":96,"date":"2019/1/1"})
db.recommend_user.insert({"userId":81,"toUserId":1,"score":65,"date":"2019/1/1"})
db.recommend_user.insert({"userId":82,"toUserId":1,"score":58,"date":"2019/1/1"})
db.recommend_user.insert({"userId":83,"toUserId":1,"score":37,"date":"2019/1/1"})
db.recommend_user.insert({"userId":84,"toUserId":1,"score":72,"date":"2019/1/1"})
db.recommend_user.insert({"userId":85,"toUserId":1,"score":48,"date":"2019/1/1"})
db.recommend_user.insert({"userId":86,"toUserId":1,"score":50,"date":"2019/1/1"})
db.recommend_user.insert({"userId":87,"toUserId":1,"score":77,"date":"2019/1/1"})
db.recommend_user.insert({"userId":88,"toUserId":1,"score":32,"date":"2019/1/1"})
db.recommend_user.insert({"userId":89,"toUserId":1,"score":45,"date":"2019/1/1"})
db.recommend_user.insert({"userId":90,"toUserId":1,"score":88,"date":"2019/1/1"})
db.recommend_user.insert({"userId":91,"toUserId":1,"score":59,"date":"2019/1/1"})
db.recommend_user.insert({"userId":92,"toUserId":1,"score":45,"date":"2019/1/1"})
db.recommend_user.insert({"userId":93,"toUserId":1,"score":80,"date":"2019/1/1"})
db.recommend_user.insert({"userId":94,"toUserId":1,"score":31,"date":"2019/1/1"})
db.recommend_user.insert({"userId":95,"toUserId":1,"score":61,"date":"2019/1/1"})
db.recommend_user.insert({"userId":96,"toUserId":1,"score":87,"date":"2019/1/1"})
db.recommend_user.insert({"userId":97,"toUserId":1,"score":80,"date":"2019/1/1"})
db.recommend_user.insert({"userId":98,"toUserId":1,"score":59,"date":"2019/1/1"})
db.recommend_user.insert({"userId":99,"toUserId":1,"score":49,"date":"2019/1/1"})

#无条件查询,看数据是否写入去了
> show tables
recommend_user
> db.recommend_user.find({})


#创建索引,toUserId:正序,score:倒序
db.recommend_user.createIndex({'toUserId':1,'score':-1})

生成相应的用户信息数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//生成数据的代码
@Test
public void testMySQLData(){
System.out.println("INSERT INTO `tb_user` (`id`, `mobile`,`password`, `created`, `updated`) VALUES ('1', '17602026868', 'e10adc3949ba59abbe56e057f20f883e', '2019-08-02 16:43:46', '2019-08-02 16:43:46');");
System.out.println("INSERT INTO `tb_user` (`id`, `mobile`,`password`, `created`, `updated`) VALUES ('2', '15800807988', 'e10adc3949ba59abbe56e057f20f883e', '2019-08-02 16:50:32', '2019-08-02 16:50:32');");
for (int i = 3; i < 100; i++) {
String mobile = "13"+RandomStringUtils.randomNumeric(9);
System.out.println("INSERT INTO `tb_user` (`id`, `mobile`,`password`, `created`, `updated`) VALUES ('"+i+"', '"+mobile+"', 'e10adc3949ba59abbe56e057f20f883e', '2019-08-02 16:43:46', '2019-08-02 16:43:46');");
}
System.out.println("INSERT INTO `tb_user_info`(`id`,`user_id`,`nick_name`, `logo`, `tags`, `sex`, `age`, `edu`, `city`, `birthday`,`cover_pic`, `industry`, `income`, `marriage`, `created`, `updated`) VALUES ('1', '1', 'heima', 'https://itcast-tanhua.oss-cn-shanghai.aliyuncs.com/images/logo/21.jpg', '单身,本科,年龄相仿', '1', '30', '本科', '北京市-北京城区-东城区', '2019-08-01', 'https://itcast-tanhua.oss-cn-shanghai.aliyuncs.com/images/logo/21.jpg', '计算机行业', '30', '已婚', '2019- 08-02 16:44:23', '2019-08-02 16:44:23');");


System.out.println("INSERT INTO `tb_user_info` (`id`, `user_id`,`nick_name`, `logo`, `tags`, `sex`, `age`, `edu`, `city`, `birthday`,`cover_pic`, `industry`, `income`, `marriage`, `created`, `updated`) VALUES ('2', '2', 'heima_2', 'https://itcast-tanhua.oss-cn-shanghai.aliyuncs.com/images/logo/22.jpg', '单身,本科,年龄相仿', '1', '30', '本科', '北京市-北京城区-东城区', '2019-08-01', 'https://itcast-tanhua.oss-cn- shanghai.aliyuncs.com/images/logo/22.jpg', '计算机行业', '30', '已婚', '2019- 08-02 16:44:23', '2019-08-02 16:44:23');");
for (int i = 3; i < 100; i++) {
String logo = "https://itcast-tanhua.oss-cn-shanghai.aliyuncs.com/images/logo/"+RandomUtils.nextInt(1,20)+".jpg";
System.out.println("INSERT INTO `tb_user_info` (`id`,`user_id`, `nick_name`, `logo`, `tags`, `sex`, `age`, `edu`, `city`,`birthday`, `cover_pic`, `industry`, `income`, `marriage`, `created`,`updated`) VALUES ('"+i+"', '"+i+"', 'heima_"+i+"', '"+logo+"', '单身,本科,年 龄相仿', '1', '"+RandomUtils.nextInt(20,50)+"', '本科', '北京市-北京城区-东城 区', '2019-08-01', '"+logo+"', '计算机行业', '40', '未婚', '2019-08-02 16:44:23', '2019-08-02 16:44:23');");
}
}

搭建工程

系统采用Dubbo构建,首先开发的是dubbo服务工程。

子工程(接口定义):

子工程(服务实现):

pom.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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>com.javami..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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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>com.javami.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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<?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>
<dependency>
<groupId>com.javami.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>
<version>0.2.0</version>
</dependency>
<!--dubbo框架-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.4</version>
</dependency>
<!--zk依赖-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.13</version>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</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>
<version>3.9.1</version>
</dependency>
<!--其他工具包依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.9</version>
</dependency>

<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.32.Final</version>
</dependency>


</dependencies>


</project>

编写dubbo服务

编写接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.tanhua.dubbo.server.api;

import com.tanhua.dubbo.server.pojo.RecommendUser;
import com.tanhua.dubbo.server.vo.PageInfo;

public interface RecommendUserApi {

/**
* 查询一位得分最高的推荐用户
*
* @param userId
* @return
*/
RecommendUser queryWithMaxScore(Long userId);

/**
* 按照得分倒序
*
* @param userId
* @param pageNum
* @param pageSize
* @return
*/
PageInfo<RecommendUser> queryPageInfo(Long userId, Integer pageNum, Integer pageSize);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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;

// 标注在实体类上,类似于hibernate的entity注解,标明由mongo来维护该表。
@AllArgsConstructor
@NoArgsConstructor
@Data
@Document(collection = "recommend_user")
public class RecommendUser implements java.io.Serializable {

private static final long serialVersionUID = 5874126532504390567L;

@Id
private ObjectId id; //主键id
@Indexed
private Long userId; //推荐的用户id
private Long toUserId; //用户id
@Indexed
private Double score; //推荐得分
private String date; //日期
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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;

/**
* 当前页
*/
private Integer pageNum;

/**
* 一页显示的大小
*/
private Integer pageSize;

/**
* 数据列表
*/
private List<T> records = Collections.emptyList();

}

编写实现

application.properties:

1
2
3
4
5
6
7
8
9
10
11
12
13
# Spring boot application
spring.application.name = itcast-tanhua-dubbo-service

dubbo.scan.basePackages = com.tanhua.dubbo.server
dubbo.application.name = dubbo-provider-tanhua

dubbo.protocol.name = dubbo
dubbo.protocol.port = 20880

dubbo.registry.address = zookeeper://192.168.31.81:2181
dubbo.registry.client = zkclient

spring.data.mongodb.uri=mongodb://192.168.31.81:27017/tanhua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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.Pageable;
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")
public class RecommendUserApiImpl implements RecommendUserApi {

@Autowired
private MongoTemplate mongoTemplate;

/**
* 今日佳人
*
* @param userId
* @return
*/
@Override
public RecommendUser queryWithMaxScore(Long userId) {
// 条件
Criteria criteria = Criteria.where("toUserId").is(userId);
// 按照得分倒序排序,获取第一条数据
Query query = Query.query(criteria).with(Sort.by(Sort.Order.desc("score")))
.limit(1);
return this.mongoTemplate.findOne(query, RecommendUser.class);
}

/**
* 推荐用户的列表查询
*
* @param userId
* @param pageNum
* @param pageSize
* @return
*/
@Override
public PageInfo<RecommendUser> queryPageInfo(Long userId, Integer pageNum, Integer pageSize) {
// 条件
Criteria criteria = Criteria.where("toUserId").is(userId);
Pageable pageable = PageRequest.of(pageNum - 1, pageSize, Sort.by(Sort.Order.desc("score")));
Query query = Query.query(criteria).with(pageable);
List<RecommendUser> recommendUsers = this.mongoTemplate.find(query, RecommendUser.class);

// 说明:数据总条数,暂时不提供,如果需要的话再提供
return new PageInfo<>(0, pageNum, pageSize, recommendUsers);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
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);
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.tanhua.dubbo.server.api;

import com.tanhua.dubbo.server.pojo.RecommendUser;
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;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class TestRecommendUserApi {

@Autowired
private RecommendUserApi recommendUserApi;

@Test
public void testQueryWithMaxScore() {
RecommendUser recommendUser = this.recommendUserApi.queryWithMaxScore(1L);
System.out.println(recommendUser);
}

@Test
public void testList(){
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));
}

}

搭建接口服务工程

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<?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>com.javami.tanhua</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>my-tanhua-server</artifactId>
<dependencies>
<dependency>
<groupId>com.javami.tanhua</groupId>
<artifactId>my-tanhua-dubbo-interface</artifactId>
<version>1.0-SNAPSHOT</version>
<!--<exclusions>-->
<!--<exclusion>-->
<!--<groupId>org.springframework.boot</groupId>-->
<!--<artifactId>spring-boot-starter-data-mongodb</artifactId>-->
<!--</exclusion>-->
<!--</exclusions>-->
</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>
<version>0.2.0</version>
</dependency>
<!--dubbo框架-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.4</version>
</dependency>
<!--zk依赖-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.13</version>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<!--其他工具包依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.mybatis-plus}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>


</project>

application.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
spring.application.name = itcast-tanhua-server
server.port = 18081

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1: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.application.name = itcast-tanhua-server
dubbo.registry.address = zookeeper://192.168.31.81:2181
dubbo.registry.client = zkclient

# 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

tanhua.sso.url=http://127.0.0.1:18080
#默认今日佳人推荐用户
tanhua.sso.default.user=2

#是否开启数据缓存
tanhua.cache.enable=true

ServerApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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})
public class ServerApplication {

public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}

RestTemplateConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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 {

@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
RestTemplate restTemplate = new RestTemplate(factory);
// 支持中文编码
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(Charset.forName("UTF-8")));
return restTemplate;
}

@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(5000);//单位为ms
factory.setConnectTimeout(5000);//单位为ms
return factory;
}
}

实现今日佳人服务

mock服务

地址:https://mock.boxuegu.com/project/164/interface/api/64568


基础代码

SexEnum
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.tanhua.server.enums;

import com.baomidou.mybatisplus.core.enums.IEnum;

public enum SexEnum implements IEnum<Integer> {

MAN(1,"男"),
WOMAN(2,"女"),
UNKNOWN(3,"未知");

private int value;
private String desc;

SexEnum(int value, String desc) {
this.value = value;
this.desc = desc;
}

@Override
public Integer getValue() {
return this.value;
}

@Override
public String toString() {
return this.desc;
}
}
BasePojo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}
User
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.tanhua.server.pojo;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User extends BasePojo {

private Long id;
private String mobile; //手机号

@JsonIgnore
private String password; //密码,json序列化时忽略

}
UserInfo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.tanhua.server.pojo;

import com.tanhua.server.enums.SexEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo extends BasePojo {

private Long id;
private Long userId; //用户id
private String nickName; //昵称
private String logo; //用户头像
private String tags; //用户标签:多个用逗号分隔
private SexEnum sex; //性别
private Integer age; //年龄
private String edu; //学历
private String city; //城市
private String birthday; //生日
private String coverPic; // 封面图片
private String industry; //行业
private String income; //收入
private String marriage; //婚姻状态

}

实现功能

实现描述:

  • 需要根据前端定义的结构定义java对象
  • 根据sso系统提供的接口查询当前登录用户的信息
  • 根据dubbo系统提供的服务进行查询今日佳人数据
TodayBest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.tanhua.server.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* 今日佳人
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TodayBest {

private Long id;
private String avatar;
private String nickname;
private String gender; //性别 man woman
private Integer age;
private String[] tags;
private Long fateValue; //缘分值

}
TodayBestController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.tanhua.server.controller;

import com.tanhua.server.service.TodayBestService;
import com.tanhua.server.vo.PageResult;
import com.tanhua.server.vo.RecommendUserQueryParam;
import com.tanhua.server.vo.TodayBest;
import org.springframework.beans.factory.annotation.Autowired;
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")
public class TodayBestController {

@Autowired
private TodayBestService todayBestService;

@GetMapping("todayBest")
public TodayBest queryTodayBest(@RequestHeader("Authorization") String token) {
return this.todayBestService.queryTodayBest(token);
}

}
TodayBestService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.tanhua.server.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tanhua.dubbo.server.pojo.RecommendUser;
import com.tanhua.dubbo.server.vo.PageInfo;
import com.tanhua.server.pojo.User;
import com.tanhua.server.pojo.UserInfo;
import com.tanhua.server.vo.PageResult;
import com.tanhua.server.vo.RecommendUserQueryParam;
import com.tanhua.server.vo.TodayBest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Service
public class TodayBestService {

@Autowired
private UserService userService;

@Autowired
private UserInfoService userInfoService;

@Autowired
private RecommendUserService recommendUserService;

@Value("${tanhua.sso.default.user}")
private Long defaultUserId;


public TodayBest queryTodayBest(String token) {

//根据token查询当前登录的用户信息
User user = this.userService.queryUserByToken(token);
if (user == null) {
return null;
}

TodayBest todayBest = this.recommendUserService.queryTodayBest(user.getId());
if (todayBest == null) {
//未找到最高得分的推荐用户,给出一个默认推荐用户
todayBest = new TodayBest();
todayBest.setId(defaultUserId);
todayBest.setFateValue(95L);
}

// 补全用户信息
UserInfo userInfo = this.userInfoService.queryUserInfoById(todayBest.getId());
if (null != userInfo) {
todayBest.setAge(userInfo.getAge());
todayBest.setAvatar(userInfo.getLogo());
todayBest.setGender(userInfo.getSex().name().toLowerCase());
todayBest.setNickname(userInfo.getNickName());
todayBest.setTags(StringUtils.split(userInfo.getTags(), ','));
}

return todayBest;
}
}
UserService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.tanhua.server.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.tanhua.server.pojo.User;
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
public class UserService {

@Autowired
private RestTemplate restTemplate;

@Value("${tanhua.sso.url}")
private String url;

private static final ObjectMapper MAPPER = new ObjectMapper();


/**
* 调用SSO系统中的接口服务进行查询
*
* @param token
* @return 如果查询到,返回User对象,如果未查询到就反会null
*/
public User queryUserByToken(String token) {
String jsonData = this.restTemplate.getForObject(url + "/user/" + token, String.class);
if(StringUtils.isNotEmpty(jsonData)){
try {
return MAPPER.readValue(jsonData, User.class);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
RecommendUserService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.tanhua.server.service;

import com.alibaba.dubbo.config.annotation.Reference;
import com.tanhua.dubbo.server.api.RecommendUserApi;
import com.tanhua.dubbo.server.pojo.RecommendUser;
import com.tanhua.dubbo.server.vo.PageInfo;
import com.tanhua.server.vo.TodayBest;
import org.springframework.stereotype.Service;

@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();
// 如果得分为98.2时,需要显示的得分是98
double score = Math.floor(recommendUser.getScore());
todayBest.setFateValue(Double.valueOf(score).longValue()); //缘分值

todayBest.setId(recommendUser.getUserId()); //用户id

return todayBest;
}

}
UserInfoService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.tanhua.server.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tanhua.server.mapper.UserInfoMapper;
import com.tanhua.server.pojo.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserInfoService {

@Autowired
private UserInfoMapper userInfoMapper;

/**
* 查询数据库,查找用户的信息数据
* 说明:为了简单处理,直接查询数据库了,建议:编写dubbo服务,进行调用
* @param id
* @return
*/
public UserInfo queryUserInfoById(Long id) {
return this.userInfoMapper.selectById(id);
}
}

测试

单元测试,测试dubbo服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.tanhua.server;

import com.tanhua.server.service.TodayBestService;
import com.tanhua.server.vo.PageResult;
import com.tanhua.server.vo.RecommendUserQueryParam;
import com.tanhua.server.vo.TodayBest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class TestTodayBest {

@Autowired
private TodayBestService todayBestService;

@Test
public void testQueryTodayBest(){
String token = "eyJhbGciOiJIUzI1NiJ9.eyJtb2JpbGUiOiIxNzYwMjAyNjg2OCIsImlkIjoxfQ.OzoVY9yavYS-albcNGlYg1kKK2pp3QffrmcopfI3IhY";
TodayBest todayBest = this.todayBestService.queryTodayBest(token);
System.out.println(todayBest);
}

}

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

结果:

实现过程中,枚举的bug:
由于之前设计的tb_user_info表中,sex字段类型为tinyint,导致枚举类型在读取数据时不能正确映射数据,需要将类型改为int。否则会报错!

解决MongoDB启动bug

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

解决有2种方案:

  • 排除掉mongo的依赖
  • springboot中添加排除自动配置的注解
1
2
3
4
5
6
7
8
@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);
}
}

统一接口服务入口

现在我们有sso和server需要对外提供接口服务,而在前端只能设置一个请求地址,所以我们需要将服务接口统一下,需要使用nginx进行统一入口。

部署安装nginx

安装包在资料中:nginx-1.17.3.zip
安装在任意目录,通过命令:start nginx.exe 启动:

重启加载配置文件命令:nginx.exe -s reload

修改配置

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
worker_processes  1;
events {
worker_connections 1024;
}


http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;

server {
listen 80;
server_name localhost;


error_page 500 502 503 504 /50x.html;

location = /50x.html {
root html;
}

location /user {
proxy_pass http://127.0.0.1:18080;
}

location / {
proxy_pass http://127.0.0.1:18081;
}

}
}

测试


推荐列表

mock接口

地址:https://mock.boxuegu.com/project/164/interface/api/64561

响应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
{
"counts": 4698,
"pagesize": 20,
"pages": 58,
"page": 16,
"items": [
{
"id": 1011,
"avatar": "assets/images/avatar_2.png",
"nickname": "黑马小妹",
"gender": "woman",
"age": 23,
"tags": [
"本科",
"年龄相仿",
"单身"
],
"fateValue": 96
},
{
"id": 5708,
"avatar": "assets/images/avatar_4.png",
"nickname": "黑马小妹",
"gender": "man",
"age": 24,
"tags": [
"单身",
"本科",
"年龄相仿"
],
"fateValue": 94
},
{
"id": 4768,
"avatar": "assets/images/avatar_3.png",
"nickname": "黑马小妹",
"gender": "man",
"age": 24,
"tags": [
"年龄相仿",
"单身",
"本科"
],
"fateValue": 80
}
]
}

查询参数对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.tanhua.server.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class RecommendUserQueryParam {

private Integer page = 1; //当前页数
private Integer pagesize = 10; //页尺寸
private String gender; //性别 man woman
private String lastLogin; //近期登陆时间
private Integer age; //年龄
private String city; //居住地
private String education; //学历
}

Controller

1
2
3
4
@GetMapping("recommendation")
public PageResult queryRecommendUserList(RecommendUserQueryParam queryParam, @RequestHeader("Authorization") String token) {
return this.todayBestService.queryRecommendUserList(queryParam, token);
}

Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public PageResult queryRecommendUserList(RecommendUserQueryParam queryParam, String token) {
//根据token查询当前登录的用户信息
User user = this.userService.queryUserByToken(token);
if (user == null) {
return null;
}

PageInfo<RecommendUser> pageInfo = this.recommendUserService.queryRecommendUserList(user.getId(), queryParam.getPage(), queryParam.getPagesize());
List<RecommendUser> records = pageInfo.getRecords();
List<Long> userIds = new ArrayList<>();
for (RecommendUser record : records) {
userIds.add(record.getUserId());
}


QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.in("user_id", userIds); //用户id

if (queryParam.getAge() != null) {
queryWrapper.lt("age", queryParam.getAge()); //年龄
}

if (StringUtils.isNotEmpty(queryParam.getCity())) {
queryWrapper.eq("city", queryParam.getCity()); //城市
}

//需要查询用户的信息,并且按照条件查询
List<UserInfo> userInfos = this.userInfoService.queryUserInfoList(queryWrapper);
List<TodayBest> todayBests = new ArrayList<>();
for (UserInfo userInfo : userInfos) {
TodayBest todayBest = new TodayBest();

todayBest.setId(userInfo.getUserId());
todayBest.setAge(userInfo.getAge());
todayBest.setAvatar(userInfo.getLogo());
todayBest.setGender(userInfo.getSex().name().toLowerCase());
todayBest.setNickname(userInfo.getNickName());
todayBest.setTags(StringUtils.split(userInfo.getTags(), ','));

for (RecommendUser record : records) {
if(record.getUserId().longValue() == todayBest.getId().longValue()){
double score = Math.floor(record.getScore());
todayBest.setFateValue(Double.valueOf(score).longValue()); //缘分值
}
}

todayBests.add(todayBest);
}

//对结果集做排序,按照缘分值倒序排序
Collections.sort(todayBests, (o1, o2) -> Long.valueOf(o2.getFateValue() - o1.getFateValue()).intValue());

return new PageResult(0, queryParam.getPagesize(), 0, queryParam.getPage(), todayBests);
}

测试


缓存

实现缓存逻辑有2种方式:

  1. 每个接口单独控制缓存逻辑
  2. 统一控制缓存逻辑

采用拦截器进行缓存命中

编写拦截器:RedisCacheInterceptor。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package com.tanhua.server.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
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.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

@Component
public class RedisCacheInterceptor implements HandlerInterceptor {

private static ObjectMapper mapper = new ObjectMapper();

@Autowired
private RedisTemplate<String, String> redisTemplate;

@Value("${tanhua.cache.enable}")
private Boolean enable;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

if (!enable) {
//未开启缓存
return true;
}

String method = request.getMethod();
if (!StringUtils.equalsAnyIgnoreCase(method, "GET", "POST")) {
// 非GET、POST的请求不进行缓存处理
return true;
}

// 通过缓存做命中,查询redis,redisKey ? 组成:md5(请求的url + 请求参数)
String redisKey = createRedisKey(request);
String data = this.redisTemplate.opsForValue().get(redisKey);
if (StringUtils.isEmpty(data)) {
// 缓存未命中
return true;
}

// 将data数据进行响应
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(data);

return false;
}

public static String createRedisKey(HttpServletRequest request) throws
Exception {
String paramStr = request.getRequestURI();
Map<String, String[]> parameterMap = request.getParameterMap();
if (parameterMap.isEmpty()) {
//请求体的数据只能读取一次,需要进行包装Request进行解决
paramStr += IOUtils.toString(request.getInputStream(), "UTF-8");
} else {
paramStr += mapper.writeValueAsString(request.getParameterMap());
}

String authorization = request.getHeader("Authorization");
if (StringUtils.isNotEmpty((authorization))) {
paramStr += "_" + authorization;
}

return "SERVER_DATA_" + DigestUtils.md5Hex(paramStr);
}
}

application.properties:

1
2
#是否开启数据缓存
tanhua.cache.enable=false

注册拦截器到Spring容器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 {

@Autowired
private RedisCacheInterceptor redisCacheInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this.redisCacheInterceptor).addPathPatterns("/**");
}
}

包装request对象

由于在拦截器中读取了输入流的数据,在request中的输入流只能读取一次,请求进去Controller时,输入流中已经没有数据了,导致获取不到数据。

编写HttpServletRequest的包装类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package com.tanhua.server.interceptor;

import org.apache.commons.io.IOUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
* 包装HttpServletRequest
*/
public class MyServletRequestWrapper extends HttpServletRequestWrapper {

private final byte[] body;

/**
* Construct a wrapper for the specified request.
*
* @param request The request to be wrapped
*/
public MyServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = IOUtils.toByteArray(super.getInputStream());
}

@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}

@Override
public ServletInputStream getInputStream() throws IOException {
return new RequestBodyCachingInputStream(body);
}

private class RequestBodyCachingInputStream extends ServletInputStream {
private byte[] body;
private int lastIndexRetrieved = -1;
private ReadListener listener;

public RequestBodyCachingInputStream(byte[] body) {
this.body = body;
}

@Override
public int read() throws IOException {
if (isFinished()) {
return -1;
}
int i = body[lastIndexRetrieved + 1];
lastIndexRetrieved++;
if (isFinished() && listener != null) {
try {
listener.onAllDataRead();
} catch (IOException e) {
listener.onError(e);
throw e;
}
}
return i;
}

@Override
public boolean isFinished() {
return lastIndexRetrieved == body.length - 1;
}

@Override
public boolean isReady() {
// This implementation will never block
// We also never need to call the readListener from this method, as this method will never return false
return isFinished();
}

@Override
public void setReadListener(ReadListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener cann not be null");
}
if (this.listener != null) {
throw new IllegalArgumentException("listener has been set");
}
this.listener = listener;
if (!isFinished()) {
try {
listener.onAllDataRead();
} catch (IOException e) {
listener.onError(e);
}
} else {
try {
listener.onAllDataRead();
} catch (IOException e) {
listener.onError(e);
}
}
}

@Override
public int available() throws IOException {
return body.length - lastIndexRetrieved - 1;
}

@Override
public void close() throws IOException {
lastIndexRetrieved = body.length - 1;
body = null;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.tanhua.server.interceptor;

import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
* 替换Request对象
*/
@Component
public class RequestReplaceFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (!(request instanceof MyServletRequestWrapper)) {
request = new MyServletRequestWrapper(request);
}
filterChain.doFilter(request, response);
}
}

测试:

响应结果写入到缓存

前面已经完成了缓存命中的逻辑,那么在查询到数据后,如果将结果写入到缓存呢?

思考:通过拦截器可以实现吗?

通过ResponseBodyAdvice进行实现。

ResponseBodyAdvice是Spring提供的高级用法,会在结果被处理前进行拦截,拦截的逻辑自己实现,这样就可以实现拿到结果数据进行写入缓存的操作了。

具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.tanhua.server.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
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.bind.annotation.PostMapping;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.time.Duration;

@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice {

@Autowired
private RedisTemplate<String, String> redisTemplate;

private ObjectMapper mapper = new ObjectMapper();

@Override
public boolean supports(MethodParameter returnType, Class converterType) {
//支持的处理类型,这里仅对get和post进行处理
return returnType.hasMethodAnnotation(GetMapping.class) || returnType.hasMethodAnnotation(PostMapping.class);
}

@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
try {
String redisKey = RedisCacheInterceptor.createRedisKey(((ServletServerHttpRequest) request).getServletRequest());
String redisValue;
if (body instanceof String) {
redisValue = (String) body;
} else {
redisValue = mapper.writeValueAsString(body);
}
//存储到redis中
this.redisTemplate.opsForValue().set(redisKey, redisValue, Duration.ofHours(1));
} catch (Exception e) {
e.printStackTrace();
}
return body;
}
}

到目前为止,缓存逻辑添加完成。

整合测试

将资料中的app-debug-004.apk进行安装,并且修改服务地址进行测试。