在微服务之间进行通信的传统方法之一是通过它们的REST APIs。然而,随着系统的发展和微服务数量的增加,通信变得更加复杂,架构可能开始类似于我们的老朋友意大利面条的反模式,服务之间相互依赖或紧密耦合,拖累了开发团队。这种模式可以表现出低延迟,但只有在服务高度可用的情况下才能发挥作用。
为了克服这个设计上的缺点,新的架构旨在将发送者和接收者解耦,采用异步消息传递。在以Kafka为中心的架构中,低延迟被保留了下来,还有一些额外的优势,比如在可用的消费者之间进行消息平衡和集中管理。
在处理棕地平台(遗留)时,建议采用实现异步消息传递的方式来解耦单体,并为迁移到微服务做准备。
在本教程中,你将学习如何:
- 用JHipster创建一个微服务架构
- 启用Kafka集成以实现微服务的通信
- 将Okta设置为认证提供者
什么是Kafka?
Apache Kafka是一个分布式流媒体平台。它最初被认为是一个消息队列,由LinkedIn在2011年开源。它的社区对Kafka进行了进化,以提供关键的功能:
- 发布和订阅记录流,就像一个消息队列。
- 存储系统,因此消息可以被异步地消费。Kafka将数据写入一个可扩展的磁盘结构中,并复制它以实现容错。生产者可以等待写确认。
- 用Kafka Streams API进行流处理,可以将复杂的输入流聚合或连接到处理后的数据输出流上。
传统的消息传递模式是队列和发布-订阅。在队列中,每条记录都会被送到一个消费者手中。在发布-订阅中,记录会被所有的消费者收到。
Kafka中的消费者组是一个抽象,它结合了两种模式。记录处理可以在消费者组的成员之间进行负载平衡,Kafka允许你向多个消费者组广播消息。这是同样的发布-订阅语义,其中订阅者是一个消费者集群而不是一个单一的进程。
Kafka的流行使用案例包括:
- 传统的消息传递,以更好的延迟和可扩展性将数据生产者与处理器解耦。
- 用实时发布-订阅馈送来跟踪网站活动。
- 作为基于文件的日志聚合的替代品,事件数据成为消息流。
- 数据管道,其中从主题中消耗的数据被转换并馈送到新的主题。
- 作为分布式系统的外部提交日志。
- 作为事件源应用程序的后端日志存储,其中每个状态变化都按时间顺序记录。
与Kafka的微服务通信
让我们建立一个有JHipster和Kafka支持的微服务架构。在本教程中,你将创建store 和alert 微服务。商店微服务将创建和更新商店记录。alert 微服务将接收来自store 的更新事件并发送电子邮件提醒。
先决条件
安装JHipster
npm install -g generator-jhipster@7.9.3
--version 命令应该输出这样的内容
$ jhipster --version
INFO! Using bundled JHipster
7.9.3
为该项目创建一个目录:
mkdir jhipster-kafka
cd jhipster-kafka
创建一个apps.jdl 文件,在JHipster领域语言(JDL)中定义存储、警报和网关应用程序。通过在商店和警报应用程序的定义中添加messageBroker kafka ,启用Kafka集成。
application {
config {
baseName gateway,
packageName com.okta.developer.gateway,
applicationType gateway,
authenticationType oauth2,
prodDatabaseType postgresql,
serviceDiscoveryType eureka,
testFrameworks [cypress]
}
entities Store, StoreAlert
}
application {
config {
baseName store,
packageName com.okta.developer.store,
applicationType microservice,
authenticationType oauth2,
databaseType mongodb,
devDatabaseType mongodb,
prodDatabaseType mongodb,
enableHibernateCache false,
serverPort 8082,
serviceDiscoveryType eureka
messageBroker kafka
}
entities Store
}
application {
config {
baseName alert,
packageName com.okta.developer.alert,
applicationType microservice,
authenticationType oauth2,
serverPort 8082,
serviceDiscoveryType eureka
messageBroker kafka
}
entities StoreAlert
}
enum StoreStatus {
OPEN,
CLOSED
}
entity Store {
name String required,
address String required,
status StoreStatus,
createTimestamp Instant required,
updateTimestamp Instant
}
entity StoreAlert {
storeName String required,
storeStatus String required,
timestamp Instant required
}
microservice Store with store
microservice StoreAlert with alert
现在,在你的jhipster-kafka 文件夹中,用以下命令导入这个文件:
jhipster jdl apps.jdl
用Docker Compose配置微服务部署
在项目文件夹中,为Docker Compose创建一个子文件夹并运行JHipster的docker-compose 子生成器。
mkdir docker-compose
cd docker-compose
jhipster docker-compose
生成器将要求你定义以下内容:
- 应用程序的类型:微服务应用
- 网关的类型:基于Spring云网关的JHipster网关
- 将服务的根目录保留为默认的:./
- 要包括哪些应用:网关、存储、警报
- 哪些应用程序要与集群数据库一起使用:(无)
- 如果应该启用监控:没有
- JHipster注册表的密码。
<default>
几乎当生成器完成时,输出中显示一个警告:
WARNING! Docker Compose configuration generated, but no Jib cache found
If you forgot to generate the Docker image for this application, please run:
To generate the missing Docker image(s), please run:
./mvnw -ntp -Pprod verify jib:dockerBuild in /home/indiepopart/jhipster-kafka/alert
./mvnw -ntp -Pprod verify jib:dockerBuild in /home/indiepopart/jhipster-kafka/gateway
./mvnw -ntp -Pprod verify jib:dockerBuild in /home/indiepopart/jhipster-kafka/store
你稍后将生成图像,但首先,让我们为你的微服务添加一些安全和Kafka集成。
添加OpenID Connect(OIDC)认证
这个微服务架构被设置为针对Keycloak进行认证。让我们更新设置,使用Okta作为认证提供者。
在你开始之前,你需要一个免费的Okta开发者账户。安装Okta CLI并运行okta register ,以注册一个新账户。如果你已经有一个账户,运行okta login 。然后,运行okta apps create jhipster 。选择默认的应用程序名称,或根据你的需要进行更改。 然后,将重定向URI改为:
http://localhost:8081/login/oauth2/code/oidc,http://localhost:8761/login/oauth2/code/oidc
使用http://localhost:8081,http://localhost:8761 为注销重定向URI。
Okta CLI是做什么的?
Okta CLI简化了JHipster应用程序的配置,并为您做了几件事:
- 创建一个具有正确(见上文,下面是默认值)重定向URI的OIDC应用。
- 登录:
http://localhost:8080/login/oauth2/code/oidc和http://localhost:8761/login/oauth2/code/oidc - 注销:
http://localhost:8080和http://localhost:8761
- 登录:
- 创建JHipster期望的
ROLE_ADMIN和ROLE_USER组 - 将你的当前用户添加到
ROLE_ADMIN和ROLE_USER组中。 - 在你的默认授权服务器中创建一个
groups,并将用户的组添加到其中。
注意:http://localhost:8761* 重定向URI是为JHipster注册处准备的,在用JHipster创建微服务时经常使用。Okta CLI默认会添加这些。
完成后,你会看到如下的输出:
Okta application configuration has been written to: /path/to/app/.okta.env
运行cat .okta.env (或Windows上的type .okta.env ),查看你的应用程序的发行者和凭证。它将看起来像这样(除了占位符的值将被填充)。
export SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER_URI="/oauth2/default"
export SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_ID="{clientId}"
export SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_SECRET="{clientSecret}"
注意:您也可以使用Okta管理控制台来创建您的应用程序。参见在Okta上创建一个JHipster应用程序以了解更多信息。
在该项目中,创建一个docker-compose/.env 文件并添加以下变量。对于数值,使用你创建的Okta网络应用程序的设置:
OIDC_ISSUER_URI={yourIssuer}
OIDC_CLIENT_ID={yourClientId}
OIDC_CLIENT_SECRET={yourClientSecret}
编辑docker-compose/docker-compose.yml ,并为服务store-app 、alert-app 、gateway-app 和jhipster-registry 更新SPRING_SECURITY_* 的设置:
- SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER_URI=${OIDC_ISSUER_URI}
- SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_ID=${OIDC_CLIENT_ID}
- SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_SECRET=${OIDC_CLIENT_SECRET}
使用Spring Cloud Config来覆盖OIDC设置
除了在docker-compose.yml 中为每个应用程序设置环境变量外,还有一种方法是使用Spring Cloud Config。JHipster注册表包括Spring Cloud Config,所以这很容易做到。
打开docker-compose/central-server-config/application.yml ,在那里添加你的Okta设置:
spring:
security:
oauth2:
client:
provider:
oidc:
issuer-uri: /oauth2/default
registration:
oidc:
client-id: {yourClientId}
client-secret: {yourClientSecret}
注册中心、网关、商店和警报应用程序都被配置为在启动时读取该配置。
在商店和警报微服务之间进行通信
JHipster生成器为声明messageBroker kafka (在JDL中)的应用程序添加了一个spring-cloud-starter-stream-kafka 依赖关系,使Spring Cloud Stream编程模型与Apache Kafka绑定器一起使用Kafka作为消息传递中间件。
Spring Cloud Stream最近又被添加到JHipster中。现在,我们可以不使用Kafka核心API,而是使用绑定器的抽象,在代码中声明输入/输出参数,并让具体的绑定器实现来处理到经纪人目的地的映射。
重要提示:目前,JHipster包括Spring Cloud Stream 3.2.4,它已经废弃了基于注解的编程模型、@EnableBinding 和@StreamListener 注解,而采用了功能编程模型。请继续关注JHipster未来的更新。
在这个例子中,更新store 微服务,以便在商店实体被更新时,通过Kafka向alert 微服务发送消息。
首先,为一个新的主题store-alerts ,创建一个出站绑定。添加接口KafkaStoreAlertProducer:
package com.okta.developer.store.config;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
public interface KafkaStoreAlertProducer {
String CHANNELNAME = "binding-out-store-alert";
@Output(CHANNELNAME)
MessageChannel output();
}
在WebConfigurer 中包含出站绑定:
package com.okta.developer.store.config;
@EnableBinding({ KafkaSseConsumer.class, KafkaSseProducer.class, KafkaStoreAlertProducer.class })
@Configuration
public class WebConfigurer implements ServletContextInitializer {
...
将绑定配置添加到application.yml:
spring:
cloud:
stream:
...
bindings:
...
binding-out-store-alert:
destination: store-alerts-topic
content-type: application/json
group: store-alerts
在store 项目中,创建一个AlertService ,用于发送事件的详细信息:
package com.okta.developer.store.service;
import com.okta.developer.store.config.KafkaStoreAlertProducer;
import com.okta.developer.store.domain.Store;
import com.okta.developer.store.service.dto.StoreAlertDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.stereotype.Service;
import org.springframework.util.MimeTypeUtils;
import java.util.HashMap;
import java.util.Map;
@Service
public class AlertService {
private final Logger log = LoggerFactory.getLogger(AlertService.class);
private final MessageChannel output;
public AlertService(@Qualifier(KafkaStoreAlertProducer.CHANNELNAME) MessageChannel output) {
this.output = output;
}
public void alertStoreStatus(Store store) {
try {
StoreAlertDTO storeAlertDTO = new StoreAlertDTO(store);
log.debug("Request the message : {} to send to store-alert topic ", storeAlertDTO);
Map<String, Object> map = new HashMap<>();
map.put(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON);
MessageHeaders headers = new MessageHeaders(map);
output.send(new GenericMessage<>(storeAlertDTO, headers));
} catch (Exception e){
log.error("Could not send store alert", e);
throw new AlertServiceException(e);
}
}
}
创建引用的AlertServiceException 类:
package com.okta.developer.store.service;
public class AlertServiceException extends RuntimeException {
public AlertServiceException(Throwable e) {
super(e);
}
}
并在...service.dto 包中添加一个StoreAlertDTO 类:
package com.okta.developer.store.service.dto;
import com.okta.developer.store.domain.Store;
public class StoreAlertDTO {
private String storeName;
private String storeStatus;
public StoreAlertDTO(Store store){
this.storeName = store.getName();
this.storeStatus = store.getStatus().name();
}
public String getStoreName() {
return storeName;
}
public void setStoreName(String storeName) {
this.storeName = storeName;
}
public String getStoreStatus() {
return storeStatus;
}
public void setStoreStatus(String storeStatus) {
this.storeStatus = storeStatus;
}
}
将AlertService 注入到StoreResource API的实现中,修改其构造函数。同时修改updateStore 的调用,为alert 服务发布一个StoreAlertDTO:
@RestController
@RequestMapping("/api")
public class StoreResource {
...
private final StoreRepository storeRepository;
private final AlertService alertService;
public StoreResource(StoreRepository storeRepository, AlertService alertService) {
this.storeRepository = storeRepository;
this.alertService = alertService;
}
...
@PutMapping("/stores/{id}")
public ResponseEntity<Store> updateStore(
@PathVariable(value = "id", required = false) final String id,
@Valid @RequestBody Store store
) throws URISyntaxException {
...
Store result = storeRepository.save(store);
log.debug("SEND store alert for Store: {}", store);
alertService.alertStoreStatus(result);
...
}
...
}
在生产中启用调试日志
既然你要部署prod 配置文件,让我们在生产中启用日志。修改store/src/main/java/com/okta/.../config/LoggingAspectConfiguration.java 类:
@Configuration
@EnableAspectJAutoProxy
public class LoggingAspectConfiguration {
@Bean
@Profile({JHipsterConstants.SPRING_PROFILE_DEVELOPMENT, JHipsterConstants.SPRING_PROFILE_PRODUCTION})
public LoggingAspect loggingAspect(Environment env) {
return new LoggingAspect(env);
}
}
编辑store/src/main/resources/config/application-prod.yml ,将商店应用程序的日志级别改为DEBUG 。
logging:
level:
ROOT: INFO
tech.jhipster: INFO
com.okta.developer.store: DEBUG
在警报微服务中添加一个EmailService
现在让我们定制alert 微服务。首先,在配置中添加消费者声明KafkaStoreAlertConsumer:
package com.okta.developer.alert.config;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.MessageChannel;
public interface KafkaStoreAlertConsumer {
String CHANNELNAME = "binding-in-store-alert";
@Input(CHANNELNAME)
MessageChannel input();
}
在WebConfigurer 中包括绑定:
package com.okta.developer.alert.config;
@EnableBinding({ KafkaSseConsumer.class, KafkaSseProducer.class, KafkaStoreAlertConsumer.class })
@Configuration
public class WebConfigurer implements ServletContextInitializer {
...
将入站绑定配置添加到application.yml:
spring:
cloud:
stream:
bindings:
...
binding-in-store-alert:
destination: store-alerts-topic
content-type: application/json
group: store-alerts
创建一个EmailService 来发送商店更新通知,使用Spring框架的JavaMailSender:
package com.okta.developer.alert.service;
import com.okta.developer.alert.service.dto.StoreAlertDTO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
@Service
public class EmailService {
private JavaMailSender emailSender;
@Value("${alert.distribution-list}")
private String distributionList;
public EmailService(JavaMailSender emailSender){
this.emailSender = emailSender;
}
public void sendSimpleMessage(StoreAlertDTO alertDTO){
try {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(distributionList);
message.setSubject("Store Alert: " + alertDTO.getStoreName());
message.setText(alertDTO.getStoreStatus());
message.setFrom("StoreAlert");
emailSender.send(message);
} catch (Exception exception) {
throw new EmailServiceException(exception);
}
}
}
创建引用的EmailServiceException:
package com.okta.developer.alert.service;
public class EmailServiceException extends RuntimeException {
public EmailServiceException(Exception exception) {
super(exception);
}
}
在...service.dto 包中添加一个StoreAlertDTO 类:
package com.okta.developer.alert.service.dto;
public class StoreAlertDTO {
private String storeName;
private String storeStatus;
public String getStoreName() {
return storeName;
}
public void setStoreName(String storeName) {
this.storeName = storeName;
}
public String getStoreStatus() {
return storeStatus;
}
public void setStoreStatus(String storeStatus) {
this.storeStatus = storeStatus;
}
}
在alert/src/main/resources/config/application.yml 和 alert/src/test/resources/config/application.yml 中添加一个新的属性,用于商店警报的目标邮件。
alert:
distribution-list: {distributionListAddress}
注意:你需要在src/test/.../application.yml 中为电子邮件设置一个值(例如:list@email.com 就可以了),以便测试通过。对于Docker,你将用下面的环境变量覆盖{distributionListAddress} 和{username} +{password} 占位符的值。
更新application-prod.yml 中的spring.mail.* 属性,将Gmail设置为电子邮件服务:
spring:
...
mail:
host: smtp.gmail.com
port: 587
username: {username}
protocol: smtp
tls: true
properties.mail.smtp:
auth: true
starttls.enable: true
添加一个Kafka消费者来坚持警报和发送电子邮件
创建一个AlertConsumer 服务来保存一个StoreAlert ,并在通过Kafka收到警报信息时发送电子邮件通知。添加KafkaProperties,StoreAlertRepository, 和EmailService 作为构造函数参数。然后添加一个start() 方法来初始化消费者并进入处理循环:
package com.okta.developer.alert.service;
import com.okta.developer.alert.config.KafkaStoreAlertConsumer;
import com.okta.developer.alert.domain.StoreAlert;
import com.okta.developer.alert.repository.StoreAlertRepository;
import com.okta.developer.alert.service.dto.StoreAlertDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Service;
import java.time.Instant;
@Service
public class AlertConsumer {
private final Logger log = LoggerFactory.getLogger(AlertConsumer.class);
private StoreAlertRepository storeAlertRepository;
private EmailService emailService;
public AlertConsumer(StoreAlertRepository storeAlertRepository, EmailService emailService) {
this.storeAlertRepository = storeAlertRepository;
this.emailService = emailService;
}
@StreamListener(value = KafkaStoreAlertConsumer.CHANNELNAME, copyHeaders = "false")
public void consume(Message<StoreAlertDTO> message) {
StoreAlertDTO dto = message.getPayload();
log.info("Got message from kafka stream: {} {}", dto.getStoreName(), dto.getStoreStatus());
try {
StoreAlert storeAlert = new StoreAlert();
storeAlert.setStoreName(dto.getStoreName());
storeAlert.setStoreStatus(dto.getStoreStatus());
storeAlert.setTimestamp(Instant.now());
storeAlertRepository.save(storeAlert);
emailService.sendSimpleMessage(dto);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
注意:在消息处理过程中任何未处理的异常都会使服务离开消费者组。这就是为什么上面有代码来捕捉Exception 。
作为最后一个定制步骤,按照你为store 微服务所做的同样方式更新日志配置。
微服务+Kafka容器部署
修改docker-compose/docker-compose.yml ,为alert 应用程序添加以下环境变量:
- SPRING_MAIL_USERNAME=${MAIL_USERNAME}
- SPRING_MAIL_PASSWORD=${MAIL_PASSWORD}
- ALERT_DISTRIBUTION_LIST=${DISTRIBUTION_LIST}
编辑docker-compose/.env ,为新的环境变量添加值:
MAIL_USERNAME={yourGmailAccount}
MAIL_PASSWORD={yourPassword}
DISTRIBUTION_LIST={anotherEmailAccount}
确保Docker Desktop正在运行,然后为store 微服务生成Docker镜像。从store 目录中运行以下命令:
./mvnw -ntp -Pprod verify jib:dockerBuild
# `npm run java:docker` is a shortcut for the above command
注意:如果你使用Apple Silicon,你将需要使用npm run java:docker:arm64 。
对alert 和gateway 应用程序重复上述步骤:
在你运行你的微服务架构之前,确保你有足够的内存分配。Docker Desktop的默认值是2GB,我推荐8GB。这个设置在Docker > 资源 > 高级下。
然后,使用Docker Compose运行一切:
cd docker-compose
docker compose up
在每个服务启动时,你会看到大量的日志记录。等待一两分钟,然后打开http://localhost:8761 ,用你的Okta账户登录。这是JHipster注册表,你可以用它来监控你的应用程序的状态。等到所有的服务都启动。
打开一个新的终端窗口,跟踪alert 微服务的日志,以验证它正在处理StoreAlert 记录:
docker logs -f docker-compose-alert-1 | grep Consumer
你应该看到日志条目表明alert 微服务在启动时加入的消费者组:
2022-09-05 15:20:44.146 INFO 1 --- [ main] org.apache.kafka.clients.Metadata : [Consumer clientId=consumer-store-alerts-4, groupId=store-alerts] Cluster ID: pyoOBVa3T3Gr1VP3rJBOlQ
2022-09-05 15:20:44.151 INFO 1 --- [ main] o.a.k.c.c.internals.ConsumerCoordinator : [Consumer clientId=consumer-store-alerts-4, groupId=store-alerts] Resetting generation due to: consumer pro-actively leaving the group
2022-09-05 15:20:44.151 INFO 1 --- [ main] o.a.k.c.c.internals.ConsumerCoordinator : [Consumer clientId=consumer-store-alerts-4, groupId=store-alerts] Request joining group due to: consumer pro-actively leaving the group
2022-09-05 15:20:44.162 INFO 1 --- [ main] o.a.k.clients.consumer.ConsumerConfig : ConsumerConfig values:
2022-09-05 15:20:44.190 INFO 1 --- [ main] o.a.k.clients.consumer.KafkaConsumer : [Consumer clientId=consumer-store-alerts-5, groupId=store-alerts] Subscribed to topic(s): store-alerts-topic
2022-09-05 15:20:44.225 INFO 1 --- [container-0-C-1] org.apache.kafka.clients.Metadata : [Consumer clientId=consumer-store-alerts-5, groupId=store-alerts] Resetting the last seen epoch of partition store-alerts-topic-0 to 0 since the associated topicId changed from null to 0G-IFWw-S9C3fEGLXDCOrw
2022-09-05 15:20:44.226 INFO 1 --- [container-0-C-1] org.apache.kafka.clients.Metadata : [Consumer clientId=consumer-store-alerts-5, groupId=store-alerts] Cluster ID: pyoOBVa3T3Gr1VP3rJBOlQ
2022-09-05 15:20:44.227 INFO 1 --- [container-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator : [Consumer clientId=consumer-store-alerts-5, groupId=store-alerts] Discovered group coordinator kafka:9092 (id: 2147483645 rack: null)
2022-09-05 15:20:44.229 INFO 1 --- [container-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator : [Consumer clientId=consumer-store-alerts-5, groupId=store-alerts] (Re-)joining group
2022-09-05 15:20:44.238 INFO 1 --- [container-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator : [Consumer clientId=consumer-store-alerts-5, groupId=store-alerts] Request joining group due to: need to re-join with the given member-id
2022-09-05 15:20:44.239 INFO 1 --- [container-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator : [Consumer clientId=consumer-store-alerts-5, groupId=store-alerts] (Re-)joining group
一旦一切就绪,去http://localhost:8081 的网关并登录。创建一个商店实体,然后更新它。alert 微服务在处理从store 服务收到的消息时应该记录条目:
2022-09-05 18:08:31.546 INFO 1 --- [container-0-C-1] c.o.d.alert.service.AlertConsumer : Got message from kafka stream: Candle Shop CLOSED
如果你在试图发送通知时,在alert 微服务的日志中看到MailAuthenticationException ,这可能是你的Gmail安全配置:
alert-app_1 | org.springframework.mail.MailAuthenticationException: Authentication failed; nested exception is javax.mail.AuthenticationFailedException: 535-5.7.8 Username and Password not accepted. Learn more at
alert-app_1 | 535 5.7.8 https://support.google.com/mail/?p=BadCredentials *** - gsmtp
alert-app_1 |
alert-app_1 | at org.springframework.mail.javamail.JavaMailSenderImpl.doSend(JavaMailSenderImpl.java:440)
要启用从alert 应用程序的登录,请进入myaccount.google.com,然后选择安全标签。为你的账户打开2步验证。在登录谷歌一节中,选择应用密码,并创建一个新的应用密码。在 "选择应用"下拉菜单中,设置"其他"(自定义名称),并输入该密码的名称。点击 "**生成 "**并复制该密码。更新docker-compose/.env ,并为Gmail认证设置应用密码:
MAIL_PASSWORD={yourAppPassword}
重要的是:一旦测试完成,不要忘记删除应用程序的密码。
用CTRL+C停止所有的容器,然后用docker compose up ,再次重启。再次更新一个商店,这次你应该会收到一封包含商店状态的邮件。