Commit 436cb39c53bd2b0abce99a47ab627aa7248c26cf
1 parent
b62efd8c
Add RabbitMQ support and implement message synchronization for org operations:
- Added `spring-boot-starter-amqp` dependency in POM. - Introduced classes `CorrelationDataEx`, `RetryTask`, `RetryTaskHolder`, and `RabbitMessageSender` for RabbitMQ message handling and retry logic. - Updated `OrgController` to send RabbitMQ notifications for add, update, and delete operations. - Modified `bootstrap.yml` to include `rabbitmq.yml` in shared Nacos configurations.
Showing
7 changed files
with
340 additions
and
1 deletions
itcast-auth/itcast-auth-server/pom.xml
| @@ -52,6 +52,10 @@ | @@ -52,6 +52,10 @@ | ||
| 52 | <artifactId>itcast-tools-core</artifactId> | 52 | <artifactId>itcast-tools-core</artifactId> |
| 53 | </dependency> | 53 | </dependency> |
| 54 | <dependency> | 54 | <dependency> |
| 55 | + <groupId>org.springframework.boot</groupId> | ||
| 56 | + <artifactId>spring-boot-starter-amqp</artifactId> | ||
| 57 | + </dependency> | ||
| 58 | + <dependency> | ||
| 55 | <groupId>org.springframework.cloud</groupId> | 59 | <groupId>org.springframework.cloud</groupId> |
| 56 | <artifactId>spring-cloud-starter-openfeign</artifactId> | 60 | <artifactId>spring-cloud-starter-openfeign</artifactId> |
| 57 | <exclusions> | 61 | <exclusions> |
itcast-auth/itcast-auth-server/src/main/java/com/itheima/authority/biz/mq/CorrelationDataEx.java
0 → 100644
| 1 | +package com.itheima.authority.biz.mq; | ||
| 2 | + | ||
| 3 | +import org.springframework.amqp.core.Message; | ||
| 4 | +import org.springframework.amqp.rabbit.connection.CorrelationData; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * 扩展 | ||
| 8 | + */ | ||
| 9 | +public class CorrelationDataEx extends CorrelationData { | ||
| 10 | + | ||
| 11 | + private Message message; | ||
| 12 | + private String exchange; | ||
| 13 | + private String routingKey; | ||
| 14 | + private int maxRetry; | ||
| 15 | + private int retryCount; | ||
| 16 | + | ||
| 17 | + public CorrelationDataEx(String id) { | ||
| 18 | + super(id); | ||
| 19 | + } | ||
| 20 | + | ||
| 21 | + public Message getMessage() { | ||
| 22 | + return message; | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + public void setMessage(Message message) { | ||
| 26 | + this.message = message; | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + public String getExchange() { | ||
| 30 | + return exchange; | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + public void setExchange(String exchange) { | ||
| 34 | + this.exchange = exchange; | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + public String getRoutingKey() { | ||
| 38 | + return routingKey; | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + public void setRoutingKey(String routingKey) { | ||
| 42 | + this.routingKey = routingKey; | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + public int getMaxRetry() { | ||
| 46 | + return maxRetry; | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + public void setMaxRetry(int maxRetry) { | ||
| 50 | + this.maxRetry = maxRetry; | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + public int getRetryCount() { | ||
| 54 | + return retryCount; | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + public void setRetryCount(int retryCount) { | ||
| 58 | + this.retryCount = retryCount; | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + public boolean hasNext() { | ||
| 62 | + this.retryCount += 1; | ||
| 63 | + return this.retryCount < this.maxRetry; | ||
| 64 | + } | ||
| 65 | +} |
itcast-auth/itcast-auth-server/src/main/java/com/itheima/authority/biz/mq/RabbitMessageSender.java
0 → 100644
| 1 | +package com.itheima.authority.biz.mq; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.StrUtil; | ||
| 4 | +import org.slf4j.Logger; | ||
| 5 | +import org.slf4j.LoggerFactory; | ||
| 6 | +import org.springframework.amqp.AmqpException; | ||
| 7 | +import org.springframework.amqp.core.Message; | ||
| 8 | +import org.springframework.amqp.core.MessageBuilder; | ||
| 9 | +import org.springframework.amqp.rabbit.connection.CorrelationData; | ||
| 10 | +import org.springframework.amqp.rabbit.core.RabbitTemplate; | ||
| 11 | +import org.springframework.beans.factory.annotation.Autowired; | ||
| 12 | +import org.springframework.stereotype.Component; | ||
| 13 | + | ||
| 14 | +import java.nio.charset.StandardCharsets; | ||
| 15 | +import java.util.UUID; | ||
| 16 | + | ||
| 17 | +/** | ||
| 18 | + * rabbitmq 消息发送 | ||
| 19 | + */ | ||
| 20 | +@Component | ||
| 21 | +public class RabbitMessageSender { | ||
| 22 | + private static final Logger LOGGER = LoggerFactory.getLogger(RabbitMessageSender.class); | ||
| 23 | + | ||
| 24 | + @Autowired | ||
| 25 | + private RabbitTemplate rabbitTemplate; | ||
| 26 | + | ||
| 27 | + private RabbitTemplate.ConfirmCallback confirmCallback = new RabbitTemplate.ConfirmCallback() { | ||
| 28 | + @Override | ||
| 29 | + public void confirm(CorrelationData correlationData, boolean ack, String cause) { | ||
| 30 | + if (!ack) { | ||
| 31 | + if (!(correlationData instanceof CorrelationDataEx)) { | ||
| 32 | + return; | ||
| 33 | + } | ||
| 34 | + retry((CorrelationDataEx) correlationData); | ||
| 35 | + } | ||
| 36 | + } | ||
| 37 | + }; | ||
| 38 | + | ||
| 39 | + private RabbitTemplate.ReturnCallback returnCallback = new RabbitTemplate.ReturnCallback() { | ||
| 40 | + @Override | ||
| 41 | + public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) { | ||
| 42 | + LOGGER.info("消息报文:{}", new String(message.getBody())); | ||
| 43 | + LOGGER.info("消息编号:{}", replyCode); | ||
| 44 | + LOGGER.info("描述:{}", replyText); | ||
| 45 | + LOGGER.info("交换机名称:{}", exchange); | ||
| 46 | + LOGGER.info("路由名称:{}", routingKey); | ||
| 47 | + } | ||
| 48 | + }; | ||
| 49 | + | ||
| 50 | + private void retry(CorrelationDataEx correlationDataEx) { | ||
| 51 | + if (correlationDataEx.hasNext()) { | ||
| 52 | + RetryTaskHolder.offer(new RetryTask(1, 5000, 5000, () -> send(correlationDataEx))); | ||
| 53 | + } | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + private boolean send(CorrelationDataEx correlationDataEx) { | ||
| 57 | + this.rabbitTemplate.setMandatory(true); | ||
| 58 | + this.rabbitTemplate.setConfirmCallback(this.confirmCallback); | ||
| 59 | + this.rabbitTemplate.setReturnCallback(this.returnCallback); | ||
| 60 | + try { | ||
| 61 | + this.rabbitTemplate.convertAndSend(correlationDataEx.getExchange(), correlationDataEx.getRoutingKey(), correlationDataEx.getMessage(), correlationDataEx); | ||
| 62 | + return true; | ||
| 63 | + } catch (AmqpException e) { | ||
| 64 | + LOGGER.error("Rabbit mq send error", e); | ||
| 65 | + return false; | ||
| 66 | + } | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + /** | ||
| 70 | + * 发送消息 | ||
| 71 | + * | ||
| 72 | + * @param exchange | ||
| 73 | + * @param routingKey | ||
| 74 | + * @param json | ||
| 75 | + * @param maxRetry | ||
| 76 | + */ | ||
| 77 | + public void send(String exchange, String routingKey, String json, int maxRetry) { | ||
| 78 | + if (StrUtil.isEmpty(exchange) || StrUtil.isEmpty(json)) { | ||
| 79 | + return; | ||
| 80 | + } | ||
| 81 | + String uuid = UUID.randomUUID().toString(); | ||
| 82 | + Message message = MessageBuilder.withBody(json.getBytes(StandardCharsets.UTF_8)).setContentType("application/json").setContentEncoding("UTF-8").setMessageId(uuid).build(); | ||
| 83 | + CorrelationDataEx correlationData = new CorrelationDataEx(uuid); | ||
| 84 | + correlationData.setMessage(message); | ||
| 85 | + correlationData.setExchange(exchange); | ||
| 86 | + correlationData.setRoutingKey(routingKey); | ||
| 87 | + correlationData.setMaxRetry(maxRetry); | ||
| 88 | + if (!send(correlationData)) { | ||
| 89 | + retry(correlationData); | ||
| 90 | + } | ||
| 91 | + } | ||
| 92 | +} |
itcast-auth/itcast-auth-server/src/main/java/com/itheima/authority/biz/mq/RetryTask.java
0 → 100644
| 1 | +package com.itheima.authority.biz.mq; | ||
| 2 | + | ||
| 3 | + | ||
| 4 | +import javax.validation.constraints.NotNull; | ||
| 5 | +import java.util.concurrent.Callable; | ||
| 6 | +import java.util.concurrent.Delayed; | ||
| 7 | +import java.util.concurrent.TimeUnit; | ||
| 8 | + | ||
| 9 | +/** 重试任务 */ | ||
| 10 | +public class RetryTask implements Delayed { | ||
| 11 | + | ||
| 12 | + /** 执行时间 毫秒 */ | ||
| 13 | + private long mills; | ||
| 14 | + /** 时间间隔 毫秒 */ | ||
| 15 | + private long interval; | ||
| 16 | + /** 最大执行次数 */ | ||
| 17 | + private int max; | ||
| 18 | + /** 当前执行次数 */ | ||
| 19 | + private int current; | ||
| 20 | + /** 执行任务 */ | ||
| 21 | + private Callable<Boolean> callable; | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * 构造函数 | ||
| 25 | + * @param max 最大执行次数 | ||
| 26 | + * @param interval 时间间隔 毫秒 | ||
| 27 | + * @param callable 任务 | ||
| 28 | + */ | ||
| 29 | + public RetryTask(int max, long interval, Callable<Boolean> callable) { | ||
| 30 | + this.mills = System.currentTimeMillis(); | ||
| 31 | + this.max = max; | ||
| 32 | + this.interval = interval; | ||
| 33 | + this.callable = callable; | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + /** | ||
| 37 | + * 构造函数 | ||
| 38 | + * @param max 最大执行次数 | ||
| 39 | + * @param interval 时间间隔 毫秒 | ||
| 40 | + * @param offset 时间偏移 毫秒 | ||
| 41 | + * @param callable 任务 | ||
| 42 | + */ | ||
| 43 | + public RetryTask(int max, long interval, long offset, Callable<Boolean> callable) { | ||
| 44 | + this.mills = System.currentTimeMillis() + offset; | ||
| 45 | + this.max = max; | ||
| 46 | + this.interval = interval; | ||
| 47 | + this.callable = callable; | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + @Override | ||
| 51 | + public long getDelay(@NotNull TimeUnit unit) { | ||
| 52 | + long temp = this.mills - System.currentTimeMillis(); | ||
| 53 | + return unit.convert(temp, TimeUnit.MILLISECONDS); | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + @Override | ||
| 57 | + public int compareTo(@NotNull Delayed delayed) { | ||
| 58 | + RetryTask retryTask = (RetryTask)delayed; | ||
| 59 | + return this.mills - retryTask.getMills() < 0 ? -1 : 1; | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + public long getMills() { | ||
| 63 | + return mills; | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + public void setMills(long mills) { | ||
| 67 | + this.mills = mills; | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + public long getInterval() { | ||
| 71 | + return interval; | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + public void setInterval(long interval) { | ||
| 75 | + this.interval = interval; | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + public int getMax() { | ||
| 79 | + return max; | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + public void setMax(int max) { | ||
| 83 | + this.max = max; | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + public int getCurrent() { | ||
| 87 | + return current; | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + public void setCurrent(int current) { | ||
| 91 | + this.current = current; | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + public Callable<Boolean> getCallable() { | ||
| 95 | + return callable; | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + public void setCallable(Callable<Boolean> callable) { | ||
| 99 | + this.callable = callable; | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + public boolean hasNext() { | ||
| 103 | + this.current += 1; | ||
| 104 | + this.mills = System.currentTimeMillis() + this.interval; | ||
| 105 | + return this.current < this.max; | ||
| 106 | + } | ||
| 107 | +} |
itcast-auth/itcast-auth-server/src/main/java/com/itheima/authority/biz/mq/RetryTaskHolder.java
0 → 100644
| 1 | +package com.itheima.authority.biz.mq; | ||
| 2 | + | ||
| 3 | +import org.slf4j.Logger; | ||
| 4 | +import org.slf4j.LoggerFactory; | ||
| 5 | +import org.springframework.core.task.AsyncTaskExecutor; | ||
| 6 | + | ||
| 7 | +import java.util.concurrent.DelayQueue; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 重试任务处理 | ||
| 11 | + */ | ||
| 12 | +public class RetryTaskHolder { | ||
| 13 | + private static final Logger LOGGER = LoggerFactory.getLogger(RetryTaskHolder.class); | ||
| 14 | + | ||
| 15 | + private static final DelayQueue<RetryTask> RETRY_TASK_QUEUE = new DelayQueue<>(); | ||
| 16 | + | ||
| 17 | + //直接开启线程执行 | ||
| 18 | + static { | ||
| 19 | + int availableProcessors = Runtime.getRuntime().availableProcessors(); | ||
| 20 | + for (int i = 0; i < availableProcessors; i++) { | ||
| 21 | + | ||
| 22 | + } | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * 添加延时任务 | ||
| 27 | + * @param retryTask | ||
| 28 | + * @return | ||
| 29 | + */ | ||
| 30 | + public static boolean offer(RetryTask retryTask) { | ||
| 31 | + return RETRY_TASK_QUEUE.offer(retryTask); | ||
| 32 | + } | ||
| 33 | +} |
itcast-auth/itcast-auth-server/src/main/java/com/itheima/authority/controller/core/OrgController.java
| 1 | package com.itheima.authority.controller.core; | 1 | package com.itheima.authority.controller.core; |
| 2 | 2 | ||
| 3 | +import cn.hutool.json.JSONObject; | ||
| 4 | +import cn.hutool.json.JSONUtil; | ||
| 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | 5 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| 4 | import com.baomidou.mybatisplus.core.metadata.IPage; | 6 | import com.baomidou.mybatisplus.core.metadata.IPage; |
| 5 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | 7 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| 8 | +import com.google.common.collect.Lists; | ||
| 9 | +import com.itheima.authority.biz.mq.RabbitMessageSender; | ||
| 6 | import com.itheima.authority.biz.service.auth.UserService; | 10 | import com.itheima.authority.biz.service.auth.UserService; |
| 7 | import com.itheima.authority.biz.service.core.OrgService; | 11 | import com.itheima.authority.biz.service.core.OrgService; |
| 8 | import com.itheima.authority.dto.core.OrgSaveDTO; | 12 | import com.itheima.authority.dto.core.OrgSaveDTO; |
| @@ -56,6 +60,8 @@ public class OrgController extends BaseController { | @@ -56,6 +60,8 @@ public class OrgController extends BaseController { | ||
| 56 | private UserService userService; | 60 | private UserService userService; |
| 57 | @Autowired | 61 | @Autowired |
| 58 | private DozerUtils dozer; | 62 | private DozerUtils dozer; |
| 63 | + @Autowired | ||
| 64 | + private RabbitMessageSender rabbitMessageSender; | ||
| 59 | 65 | ||
| 60 | // /** | 66 | // /** |
| 61 | // * 分页查询组织 | 67 | // * 分页查询组织 |
| @@ -126,6 +132,13 @@ public class OrgController extends BaseController { | @@ -126,6 +132,13 @@ public class OrgController extends BaseController { | ||
| 126 | org.setTreePath(StringUtils.join(parent.getTreePath(), parent.getId(), DEF_ROOT_PATH)); | 132 | org.setTreePath(StringUtils.join(parent.getTreePath(), parent.getId(), DEF_ROOT_PATH)); |
| 127 | } | 133 | } |
| 128 | this.orgService.saveOrg(org); | 134 | this.orgService.saveOrg(org); |
| 135 | + | ||
| 136 | + // 同步mq通知修改neo4j信息 | ||
| 137 | + JSONObject jsonObject = new JSONObject(); | ||
| 138 | + jsonObject.put("type", "ORG"); | ||
| 139 | + jsonObject.put("operation", "ADD"); | ||
| 140 | + jsonObject.put("content", Lists.newArrayList(org)); | ||
| 141 | + rabbitMessageSender.send("itcast-auth", "add", jsonObject.toString(), 3); | ||
| 129 | return this.success(org); | 142 | return this.success(org); |
| 130 | } | 143 | } |
| 131 | 144 | ||
| @@ -135,11 +148,20 @@ public class OrgController extends BaseController { | @@ -135,11 +148,20 @@ public class OrgController extends BaseController { | ||
| 135 | public R<Org> update(@RequestBody @Validated(SuperEntity.Update.class) OrgUpdateDTO data) { | 148 | public R<Org> update(@RequestBody @Validated(SuperEntity.Update.class) OrgUpdateDTO data) { |
| 136 | Org org = this.dozer.map(data, Org.class); | 149 | Org org = this.dozer.map(data, Org.class); |
| 137 | this.orgService.updateOrg(org); | 150 | this.orgService.updateOrg(org); |
| 151 | + | ||
| 152 | + // 同步mq通知修改neo4j信息 | ||
| 153 | + JSONObject jsonObject = new JSONObject(); | ||
| 154 | + jsonObject.put("type", "ORG"); | ||
| 155 | + jsonObject.put("operation", "UPDATE"); | ||
| 156 | + jsonObject.put("content", Lists.newArrayList(org)); | ||
| 157 | + rabbitMessageSender.send("itcast-auth", "update", jsonObject.toString(), 3); | ||
| 158 | + | ||
| 138 | if (data.getStatus() != null) { | 159 | if (data.getStatus() != null) { |
| 139 | org = new Org(); | 160 | org = new Org(); |
| 140 | org.setId(data.getId()); | 161 | org.setId(data.getId()); |
| 141 | org.setStatus(data.getStatus()); | 162 | org.setStatus(data.getStatus()); |
| 142 | this.orgService.updateStatus(org); | 163 | this.orgService.updateStatus(org); |
| 164 | + | ||
| 143 | } | 165 | } |
| 144 | return this.success(org); | 166 | return this.success(org); |
| 145 | } | 167 | } |
| @@ -168,7 +190,23 @@ public class OrgController extends BaseController { | @@ -168,7 +190,23 @@ public class OrgController extends BaseController { | ||
| 168 | @SysLog("删除组织") | 190 | @SysLog("删除组织") |
| 169 | @DeleteMapping | 191 | @DeleteMapping |
| 170 | public R<List<Org>> delete(@RequestParam("ids[]") List<Long> ids) { | 192 | public R<List<Org>> delete(@RequestParam("ids[]") List<Long> ids) { |
| 193 | + // 查询待删除的组织 | ||
| 194 | + List<Org> delList = new ArrayList<>(); | ||
| 195 | + for (Long id : ids) { | ||
| 196 | + Org org = this.orgService.findById(id); | ||
| 197 | + delList.add(org); | ||
| 198 | + } | ||
| 171 | List<Org> orgs = this.orgService.remove(ids); | 199 | List<Org> orgs = this.orgService.remove(ids); |
| 200 | + | ||
| 201 | + // 同步mq通知修改neo4j信息 | ||
| 202 | + for (Org org : delList) { | ||
| 203 | + JSONObject jsonObject = new JSONObject(); | ||
| 204 | + jsonObject.put("type", "ORG"); | ||
| 205 | + jsonObject.put("operation", "DEL"); | ||
| 206 | + jsonObject.put("content", Lists.newArrayList(org)); | ||
| 207 | + rabbitMessageSender.send("itcast-auth", "DEL", jsonObject.toString(), 3); | ||
| 208 | + } | ||
| 209 | + | ||
| 172 | return this.success(orgs); | 210 | return this.success(orgs); |
| 173 | } | 211 | } |
| 174 | 212 |
itcast-auth/itcast-auth-server/src/main/resources/bootstrap.yml
| @@ -20,7 +20,7 @@ spring: | @@ -20,7 +20,7 @@ spring: | ||
| 20 | namespace: aa86e672-3e5a-4480-9910-dd70bb65a7bc | 20 | namespace: aa86e672-3e5a-4480-9910-dd70bb65a7bc |
| 21 | #支持多个共享 Data Id 的配置,多个之间用逗号隔开,多个共享配置间的一个优先级的关系我们约定:按照配置出现的先后顺序,即后面的优先级要高于前面 | 21 | #支持多个共享 Data Id 的配置,多个之间用逗号隔开,多个共享配置间的一个优先级的关系我们约定:按照配置出现的先后顺序,即后面的优先级要高于前面 |
| 22 | #Data Id 必须带文件扩展名,文件扩展名既可支持 properties,也可以支持 yaml/yml。 此时 spring.cloud.nacos.config.file-extension 的配置对自定义扩展配置的 Data Id 文件扩展名没有影响。 | 22 | #Data Id 必须带文件扩展名,文件扩展名既可支持 properties,也可以支持 yaml/yml。 此时 spring.cloud.nacos.config.file-extension 的配置对自定义扩展配置的 Data Id 文件扩展名没有影响。 |
| 23 | - shared-dataids: common.yml,redis.yml,mysql.yml | 23 | + shared-dataids: common.yml,redis.yml,mysql.yml,rabbitmq.yml |
| 24 | #支持哪些共享配置的 Data Id 在配置变化时,应用中是否可动态刷新, 感知到最新的配置值,多个 Data Id 之间用逗号隔开。如果没有明确配置,默认情况下所有共享配置的 Data Id 都不支持动态刷新。‘ | 24 | #支持哪些共享配置的 Data Id 在配置变化时,应用中是否可动态刷新, 感知到最新的配置值,多个 Data Id 之间用逗号隔开。如果没有明确配置,默认情况下所有共享配置的 Data Id 都不支持动态刷新。‘ |
| 25 | refreshable-dataids: common.yml | 25 | refreshable-dataids: common.yml |
| 26 | enabled: true | 26 | enabled: true |