hw1-impl
请你在大二开发的E-BookStore系统的基础上,完成下列任务:
A. 请你设计一个Service,它包含一个计时器,可以记录用户每次登录E-BookStore后的会话保持的时间,具体要求为:
- i. 在你设计的负责用户登录和登出的 Controller中,在登录方法(login)被调用时,调用这个定时器Service,在其中初始化计时器,并开始计时;
- ii. 在登出方法(logout)被调用时,调用这个定时器Service,在其中停止计时器,并获取计时器的计时值,返回给logout,logout方法会返回给前端显示这个时间;
- iii. 如果你之前的系统未针对用户登录登出专门编写Controller,那么就按照上述要求新编写一个Controller以满足要求;
- iv. 在编写代码时,请正确配置Controller和Service的@Scope属性; v. 请你使用多个浏览器同时登录 你的系统,并且在登出时观察它们获得的计数值,并编写一个文档,解释你配置的@Scope属性值的依据,并对最后得到的计时值截图贴在文档中,将文档与代码一起上传。
B. 参照上课给的样例,编写通过Kafka消息中间件处理订单的功能,具体要求为:
- i. 前端发送的下清单请求被后端的Controller接收,然后按照你设计的消息格式,组装下订单消息,并将其发送到Kafka中你预先创建好的Topic中;
- ii. 在后端增加类似样例中用@Component注解的消息监听器类,监听指定Topic中的下订单消息;
- iii. 在监听到下订单消息后,监听器类会调用下订单服务完成下订单,并且将下订单的处理结果发送到预先创建好的另一个Topic中;其中,下订单服务应该与同步处理下订单功能复用相同的Service,以提高代码的可维护性;
- iv. 本次作业不需要实现下订单后前端展示下订单成功的页面,在后端控制台输出,编写文档,将前端发送请求->后端接收到请求进行处理->改写数据成功的流程截图,并对流程进行文字说明,以证明你开发的异步接收订单进行处理的功能正常实现了。
A.
我配置的Service的@Scope
属性是Session, 即@SessionScope
, Controller的@Scope
属性是默认的Singleton
我的依据是: 计时器Service记录用户每次登录后的会话保持的时间, 因而每个Session应当有自己的计时器Service对象, 并在其中维护自己的startTime, 以便在登出时计算会话总共的保持时间, 使用@Autowired装填@SessionScope的Service后, Spring boot会在每个会话之中代理生成一个新的计时器Service, 符合我们的需求. 而对于Controller, 目前没有负载均衡等问题, 不需要创建多个Controller, 对于所有传入请求的分发工作由一个Singleton Controller完成 减少系统的复杂度和资源占用
如图, 后端获取数据之后前端简单的console.log()
打印返回值
先后登出两个浏览器后, 一个"您已经在线了42秒",另一个"您已经在线了16秒",同一个账号在不同session下获得不同的结果, 证明了已经实现Session独立计时.
B. 前端发送请求
后端controller接受
@PostMapping("/api/order")
public void createOrder(@RequestBody OrderDTO.OrderPost orderParam, HttpSession session) {
Long userId = (Long) session.getAttribute("userId");
var kafkaParam = new OrderDTO.AsyncOrderCreateParam(orderParam, userId);
kafkaProducerService.sendMessage(Constant.ORDER_TOPIC, kafkaParam);
}
调用sendMessage在预先定义好的ORDER_TOPIC上创建消息, 同时使用使用Jackson序列化和反序列化的对象kafkaParam来作为消息的body
KafkaProducerService接口如 下
public interface KafkaProducerService {
void sendMessage(String topic, Object message);
}
实现如下, 调用了的Spring Kafka的kafkaTemplate来完成配置注入
package com.example.ebookstorebackend.serviceimpl;
import com.example.ebookstorebackend.service.KafkaProducerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
@Service
public class KafkaProducerServiceImpl implements KafkaProducerService {
@Autowired
private KafkaTemplate<String, Object> kafkaTemplate;
@Override
public void sendMessage(String topic, Object message) {
kafkaTemplate.send(topic, message);
}
}
注意部分配置在application.properties
中完成了, 例如敏感资源和kafka的整体设置
spring.kafka.consumer.bootstrap-servers=localhost:9092
spring.kafka.consumer.group-id=ebookstore
spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer
spring.kafka.consumer.properties.spring.json.trusted.packages=*
spring.kafka.producer.bootstrap-servers=localhost:9092
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
由此我们就可以改造Controller向Service传递信息的方式为向Kafka传递包装的请求参数对象AsyncOrderCreateParam
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonSerialize
@Builder(toBuilder = true)
static public class OrderPost {
String receiver;
String address;
String tel;
List<Integer> itemIds; // cartItemIds
}
@AllArgsConstructor
@Data
@NoArgsConstructor
@JsonSerialize // 使用Jackson来进行Json序列化
@Builder(toBuilder = true)
static public class AsyncOrderCreateParam{
OrderPost order;
Long userId;
}
以上完成了kafka的producer流程
之后kafka进行comsumer流程
service接受到字节流消息后解包, 并将这个param重新拆开, 进行业务逻辑
最后向另一个预定义的RESULT_TOPIC中发送数据, 表示处理完成
必要的Log信息如下图
可以看到这个id为5的订单已经入库