From 0f22083d28ef98d59080aed342897f7929ca11cf Mon Sep 17 00:00:00 2001 From: zhangsan <646228430@qq.com> Date: Sat, 19 Jul 2025 13:29:40 +0800 Subject: [PATCH] =?UTF-8?q?7.19=20=E5=BC=95=E5=85=A5RabbitMQ?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docker-compose-environment-aliyun.yml | 87 ------------------- docs/dev-ops/docker-compose-environment.yml | 18 ++++ docs/dev-ops/nginx/html/css/index.css | 13 ++- docs/dev-ops/rabbitmq/enabled_plugins | 1 + docs/tag/v2.0/nginx/html/js/index.js | 69 +++++++++++---- group-buying-sys-app/pom.xml | 5 +- .../java/edu/whut/config/RabbitMQConfig.java | 33 +++++++ .../src/main/resources/application-dev.yml | 23 +++++ .../src/main/resources/application.yml | 2 +- .../src/test/java/edu/whut/test/ApiTest.java | 25 +++++- group-buying-sys-infrastructure/pom.xml | 6 +- .../adapter/repository/TagRepository.java | 6 +- .../infrastructure/event/EventPublisher.java | 36 ++++++++ group-buying-sys-trigger/pom.xml | 4 + .../listener/TeamSuccessTopicListener.java | 30 +++++++ .../java/edu/whut/types/event/BaseEvent.java | 30 +++++++ pom.xml | 6 ++ 17 files changed, 277 insertions(+), 117 deletions(-) delete mode 100644 docs/dev-ops/docker-compose-environment-aliyun.yml create mode 100644 docs/dev-ops/rabbitmq/enabled_plugins create mode 100644 group-buying-sys-app/src/main/java/edu/whut/config/RabbitMQConfig.java create mode 100644 group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/event/EventPublisher.java create mode 100644 group-buying-sys-trigger/src/main/java/edu/whut/trigger/listener/TeamSuccessTopicListener.java create mode 100644 group-buying-sys-types/src/main/java/edu/whut/types/event/BaseEvent.java diff --git a/docs/dev-ops/docker-compose-environment-aliyun.yml b/docs/dev-ops/docker-compose-environment-aliyun.yml deleted file mode 100644 index 5997e92..0000000 --- a/docs/dev-ops/docker-compose-environment-aliyun.yml +++ /dev/null @@ -1,87 +0,0 @@ -# 命令执行 docker-compose -f docker-compose-environment-aliyun.yml up -d -version: '3.9' -services: - mysql: - image: registry.cn-hangzhou.aliyuncs.com/xfg-studio/mysql:8.0.32 - container_name: mysql - command: --default-authentication-plugin=mysql_native_password - restart: always - environment: - TZ: Asia/Shanghai - MYSQL_ROOT_PASSWORD: 123456 - ports: - - "13306:3306" - volumes: - - ./mysql/my.cnf:/etc/mysql/conf.d/mysql.cnf:ro - - ./mysql/sql:/docker-entrypoint-initdb.d - healthcheck: - test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ] - interval: 5s - timeout: 10s - retries: 10 - start_period: 15s - networks: - - my-network - - # phpmyadmin https://hub.docker.com/_/phpmyadmin - phpmyadmin: - image: registry.cn-hangzhou.aliyuncs.com/xfg-studio/phpmyadmin:5.2.1 - container_name: phpmyadmin - hostname: phpmyadmin - ports: - - 8899:80 - environment: - - PMA_HOST=mysql - - PMA_PORT=3306 - - MYSQL_ROOT_PASSWORD=123qwe!@#QWE - depends_on: - mysql: - condition: service_healthy - networks: - - my-network - - # Redis - redis: - image: registry.cn-hangzhou.aliyuncs.com/xfg-studio/redis:6.2 - container_name: redis - restart: always - hostname: redis - privileged: true - ports: - - 16379:6379 - volumes: - - ./redis/redis.conf:/usr/local/etc/redis/redis.conf - command: redis-server /usr/local/etc/redis/redis.conf - networks: - - my-network - healthcheck: - test: [ "CMD", "redis-cli", "ping" ] - interval: 10s - timeout: 5s - retries: 3 - - # RedisAdmin https://github.com/joeferner/redis-commander - # 账密 admin/admin - redis-admin: - image: registry.cn-hangzhou.aliyuncs.com/xfg-studio/redis-commander:0.8.0 - container_name: redis-admin - hostname: redis-commander - restart: always - ports: - - 8081:8081 - environment: - - REDIS_HOSTS=local:redis:6379 - - HTTP_USER=admin - - HTTP_PASSWORD=admin - - LANG=C.UTF-8 - - LANGUAGE=C.UTF-8 - - LC_ALL=C.UTF-8 - networks: - - my-network - depends_on: - redis: - condition: service_healthy - -networks: - my-network: - driver: bridge \ No newline at end of file diff --git a/docs/dev-ops/docker-compose-environment.yml b/docs/dev-ops/docker-compose-environment.yml index 794cf38..c4b0c67 100644 --- a/docs/dev-ops/docker-compose-environment.yml +++ b/docs/dev-ops/docker-compose-environment.yml @@ -80,6 +80,24 @@ services: redis: condition: service_healthy + # rabbitmq + # 账密 admin/admin + # rabbitmq-plugins enable rabbitmq_management + rabbitmq: + image: rabbitmq:3.8-management + container_name: rabbitmq + restart: always + ports: + - "5672:5672" + - "15672:15672" + environment: + RABBITMQ_DEFAULT_USER: admin + RABBITMQ_DEFAULT_PASS: admin + command: rabbitmq-server + volumes: + - ./rabbitmq/enabled_plugins:/etc/rabbitmq/enabled_plugins + - ./rabbitmq/mq-data:/var/lib/rabbitmq + networks: my-network: driver: bridge \ No newline at end of file diff --git a/docs/dev-ops/nginx/html/css/index.css b/docs/dev-ops/nginx/html/css/index.css index 7ec93be..f35e1f4 100644 --- a/docs/dev-ops/nginx/html/css/index.css +++ b/docs/dev-ops/nginx/html/css/index.css @@ -14,7 +14,7 @@ body{ /* ========== 轮播图 ========== */ .swiper-container{ - width:100%;height:375px;position:relative;overflow:hidden; + width:100%;height:375px;position:relative;overflow:hidden;margin-top: 10px; } .swiper-wrapper{display:flex;transition:transform .3s;} .swiper-slide{flex:0 0 100%;height:375px;} @@ -62,7 +62,14 @@ body{ } /* ========== 拼单列表 ========== */ -.group-buying{background:#fff;padding:15px;margin-bottom:10px;position:relative;overflow:hidden;} +.group-buying { + background: #fff; + padding: 15px; + margin-bottom: 10px; /* 如果还想保留一点外边距 */ + min-height: 230px; /* 根据需要调整数值 */ + position: relative; + overflow: hidden; +} .section-title{ font-size:16px;font-weight:bold;margin-bottom:12px;position:relative;padding-left:10px; } @@ -71,7 +78,7 @@ body{ width:3px;height:16px;background:#ff5000;border-radius:2px; } -.group-users{height:120px;position:relative;overflow:hidden;} +.group-users{height:180px;position:relative;overflow:hidden;} .user-list{position:absolute;top:0;left:0;width:100%;transition:transform .5s ease;} .user-item{ diff --git a/docs/dev-ops/rabbitmq/enabled_plugins b/docs/dev-ops/rabbitmq/enabled_plugins new file mode 100644 index 0000000..90fdaa3 --- /dev/null +++ b/docs/dev-ops/rabbitmq/enabled_plugins @@ -0,0 +1 @@ +[rabbitmq_management]. \ No newline at end of file diff --git a/docs/tag/v2.0/nginx/html/js/index.js b/docs/tag/v2.0/nginx/html/js/index.js index 7217b72..e899bf2 100644 --- a/docs/tag/v2.0/nginx/html/js/index.js +++ b/docs/tag/v2.0/nginx/html/js/index.js @@ -86,7 +86,7 @@ document.addEventListener('DOMContentLoaded', () => { } userList.innerHTML = ''; list.forEach(t => userList.appendChild(makeItem(t, groupPrice))); - initUserMarquee(); + initUserMarquee(3); // 显示 3 条 initCountdown(); } @@ -137,28 +137,61 @@ document.addEventListener('DOMContentLoaded', () => { /* ===================================================== * 2. 拼单列表纵向轮播 * =================================================== */ - function initUserMarquee() { - const items = userList.querySelectorAll('.user-item'); - if (items.length <= 1) return; + function initUserMarquee(visibleCount = 3, interval = 3000, duration = 500) { + const box = document.querySelector('.group-users'); + const listEl = userList; + let originals = Array.from(listEl.children); + const total = originals.length; - const itemH = items[0].offsetHeight; - userList.appendChild(items[0].cloneNode(true)); + if (total === 0) return; + // 如果原本就 <= 可见数,直接定高,不滚 + if (total <= visibleCount) { + const h0 = originals[0].offsetHeight; + box.style.height = (h0 * visibleCount) + 'px'; + return; + } - let idx = 0; - userList.addEventListener('transitionend', () => { - if (idx >= items.length) { - userList.style.transition = 'none'; - userList.style.transform = 'translateY(0)'; - idx = 0; - void userList.offsetWidth; + // 1. 复制整份加入末尾 + originals.forEach(item => listEl.appendChild(item.cloneNode(true))); + + // 2. 重新测量单条高度(此时 DOM 完整) + const itemH = listEl.children[0].offsetHeight; + + // 3. 设定窗口高度 + box.style.height = (itemH * visibleCount) + 'px'; + + // 4. 状态 + let index = 0; + let ticking = false; + + function step() { + index++; + listEl.style.transition = `transform ${duration}ms ease`; + listEl.style.transform = `translateY(-${index * itemH}px)`; + } + + listEl.addEventListener('transitionend', () => { + ticking = false; + // 5. 到达复制段的“第一帧”(index === total)就无缝重置 + if (index === total) { + listEl.style.transition = 'none'; + listEl.style.transform = 'translateY(0)'; + index = 0; + // 强制 reflow 再恢复 transition + void listEl.offsetHeight; + listEl.style.transition = `transform ${duration}ms ease`; } }); - setInterval(() => { - idx++; - userList.style.transition = 'transform .5s ease'; - userList.style.transform = `translateY(${-idx * itemH}px)`; - }, 3000); + const timer = setInterval(() => { + if (!ticking) { + ticking = true; + step(); + } + }, interval); + + // 可选:鼠标悬停暂停 + listEl.addEventListener('mouseenter', () => clearInterval(timer)); } /* ===================================================== diff --git a/group-buying-sys-app/pom.xml b/group-buying-sys-app/pom.xml index 0d6f19a..1eb6c9e 100644 --- a/group-buying-sys-app/pom.xml +++ b/group-buying-sys-app/pom.xml @@ -78,7 +78,10 @@ converter-gson 2.9.0 - + + org.springframework.boot + spring-boot-starter-amqp + com.squareup.okhttp3 diff --git a/group-buying-sys-app/src/main/java/edu/whut/config/RabbitMQConfig.java b/group-buying-sys-app/src/main/java/edu/whut/config/RabbitMQConfig.java new file mode 100644 index 0000000..b376e38 --- /dev/null +++ b/group-buying-sys-app/src/main/java/edu/whut/config/RabbitMQConfig.java @@ -0,0 +1,33 @@ +package edu.whut.config; + +import org.springframework.amqp.core.*; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; + +//@Configuration +public class RabbitMQConfig { + + @Value("${spring.rabbitmq.config.producer.exchange}") + private String exchangeName; + + /** + * 专属交换机 + */ + @Bean + public TopicExchange topicExchange() { + return new TopicExchange(exchangeName, true, false); + } + + /** + * 绑定队列到交换机 + */ + @Bean + public Binding topicTeamSuccessBinding( + @Value("${spring.rabbitmq.config.producer.topic_team_success.routing_key}") String routingKey, + @Value("${spring.rabbitmq.config.producer.topic_team_success.queue}") String queue) { + return BindingBuilder.bind(new Queue(queue, true)) + .to(topicExchange()) + .with(routingKey); + } + +} diff --git a/group-buying-sys-app/src/main/resources/application-dev.yml b/group-buying-sys-app/src/main/resources/application-dev.yml index 9f4a4c2..f01bfb1 100644 --- a/group-buying-sys-app/src/main/resources/application-dev.yml +++ b/group-buying-sys-app/src/main/resources/application-dev.yml @@ -30,6 +30,29 @@ spring: connection-timeout: 30000 #数据库连接超时时间,默认30秒,即30000 connection-test-query: SELECT 1 type: com.zaxxer.hikari.HikariDataSource + # RabbitMQ + rabbitmq: + addresses: 192.168.10.218 + port: 5672 + username: admin + password: admin + listener: + simple: + prefetch: 1 # 每次投递n个消息,消费完在投递n个 + template: + delivery-mode: persistent # 确保全局默认设置为持久化 + # 消息配置 + config: + # 生产者 + producer: + # 1. 用哪个交换机(Exchange) + exchange: group_buy_market_exchange + # 2. 针对不同“业务消息”定义 routing key 和 queue + topic_team_success: + # 消息主题 + routing_key: topic.team_success + # 消费队列 + queue: group_buy_market_queue_2_topic_team_success # MyBatis 配置【如需使用记得打开】 mybatis: diff --git a/group-buying-sys-app/src/main/resources/application.yml b/group-buying-sys-app/src/main/resources/application.yml index 9f96606..3d7808a 100644 --- a/group-buying-sys-app/src/main/resources/application.yml +++ b/group-buying-sys-app/src/main/resources/application.yml @@ -1,3 +1,3 @@ spring: profiles: - active: prod + active: dev diff --git a/group-buying-sys-app/src/test/java/edu/whut/test/ApiTest.java b/group-buying-sys-app/src/test/java/edu/whut/test/ApiTest.java index 025c536..134e587 100644 --- a/group-buying-sys-app/src/test/java/edu/whut/test/ApiTest.java +++ b/group-buying-sys-app/src/test/java/edu/whut/test/ApiTest.java @@ -1,19 +1,40 @@ package edu.whut.test; +import edu.whut.infrastructure.event.EventPublisher; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; +import javax.annotation.Resource; +import java.util.concurrent.CountDownLatch; + @Slf4j @RunWith(SpringRunner.class) @SpringBootTest public class ApiTest { + @Resource + private EventPublisher publisher; + + @Value("${spring.rabbitmq.config.producer.topic_team_success.routing_key}") + private String routingKey; + @Test - public void test() { - log.info("测试完成"); + public void test_rabbitmq() throws InterruptedException { + CountDownLatch countDownLatch = new CountDownLatch(1); + + publisher.publish(routingKey, "订单结算:ORD-20231234"); + publisher.publish(routingKey, "订单结算:ORD-20231235"); + publisher.publish(routingKey, "订单结算:ORD-20231236"); + publisher.publish(routingKey, "订单结算:ORD-20231237"); + publisher.publish(routingKey, "订单结算:ORD-20231238"); + + // 等待,消息消费。测试后,可主动关闭。 + countDownLatch.await(); } + } diff --git a/group-buying-sys-infrastructure/pom.xml b/group-buying-sys-infrastructure/pom.xml index 74a5f89..4c8c2f4 100644 --- a/group-buying-sys-infrastructure/pom.xml +++ b/group-buying-sys-infrastructure/pom.xml @@ -28,13 +28,15 @@ com.squareup.okhttp3 okhttp-sse - cn.hutool hutool-all 5.8.26 - + + org.springframework.boot + spring-boot-starter-amqp + edu.whut diff --git a/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/TagRepository.java b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/TagRepository.java index ed09e61..9c6ce4a 100644 --- a/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/TagRepository.java +++ b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/adapter/repository/TagRepository.java @@ -73,13 +73,13 @@ public class TagRepository implements ITagRepository { try { crowdTagsDetailDao.addCrowdTagsUserId(crowdTagsDetailReq); - // 获取BitSet - RBitSet bitSet = redisService.getBitSet(tagId); - bitSet.set(redisService.getIndexFromUserId(userId)); } catch (DuplicateKeyException ignore) { log.info("用户id{}已在人群标签{}中",userId,tagId); // 忽略唯一索引冲突 } + // 获取BitSet + RBitSet bitSet = redisService.getBitSet(tagId); + bitSet.set(redisService.getIndexFromUserId(userId)); } /** * 更新标签的统计量(例如已命中的用户总数)。 diff --git a/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/event/EventPublisher.java b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/event/EventPublisher.java new file mode 100644 index 0000000..94a9927 --- /dev/null +++ b/group-buying-sys-infrastructure/src/main/java/edu/whut/infrastructure/event/EventPublisher.java @@ -0,0 +1,36 @@ +package edu.whut.infrastructure.event; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.core.MessageDeliveryMode; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * 消息发送 + */ +@Slf4j +@Component +public class EventPublisher { + + @Autowired + private RabbitTemplate rabbitTemplate; + + @Value("${spring.rabbitmq.config.producer.exchange}") + private String exchangeName; + + public void publish(String routingKey, String message) { + try { + rabbitTemplate.convertAndSend(exchangeName, routingKey, message, m -> { + // 持久化消息配置 + m.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); + return m; + }); + } catch (Exception e) { + log.error("发送MQ消息失败 team_success message:{}", message, e); + throw e; + } + } + +} diff --git a/group-buying-sys-trigger/pom.xml b/group-buying-sys-trigger/pom.xml index ac38fb9..19bc5a4 100644 --- a/group-buying-sys-trigger/pom.xml +++ b/group-buying-sys-trigger/pom.xml @@ -30,6 +30,10 @@ org.redisson redisson-spring-boot-starter + + org.springframework.boot + spring-boot-starter-amqp + edu.whut diff --git a/group-buying-sys-trigger/src/main/java/edu/whut/trigger/listener/TeamSuccessTopicListener.java b/group-buying-sys-trigger/src/main/java/edu/whut/trigger/listener/TeamSuccessTopicListener.java new file mode 100644 index 0000000..8f67270 --- /dev/null +++ b/group-buying-sys-trigger/src/main/java/edu/whut/trigger/listener/TeamSuccessTopicListener.java @@ -0,0 +1,30 @@ +package edu.whut.trigger.listener; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.core.ExchangeTypes; +import org.springframework.amqp.rabbit.annotation.Exchange; +import org.springframework.amqp.rabbit.annotation.Queue; +import org.springframework.amqp.rabbit.annotation.QueueBinding; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Component; + +/** + * @author Fuzhengwei bugstack.cn @小傅哥 + * 结算完成消息监听 + */ +@Slf4j +@Component +public class TeamSuccessTopicListener { + + @RabbitListener( + bindings = @QueueBinding( + value = @Queue(value = "${spring.rabbitmq.config.producer.topic_team_success.queue}"), + exchange = @Exchange(value = "${spring.rabbitmq.config.producer.exchange}", type = ExchangeTypes.TOPIC), + key = "${spring.rabbitmq.config.producer.topic_team_success.routing_key}" + ) + ) + public void listener(String message) { + log.info("接收消息:{}", message); + } + +} diff --git a/group-buying-sys-types/src/main/java/edu/whut/types/event/BaseEvent.java b/group-buying-sys-types/src/main/java/edu/whut/types/event/BaseEvent.java new file mode 100644 index 0000000..c9dd79e --- /dev/null +++ b/group-buying-sys-types/src/main/java/edu/whut/types/event/BaseEvent.java @@ -0,0 +1,30 @@ +package edu.whut.types.event; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + +/** + * 基础时间 + */ +@Data +public abstract class BaseEvent { + + public abstract EventMessage buildEventMessage(T data); + + public abstract String topic(); + + @Data + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class EventMessage { + private String id; + private Date timestamp; + private T data; + } + +} diff --git a/pom.xml b/pom.xml index 5760469..579d704 100644 --- a/pom.xml +++ b/pom.xml @@ -132,6 +132,12 @@ logging-interceptor 3.14.9 + + + org.springframework.boot + spring-boot-starter-amqp + 3.2.0 +