├── .gitignore
├── README.md
├── pom.xml
└── src
├── main
├── java
│ └── com
│ │ └── fengzhu
│ │ └── reading
│ │ ├── CommonResponse.java
│ │ ├── Constants.java
│ │ ├── ReadingApplication.java
│ │ ├── config
│ │ ├── AuthWebMvcConfigurer.java
│ │ ├── ElasticSearchConfig.java
│ │ ├── RabbitmqConfig.java
│ │ ├── RedisConfig.java
│ │ └── SpringDocConfig.java
│ │ ├── consumer
│ │ └── LikesMessageConsumer.java
│ │ ├── controller
│ │ ├── BookController.java
│ │ ├── BookRatingController.java
│ │ ├── BookReviewController.java
│ │ ├── LikesController.java
│ │ ├── TagController.java
│ │ └── UserController.java
│ │ ├── converter
│ │ ├── BookAvgRatingConverter.java
│ │ ├── BookConverter.java
│ │ ├── BookRatingConverter.java
│ │ ├── BookReviewConverter.java
│ │ ├── LikesUserRecordConverter.java
│ │ ├── TagConverter.java
│ │ ├── TagGroupConverter.java
│ │ └── UserConverter.java
│ │ ├── dataObject
│ │ ├── BaseEntity.java
│ │ ├── Book.java
│ │ ├── BookAvgRating.java
│ │ ├── BookES.java
│ │ ├── BookRating.java
│ │ ├── BookReview.java
│ │ ├── BookTag.java
│ │ ├── LikesBusiness.java
│ │ ├── LikesStatistic.java
│ │ ├── LikesUserRecord.java
│ │ ├── Tag.java
│ │ ├── TagGroup.java
│ │ └── User.java
│ │ ├── dto
│ │ ├── BookAvgRatingDTO.java
│ │ ├── BookDTO.java
│ │ ├── BookRankDTO.java
│ │ ├── BookRatingDTO.java
│ │ ├── BookReviewDTO.java
│ │ ├── LikesUserRecordDTO.java
│ │ ├── TagDTO.java
│ │ ├── TagGroupDTO.java
│ │ └── UserDTO.java
│ │ ├── enums
│ │ └── Enable.java
│ │ ├── exceptions
│ │ └── TokenAuthExpiredException.java
│ │ ├── interceptors
│ │ ├── AuthHandlerInterceptor.java
│ │ └── GlobalExceptionHandler.java
│ │ ├── repository
│ │ ├── BookAvgRatingRepository.java
│ │ ├── BookESRepository.java
│ │ ├── BookRatingRepository.java
│ │ ├── BookRepository.java
│ │ ├── BookReviewRepository.java
│ │ ├── BookTagRepository.java
│ │ ├── LikesStatisticRepository.java
│ │ ├── LikesUserRecordRepository.java
│ │ ├── TagGroupRepository.java
│ │ ├── TagRepository.java
│ │ └── UserRepository.java
│ │ ├── service
│ │ ├── BookAvgRatingService.java
│ │ ├── BookRatingService.java
│ │ ├── BookReviewService.java
│ │ ├── BookService.java
│ │ ├── LikesService.java
│ │ ├── RabbitmqService.java
│ │ ├── TagService.java
│ │ ├── UserService.java
│ │ └── impl
│ │ │ ├── BookAvgRatingServiceImpl.java
│ │ │ ├── BookRatingServiceImpl.java
│ │ │ ├── BookReviewServiceImpl.java
│ │ │ ├── BookServiceImpl.java
│ │ │ ├── LikesServiceImpl.java
│ │ │ ├── TagServiceImpl.java
│ │ │ └── UserServiceImpl.java
│ │ ├── task
│ │ └── BookAvgScoreTask.java
│ │ ├── utils
│ │ ├── BaseContext.java
│ │ └── JwtUtils.java
│ │ └── validator
│ │ ├── BookRatingValidator.java
│ │ ├── BookReviewValidator.java
│ │ ├── BookValidator.java
│ │ └── LikesValidator.java
└── resources
│ └── application.properties
└── test
└── java
└── com
└── fengzhu
└── reading
└── ReadingApplicationTests.java
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**/target/
5 | !**/src/test/**/target/
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 | !**/src/main/**/build/
30 | !**/src/test/**/build/
31 |
32 | ### VS Code ###
33 | .vscode/
34 |
35 | .mvn/
36 | mvnw
37 | mvnw.cmd
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 基于spring boot的一个学习项目,会使用到redis, rabbitmq, elastic search等框架
2 | 部署简单,非常适合学习提高
3 |
4 | 实现一个类似豆瓣读书 [https://book.douban.com/](https://book.douban.com/) 类似功能的网站,后端系统API应该如何设计
5 |
6 | ### 业务需求
7 | + 支持用户登陆注册
8 | + 展示图书列表,需要分页,支持按标签过滤
9 | + 图书标签,按组别分类,展示所有标签的时候需要展示每个标签包含的图书
10 | + 展示畅销图书排行榜
11 | + 用户可以对图书评分,评论
12 | + 图书详情页面需要展示 图书基础信息,评分信息,书评列表
13 | + 点赞系统支持对图书,书评的点赞以及查看用户的点赞列表
14 | + 支持图书的全文搜索
15 |
16 |
17 |
18 | ### 数据库设计
19 | 
20 |
21 |
22 |
23 | ### API
24 | + 用户注册
25 | + 用户登陆
26 | + 新增图书 /book post
27 | + 更新图书 /book put
28 | + 查询图书列表,支持按标签过滤 /books get
29 | + 查询图书详情
30 | + 查询畅销图书排行榜 /book/rank get
31 | + 搜索图书(接入es) /book/search
32 | + 新增图书评分 /book/rating
33 | + 新增图书书评 /book/review
34 | + 查询图书书评列表 支持分页
35 | + 新增标签 /tag post
36 | + 查询所有图书标签,按标签组别分类 /tags get
37 | + 点赞接口,支持点赞图书,书评等
38 | + 查询用户的点赞列表
39 | + 查询图书或者书评的点赞总数
40 |
41 |
42 |
43 | ### 项目亮点
44 | #### 利用bitmap算法减少关联数据量,利用位运算提高效率
45 | #### 利用canal同步mysql数据到es, 使用es的全文搜索功能
46 | 安装es跟kibana: [https://www.cnblogs.com/benjieqiang/p/17501293.html](https://www.cnblogs.com/benjieqiang/p/17501293.html)
47 |
48 | 安装canal-server, canal-adapter: [https://www.macrozheng.com/project/canal_start.html](https://www.macrozheng.com/project/canal_start.html)
49 |
50 | #### 利用redis实现图书的热度排行榜
51 | #### spring boot定时任务同步数据
52 | #### 利用completableFuture提升图书详情接口性能
53 | #### 利用rabbitmq,redis实现支持高并发读写的点赞系统
54 |
55 |
56 | ### 启动依赖
57 | redis:docker安装
58 |
59 | rabbitmq: docker安装
60 |
61 | elastic search: docker安装
62 |
63 | 安装canal-server, canal-adapter: [https://www.macrozheng.com/project/canal_start.html](https://www.macrozheng.com/project/canal_start.html)
64 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 3.2.5
9 |
10 |
11 | com.fengzhu
12 | reading
13 | 0.0.1-SNAPSHOT
14 | reading
15 | a reading system simulating douban
16 |
17 | 17
18 |
19 |
20 |
21 | org.springframework.boot
22 | spring-boot-starter-data-jpa
23 |
24 |
25 | org.springframework.boot
26 | spring-boot-starter-web
27 |
28 |
29 |
30 | com.mysql
31 | mysql-connector-j
32 | runtime
33 |
34 |
35 |
36 | org.projectlombok
37 | lombok
38 | true
39 |
40 |
41 |
42 | org.springdoc
43 | springdoc-openapi-starter-webmvc-ui
44 | 2.3.0
45 |
46 |
47 |
48 | com.google.guava
49 | guava
50 | 32.1.3-jre
51 |
52 |
53 |
54 | org.springframework.boot
55 | spring-boot-starter-data-redis
56 |
57 |
58 | io.lettuce
59 | lettuce-core
60 |
61 |
62 |
63 |
64 |
65 | redis.clients
66 | jedis
67 |
68 |
69 | org.apache.commons
70 | commons-pool2
71 |
72 |
73 |
74 | org.springframework.boot
75 | spring-boot-starter-amqp
76 |
77 |
78 | org.springframework.amqp
79 | spring-rabbit-test
80 | test
81 |
82 |
83 |
84 | org.springframework.data
85 | spring-data-elasticsearch
86 |
87 |
88 |
89 | org.elasticsearch.client
90 | elasticsearch-rest-high-level-client
91 | 7.16.1
92 |
93 |
94 |
95 | com.alibaba.otter
96 | canal.client
97 | 1.1.6
98 |
99 |
100 |
101 | com.auth0
102 | java-jwt
103 | 3.10.3
104 |
105 |
106 |
107 |
108 | com.alibaba.otter
109 | canal.protocol
110 | 1.1.6
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | org.springframework.boot
122 | spring-boot-starter-test
123 | test
124 |
125 |
126 |
127 |
128 |
129 |
130 | org.springframework.boot
131 | spring-boot-maven-plugin
132 |
133 |
134 |
135 | org.projectlombok
136 | lombok
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
--------------------------------------------------------------------------------
/src/main/java/com/fengzhu/reading/CommonResponse.java:
--------------------------------------------------------------------------------
1 | package com.fengzhu.reading;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class CommonResponse {
7 |
8 | private T data;
9 | private boolean success;
10 | private String errorMsg;
11 |
12 | public static CommonResponse newSuccess(K data) {
13 | CommonResponse response = new CommonResponse<>();
14 | response.setData(data);
15 | response.setSuccess(true);
16 | return response;
17 | }
18 |
19 | public static CommonResponse newFail(String errorMsg, K data) {
20 | CommonResponse response = new CommonResponse<>();
21 | response.setErrorMsg(errorMsg);
22 | response.setSuccess(false);
23 | response.setData(data);
24 | return response;
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/fengzhu/reading/Constants.java:
--------------------------------------------------------------------------------
1 | package com.fengzhu.reading;
2 |
3 | public class Constants {
4 |
5 | public static final String BOOK_RANK_KEY = "book-rank";
6 |
7 | public static final String LIKE_USER_KEY = "likes-user-";
8 |
9 | public static final String LIKE_STATISTIC_KEY = "likes-statistic-";
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/fengzhu/reading/ReadingApplication.java:
--------------------------------------------------------------------------------
1 | package com.fengzhu.reading;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.scheduling.annotation.EnableScheduling;
6 |
7 | @SpringBootApplication
8 | @EnableScheduling
9 | public class ReadingApplication {
10 |
11 | public static void main(String[] args) {
12 | SpringApplication.run(ReadingApplication.class, args);
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/fengzhu/reading/config/AuthWebMvcConfigurer.java:
--------------------------------------------------------------------------------
1 | package com.fengzhu.reading.config;
2 |
3 | import com.fengzhu.reading.interceptors.AuthHandlerInterceptor;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
8 |
9 | @Configuration
10 | public class AuthWebMvcConfigurer implements WebMvcConfigurer {
11 |
12 | @Autowired
13 | private AuthHandlerInterceptor authHandlerInterceptor;
14 |
15 | @Override
16 | public void addInterceptors(InterceptorRegistry registry) {
17 | registry.addInterceptor(authHandlerInterceptor)
18 | .addPathPatterns("/**")
19 | .excludePathPatterns("/user/**", "/swagger-ui.html",
20 | "/swagger-ui/**", "/v3/api-docs/**");
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/fengzhu/reading/config/ElasticSearchConfig.java:
--------------------------------------------------------------------------------
1 | package com.fengzhu.reading.config;
2 |
3 | import org.springframework.context.annotation.Configuration;
4 | import org.springframework.data.elasticsearch.client.ClientConfiguration;
5 | import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration;
6 | import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
7 |
8 | @Configuration
9 | @EnableElasticsearchRepositories(basePackages = "com.fengzhu.reading.repository")
10 | public class ElasticSearchConfig extends ElasticsearchConfiguration {
11 |
12 | @Override
13 | public ClientConfiguration clientConfiguration() {
14 | return ClientConfiguration.builder()
15 | .connectedTo("localhost:9200").build();
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/fengzhu/reading/config/RabbitmqConfig.java:
--------------------------------------------------------------------------------
1 | package com.fengzhu.reading.config;
2 |
3 | import org.springframework.amqp.core.*;
4 | import org.springframework.amqp.rabbit.core.RabbitTemplate;
5 | import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
6 | import org.springframework.amqp.support.converter.MessageConverter;
7 | import org.springframework.beans.factory.annotation.Value;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.Configuration;
10 | import org.springframework.amqp.rabbit.connection.ConnectionFactory;
11 |
12 | import java.util.HashMap;
13 | import java.util.Map;
14 |
15 | @Configuration
16 | public class RabbitmqConfig {
17 |
18 | @Value("${rabbitmq.reading.queue}")
19 | private String queueName;
20 |
21 | @Value("${rabbitmq.reading.exchange}")
22 | private String exchange;
23 |
24 | @Value("${rabbitmq.reading.routingkey}")
25 | private String routingkey;
26 |
27 | @Value("${rabbitmq.reading.dlq.queue}")
28 | private String dlqQueueName;
29 |
30 | @Value("${rabbitmq.reading.dlq.exchange}")
31 | private String dlqExchange;
32 |
33 | @Value("${rabbitmq.reading.dlq.routingkey}")
34 | private String dlqRoutingkey;
35 |
36 | @Bean
37 | Queue queue() {
38 | Map params = new HashMap<>();
39 | params.put("x-dead-letter-exchange", dlqExchange);
40 | params.put("x-dead-letter-routing-key", dlqRoutingkey);
41 | return QueueBuilder.durable(queueName).withArguments(params).build();
42 | }
43 |
44 | @Bean
45 | DirectExchange exchange() {
46 | return new DirectExchange(exchange);
47 | }
48 |
49 | @Bean
50 | Binding binding(Queue queue, DirectExchange exchange) {
51 | return BindingBuilder.bind(queue).to(exchange).with(routingkey);
52 | }
53 |
54 | @Bean
55 | Queue dlqQueue() {
56 | return new Queue(dlqQueueName);
57 | }
58 |
59 | @Bean
60 | public DirectExchange dlqExchange(){
61 | return new DirectExchange(dlqExchange);
62 | }
63 |
64 | @Bean
65 | public MessageConverter jsonMessageConverter() {
66 | return new Jackson2JsonMessageConverter();
67 | }
68 |
69 | @Bean
70 | Binding dlqBinding(Queue dlqQueue, DirectExchange dlqExchange) {
71 | return BindingBuilder.bind(dlqQueue).to(dlqExchange).with(dlqRoutingkey);
72 | }
73 |
74 | @Bean
75 | public AmqpTemplate amqpTemplate(ConnectionFactory connectionFactory) {
76 | final RabbitTemplate template = new RabbitTemplate(connectionFactory);
77 | template.setMessageConverter(jsonMessageConverter());
78 | return template;
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/com/fengzhu/reading/config/RedisConfig.java:
--------------------------------------------------------------------------------
1 | package com.fengzhu.reading.config;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.data.redis.connection.RedisConnectionFactory;
6 | import org.springframework.data.redis.core.RedisTemplate;
7 | import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
8 | import org.springframework.data.redis.serializer.StringRedisSerializer;
9 |
10 | @Configuration
11 | public class RedisConfig {
12 |
13 | @Bean("redisTemplate")
14 | public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
15 | RedisTemplate redisTemplate = new RedisTemplate<>();
16 | redisTemplate.setConnectionFactory(factory);
17 | redisTemplate.setKeySerializer(new StringRedisSerializer());
18 | redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
19 | return redisTemplate;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/fengzhu/reading/config/SpringDocConfig.java:
--------------------------------------------------------------------------------
1 | package com.fengzhu.reading.config;
2 |
3 | import io.swagger.v3.oas.models.ExternalDocumentation;
4 | import io.swagger.v3.oas.models.OpenAPI;
5 | import io.swagger.v3.oas.models.info.Info;
6 | import io.swagger.v3.oas.models.info.License;
7 | import org.springframework.context.annotation.Bean;
8 | import org.springframework.context.annotation.Configuration;
9 |
10 |
11 | @Configuration
12 | public class SpringDocConfig {
13 |
14 | @Bean
15 | public OpenAPI api() {
16 | return new OpenAPI()
17 | .info(new Info().title("reading system api")
18 | .description("restful api")
19 | .version("v1.0.0")
20 | .license(new License().name("").url("")))
21 | .externalDocs(new ExternalDocumentation().description("")
22 | .url(""));
23 | }
24 | }
--------------------------------------------------------------------------------
/src/main/java/com/fengzhu/reading/consumer/LikesMessageConsumer.java:
--------------------------------------------------------------------------------
1 | package com.fengzhu.reading.consumer;
2 |
3 |
4 | import com.fengzhu.reading.converter.LikesUserRecordConverter;
5 | import com.fengzhu.reading.dataObject.LikesStatistic;
6 | import com.fengzhu.reading.dataObject.LikesUserRecord;
7 | import com.fengzhu.reading.dto.LikesUserRecordDTO;
8 | import com.fengzhu.reading.repository.LikesStatisticRepository;
9 | import com.fengzhu.reading.repository.LikesUserRecordRepository;
10 | import com.fengzhu.reading.validator.LikesValidator;
11 | import com.google.gson.Gson;
12 | import lombok.Data;
13 | import lombok.extern.slf4j.Slf4j;
14 | import org.springframework.amqp.rabbit.annotation.RabbitListener;
15 | import org.springframework.beans.factory.annotation.Autowired;
16 | import org.springframework.beans.factory.annotation.Value;
17 | import org.springframework.data.redis.core.RedisTemplate;
18 | import org.springframework.stereotype.Component;
19 |
20 | import static com.fengzhu.reading.Constants.LIKE_STATISTIC_KEY;
21 | import static com.fengzhu.reading.Constants.LIKE_USER_KEY;
22 |
23 | @Component
24 | @Slf4j
25 | @Data
26 | public class LikesMessageConsumer {
27 |
28 | @Value("${rabbitmq.reading.queue}")
29 | private String queueName;
30 |
31 | @Value("${rabbitmq.reading.exchange}")
32 | private String exchange;
33 |
34 | @Autowired
35 | private LikesUserRecordRepository likesUserRecordRepository;
36 |
37 | @Autowired
38 | private LikesStatisticRepository likesStatisticRepository;
39 |
40 | @Autowired
41 | private RedisTemplate redisTemplate;
42 |
43 | @Autowired
44 | private LikesValidator likesValidator;
45 |
46 | private Gson gson = new Gson();
47 |
48 | @RabbitListener(queues = "${rabbitmq.reading.queue}")
49 | public void handleMessage(LikesUserRecordDTO likesUserRecordDTO) {
50 | log.info("Received message from queue {}: message:{}", queueName, gson.toJson(likesUserRecordDTO));
51 | likesValidator.validateAddNewBook(likesUserRecordDTO);
52 | likesUserRecordRepository.findUserLikeRecord(likesUserRecordDTO.getUserId(), likesUserRecordDTO.getBusinessId(),
53 | likesUserRecordDTO.getItemId())
54 | .ifPresentOrElse(likesUserRecord -> doUnLikeAction(likesUserRecord), () -> doLikeAction(likesUserRecordDTO));
55 | }
56 |
57 | private void doLikeAction(LikesUserRecordDTO likesUserRecordDTO) {
58 | LikesUserRecord likesUserRecord = LikesUserRecordConverter.convertToLikesUserRecord(likesUserRecordDTO);
59 | likesUserRecordRepository.save(likesUserRecord);
60 |
61 | String likesStatisticKey = LIKE_STATISTIC_KEY + likesUserRecordDTO.getBusinessId() + ":" + likesUserRecordDTO.getItemId();
62 |
63 | likesStatisticRepository.findByBusinessIdAndItemId(likesUserRecordDTO.getBusinessId(),
64 | likesUserRecordDTO.getItemId())
65 | .ifPresentOrElse(likesStatistic -> {
66 | long newLikeCount = likesStatistic.getLikeCount() + 1;
67 | likesStatistic.setLikeCount(newLikeCount);
68 | likesStatisticRepository.save(likesStatistic);
69 | redisTemplate.opsForValue().set(likesStatisticKey, newLikeCount);
70 | }, () -> {
71 | LikesStatistic likesStatistic = LikesStatistic.builder()
72 | .businessId(likesUserRecordDTO.getBusinessId())
73 | .itemId(likesUserRecordDTO.getItemId())
74 | .likeCount(1).build();
75 |
76 | likesStatisticRepository.save(likesStatistic);
77 | redisTemplate.opsForValue().set(likesStatisticKey, 1L);
78 | });
79 |
80 | redisTemplate.opsForSet().add(LIKE_USER_KEY + likesUserRecordDTO.getUserId(), likesUserRecordDTO.getBusinessId() + ":" +
81 | likesUserRecordDTO.getItemId());
82 |
83 | }
84 |
85 | private void doUnLikeAction(LikesUserRecord likesUserRecord) {
86 | likesUserRecord.setLikes(false);
87 | likesUserRecordRepository.save(likesUserRecord);
88 |
89 | String likesStatisticKey = LIKE_STATISTIC_KEY + likesUserRecord.getBusinessId() + ":" + likesUserRecord.getItemId();
90 | likesStatisticRepository.findByBusinessIdAndItemId(likesUserRecord.getBusinessId(),
91 | likesUserRecord.getItemId())
92 | .ifPresentOrElse(likesStatistic -> {
93 | long newLikeCount = likesStatistic.getLikeCount() - 1;
94 | likesStatistic.setLikeCount(newLikeCount);
95 | likesStatisticRepository.save(likesStatistic);
96 |
97 | redisTemplate.opsForValue().set(likesStatisticKey, newLikeCount);
98 | }, () -> {
99 | log.info("there is no likes statistic for businessId:{}, itemId:{}",
100 | likesUserRecord.getBusinessId(), likesUserRecord.getItemId());
101 | });
102 |
103 | redisTemplate.opsForSet().remove(LIKE_USER_KEY + likesUserRecord.getUserId(), likesUserRecord.getBusinessId() + ":" +
104 | likesUserRecord.getItemId());
105 |
106 | }
107 |
108 | }
109 |
--------------------------------------------------------------------------------
/src/main/java/com/fengzhu/reading/controller/BookController.java:
--------------------------------------------------------------------------------
1 | package com.fengzhu.reading.controller;
2 |
3 | import com.fengzhu.reading.CommonResponse;
4 | import com.fengzhu.reading.dto.BookAvgRatingDTO;
5 | import com.fengzhu.reading.dto.BookDTO;
6 | import com.fengzhu.reading.dto.BookRankDTO;
7 | import com.fengzhu.reading.dto.BookReviewDTO;
8 | import com.fengzhu.reading.service.BookAvgRatingService;
9 | import com.fengzhu.reading.service.BookReviewService;
10 | import com.fengzhu.reading.service.BookService;
11 | import com.fengzhu.reading.validator.BookValidator;
12 | import lombok.extern.slf4j.Slf4j;
13 | import org.apache.commons.lang.StringUtils;
14 | import org.springframework.beans.factory.annotation.Autowired;
15 | import org.springframework.data.domain.Page;
16 | import org.springframework.data.domain.PageRequest;
17 | import org.springframework.data.domain.Sort;
18 | import org.springframework.web.bind.annotation.*;
19 |
20 | import java.util.List;
21 | import java.util.concurrent.CompletableFuture;
22 |
23 | @RestController
24 | @Slf4j
25 | public class BookController {
26 |
27 | @Autowired
28 | private BookService bookService;
29 |
30 | @Autowired
31 | private BookReviewService bookReviewService;
32 |
33 | @Autowired
34 | private BookAvgRatingService bookAvgRatingService;
35 |
36 | @Autowired
37 | private BookValidator bookValidator;
38 |
39 | @PostMapping("/book")
40 | public CommonResponse addNewBook(@RequestBody BookDTO bookDTO) {
41 | bookValidator.validateAddNewBook(bookDTO);
42 | return CommonResponse.newSuccess(bookService.addNewBook(bookDTO));
43 | }
44 |
45 | @PutMapping("/book")
46 | public CommonResponse updateBook(@RequestBody BookDTO bookDTO) {
47 | return CommonResponse.newSuccess(bookService.updateBook(bookDTO));
48 | }
49 |
50 | @GetMapping("/book")
51 | public CommonResponse> lookupBooks(@RequestParam(required = false) Long tagId,
52 | @RequestParam(required = false, defaultValue = "10") Integer pageSize,
53 | @RequestParam(required = false, defaultValue = "0") Integer pageNumber) {
54 |
55 | PageRequest pageRequest = PageRequest.of(pageNumber, pageSize);
56 | return CommonResponse.newSuccess(bookService.lookupBooks(pageRequest, tagId));
57 | }
58 |
59 | @GetMapping("/book/{bookId}")
60 | public CommonResponse getBookById(@PathVariable Long bookId) {
61 | CompletableFuture bookFuture = CompletableFuture.
62 | supplyAsync(() -> bookService.getBookDTOById(bookId));
63 |
64 | CompletableFuture> bookReviewFuture = CompletableFuture.
65 | supplyAsync(() -> bookReviewService.getBookReviewDTOListByBookId(bookId, PageRequest.of(0, 10, Sort.by("likeCount").descending())));
66 |
67 | CompletableFuture bookAvgRatingFuture = CompletableFuture
68 | .supplyAsync(() -> bookAvgRatingService.getAvgRatingByBookId(bookId));
69 |
70 | // 利用CompletableFuture实现异步加载book, rating, review
71 | CompletableFuture allTask = CompletableFuture.allOf(bookFuture, bookReviewFuture, bookAvgRatingFuture);
72 | CompletableFuture combinedFuture = allTask.thenApply(v -> {
73 | BookDTO bookDTO = bookFuture.join();
74 | Page bookReviewDTOList = bookReviewFuture.join();
75 | BookAvgRatingDTO bookAvgRatingDTO = bookAvgRatingFuture.join();
76 | bookDTO.setBookReviewDTOList(bookReviewDTOList);
77 | bookDTO.setBookAvgRatingDTO(bookAvgRatingDTO);
78 | return bookDTO;
79 | }).whenComplete((res, ex) -> {
80 | if (ex != null) {
81 | log.error("getBookById occur exception", ex);
82 | }
83 | });
84 | try {
85 | return CommonResponse.newSuccess(combinedFuture.get());
86 | } catch (Throwable e) {
87 | return CommonResponse.newFail("获取book信息失败", null);
88 | }
89 |
90 | }
91 |
92 | @GetMapping("/book/rank")
93 | public CommonResponse> getBookRank(@RequestParam(required = false, defaultValue = "10") int size) {
94 | return CommonResponse.newSuccess(bookService.getBookRank(size));
95 | }
96 |
97 | @GetMapping("/book/search")
98 | public CommonResponse> searchBooks(@RequestParam String keyword) {
99 | if (StringUtils.isEmpty(keyword)) {
100 | return CommonResponse.newSuccess(null);
101 | }
102 | return CommonResponse.newSuccess(bookService.searchBooks(keyword));
103 | }
104 |
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/com/fengzhu/reading/controller/BookRatingController.java:
--------------------------------------------------------------------------------
1 | package com.fengzhu.reading.controller;
2 |
3 | import com.fengzhu.reading.CommonResponse;
4 | import com.fengzhu.reading.dto.BookRatingDTO;
5 | import com.fengzhu.reading.service.BookRatingService;
6 | import com.fengzhu.reading.validator.BookRatingValidator;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.web.bind.annotation.*;
9 |
10 | @RestController
11 | public class BookRatingController {
12 |
13 | @Autowired
14 | private BookRatingValidator bookRatingValidator;
15 |
16 | @Autowired
17 | private BookRatingService bookRatingService;
18 |
19 | @PostMapping("/book/rating")
20 | public CommonResponse addNewBookRating(@RequestBody BookRatingDTO bookRatingDTO) {
21 | bookRatingValidator.validateAddNewBookRating(bookRatingDTO);
22 | return CommonResponse.newSuccess(bookRatingService.addNewBookRating(bookRatingDTO));
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/fengzhu/reading/controller/BookReviewController.java:
--------------------------------------------------------------------------------
1 | package com.fengzhu.reading.controller;
2 |
3 | import com.fengzhu.reading.CommonResponse;
4 | import com.fengzhu.reading.dto.BookReviewDTO;
5 | import com.fengzhu.reading.service.BookReviewService;
6 | import com.fengzhu.reading.validator.BookReviewValidator;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.data.domain.Page;
9 | import org.springframework.data.domain.PageRequest;
10 | import org.springframework.data.domain.Sort;
11 | import org.springframework.web.bind.annotation.*;
12 |
13 | @RestController
14 | public class BookReviewController {
15 |
16 | @Autowired
17 | private BookReviewService bookReviewService;
18 |
19 | @Autowired
20 | private BookReviewValidator bookReviewValidator;
21 |
22 | @PostMapping("/book/review")
23 | public CommonResponse addNewBookReview(@RequestBody BookReviewDTO bookReviewDTO) {
24 | bookReviewValidator.validateAddNewBookReview(bookReviewDTO);
25 | return CommonResponse.newSuccess(bookReviewService.addNewBookReview(bookReviewDTO));
26 | }
27 |
28 | @GetMapping("/book/review/{bookId}")
29 | public CommonResponse> getBookReviewByBookId(@PathVariable long bookId, @RequestParam(value = "pageNumber", required = false, defaultValue = "0") int pageNumber,
30 | @RequestParam(value = "pageSize", required = false, defaultValue = "10") int pageSize,
31 | @RequestParam(required = false, defaultValue = "likeCount") String sortField) {
32 |
33 | return CommonResponse.newSuccess(bookReviewService.getBookReviewDTOListByBookId(bookId,
34 | PageRequest.of(pageNumber, pageSize, Sort.by(sortField).descending())));
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/fengzhu/reading/controller/LikesController.java:
--------------------------------------------------------------------------------
1 | package com.fengzhu.reading.controller;
2 |
3 | import com.fengzhu.reading.CommonResponse;
4 | import com.fengzhu.reading.dto.LikesUserRecordDTO;
5 | import com.fengzhu.reading.service.LikesService;
6 | import com.fengzhu.reading.utils.BaseContext;
7 | import com.fengzhu.reading.validator.LikesValidator;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.web.bind.annotation.*;
10 |
11 | import java.util.List;
12 |
13 | @RestController
14 | public class LikesController {
15 |
16 | @Autowired
17 | private LikesService likesService;
18 |
19 | @Autowired
20 | private LikesValidator likesValidator;
21 |
22 | @PostMapping("/likes")
23 | public CommonResponse addlikeItem(@RequestBody LikesUserRecordDTO likesUserRecordDTO) {
24 | likesValidator.validateAddNewBook(likesUserRecordDTO);
25 | return CommonResponse.newSuccess(likesService.addNewLikesRecord(likesUserRecordDTO));
26 | }
27 |
28 | @GetMapping("/likes/{businessId}")
29 | public CommonResponse> getMyLikes(@PathVariable Long businessId) {
30 | long userId = BaseContext.getCurrentId();
31 | return CommonResponse.newSuccess(likesService.getMyLikes(userId, businessId));
32 | }
33 |
34 | @GetMapping("/likes/{businessId}/{itemId}")
35 | public CommonResponse getItemLikesCount(@PathVariable Long businessId, @PathVariable Long itemId) {
36 | return CommonResponse.newSuccess(likesService.getItemLikesCount(businessId, itemId));
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/fengzhu/reading/controller/TagController.java:
--------------------------------------------------------------------------------
1 | package com.fengzhu.reading.controller;
2 |
3 |
4 | import com.fengzhu.reading.CommonResponse;
5 | import com.fengzhu.reading.dto.TagDTO;
6 | import com.fengzhu.reading.dto.TagGroupDTO;
7 | import com.fengzhu.reading.service.TagService;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.web.bind.annotation.GetMapping;
10 | import org.springframework.web.bind.annotation.PostMapping;
11 | import org.springframework.web.bind.annotation.RequestBody;
12 | import org.springframework.web.bind.annotation.RestController;
13 |
14 | import java.util.List;
15 | import java.util.Map;
16 |
17 | @RestController
18 | public class TagController {
19 |
20 | @Autowired
21 | private TagService tagService;
22 |
23 | @PostMapping("/tag")
24 | public CommonResponse addNewTag(@RequestBody TagDTO tagDTO) {
25 | return CommonResponse.newSuccess(tagService.addNewTag(tagDTO));
26 | }
27 |
28 | @GetMapping("/tag")
29 | public CommonResponse