From 482c32a59c22dc26015eaf67b66f43b8b6c8ac40 Mon Sep 17 00:00:00 2001 From: jianghao <332515344@qq.com> Date: Wed, 11 Feb 2026 14:28:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E9=80=9A=E7=9F=A5?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E3=80=81RabbitMQ=E9=9B=86=E6=88=90=E5=8F=8AD?= =?UTF-8?q?ocker=E4=B8=80=E9=94=AE=E9=83=A8=E7=BD=B2=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 新增通知中心功能,支持好友请求、评论、点赞等多种通知类型的持久化与推送 2. 集成 RabbitMQ 用于异步处理动态日志,解耦动态服务与日志记录逻辑 3. 提供完整的 Docker Compose 部署方案及一键启动/停止脚本(Shell/Bat) 4. 优化文件服务,增加图片上传时的自动压缩处理以节省存储空间 5. 增强动态服务,支持通过 shareId 公开访问动态项及关键词搜索功能 6. 完善代码健壮性,在关键业务 Service 层增加 @Transactional 事务控制 --- deploy/README.md | 151 +++++++++++ deploy/conf/minio-config/config.json | 154 +++++++++++ deploy/conf/my-container.cnf | 58 +++++ deploy/conf/my.cnf | 45 ++++ .../conf/nacos-config/application.properties | 233 +++++++++++++++++ deploy/conf/redis-container.conf | 52 ++++ deploy/conf/redis.conf | 48 ++++ deploy/deploy-all.bat | 50 ++++ deploy/deploy-all.sh | 46 ++++ deploy/docker-compose-all.yml | 101 ++++++++ deploy/docker-compose.yaml | 28 ++ deploy/docker-compose.yml | 29 +++ deploy/env-prod.conf | 3 + deploy/start-minio.sh | 10 + deploy/start-mysql.sh | 9 + deploy/start.sh | 10 + deploy/stop-all.bat | 20 ++ deploy/stop-all.sh | 18 ++ nacos-config/application.properties | 243 ++++++++++++++++++ nacos-config/init.d/jdbc.properties | 6 + pom.xml | 8 + .../file/service/impl/FileServiceImpl.java | 10 +- timeline-story-service/pom.xml | 19 +- .../timeline/story/config/RabbitMQConfig.java | 31 +++ .../timeline/story/config/SecurityConfig.java | 23 ++ .../story/controller/StoryController.java | 97 +++---- .../story/controller/StoryItemController.java | 28 +- .../controller/StoryPublicController.java | 27 ++ .../controller/StoryShareController.java | 6 +- .../timeline/story/dao/StoryItemMapper.java | 17 ++ .../timeline/story/entity/StoryActivity.java | 1 + .../com/timeline/story/entity/StoryItem.java | 24 +- .../story/feign/UserServiceClient.java | 26 +- .../story/mq/ActivityLogConsumer.java | 34 +++ .../story/mq/ActivityLogProducer.java | 21 ++ .../story/service/StoryItemService.java | 26 +- .../timeline/story/service/StoryService.java | 18 +- .../impl/StoryActivityServiceImpl.java | 21 ++ .../service/impl/StoryItemServiceImpl.java | 227 +++------------- .../impl/StoryPermissionServiceImpl.java | 6 +- .../story/service/impl/StoryServiceImpl.java | 58 ++++- .../story/vo/StoryDetailWithItemsVo.java | 13 + .../com/timeline/story/vo/StoryItemAddVo.java | 7 +- .../timeline/story/vo/StoryItemShareVo.java | 12 + .../com/timeline/story/vo/StoryItemVo.java | 19 +- .../src/main/resources/application.properties | 7 + .../timeline/story/dao/StoryItemMapper.xml | 41 ++- timeline-user-service/pom.xml | 8 +- .../config/StompPrincipalInterceptor.java | 9 +- .../UserIdPrincipalHandshakeHandler.java | 2 + .../config/XUserIdHandshakeInterceptor.java | 2 + .../user/controller/AuthController.java | 5 +- .../controller/MessagePushController.java | 123 ++++++--- .../controller/NotificationController.java | 31 +++ .../controller/TestNotificationPayload.java | 35 ++- .../user/controller/UserController.java | 6 +- .../controller/UserMessageController.java | 12 +- .../controller/WebSocketTestController.java | 42 ++- .../timeline/user/dao/NotificationMapper.java | 20 ++ .../com/timeline/user/dto/ChatMessage.java | 1 - .../user/dto/NotificationPayload.java | 28 ++ .../timeline/user/dto/NotificationType.java | 11 + .../com/timeline/user/dto/UpdateUser.java | 17 ++ .../com/timeline/user/dto/UserMessageDto.java | 40 --- .../timeline/user/entity/FriendNotify.java | 6 +- .../timeline/user/entity/Notification.java | 20 ++ .../user/service/NotificationService.java | 17 ++ .../user/service/impl/FriendServiceImpl.java | 127 +++------ .../service/impl/NotificationServiceImpl.java | 92 +++++++ .../service/impl/UserAuthServiceImpl.java | 8 +- .../user/service/impl/UserServiceImpl.java | 37 +-- .../timeline/user/ws/ChatWsController.java | 34 --- .../user/ws/WebSocketEventListener.java | 84 +++--- .../user/ws/WebSocketSessionRegistry.java | 1 - .../com/timeline/user/ws/WsNotifyService.java | 27 +- .../timeline/user/dao/NotificationMapper.xml | 33 +++ .../resources/mapper/NotificationMapper.xml | 23 ++ 77 files changed, 2396 insertions(+), 646 deletions(-) create mode 100644 deploy/README.md create mode 100644 deploy/conf/minio-config/config.json create mode 100644 deploy/conf/my-container.cnf create mode 100644 deploy/conf/my.cnf create mode 100644 deploy/conf/nacos-config/application.properties create mode 100644 deploy/conf/redis-container.conf create mode 100644 deploy/conf/redis.conf create mode 100644 deploy/deploy-all.bat create mode 100644 deploy/deploy-all.sh create mode 100644 deploy/docker-compose-all.yml create mode 100644 deploy/docker-compose.yaml create mode 100644 deploy/docker-compose.yml create mode 100644 deploy/env-prod.conf create mode 100644 deploy/start-minio.sh create mode 100644 deploy/start-mysql.sh create mode 100644 deploy/start.sh create mode 100644 deploy/stop-all.bat create mode 100644 deploy/stop-all.sh create mode 100644 nacos-config/application.properties create mode 100644 nacos-config/init.d/jdbc.properties create mode 100644 timeline-story-service/src/main/java/com/timeline/story/config/RabbitMQConfig.java create mode 100644 timeline-story-service/src/main/java/com/timeline/story/config/SecurityConfig.java create mode 100644 timeline-story-service/src/main/java/com/timeline/story/controller/StoryPublicController.java create mode 100644 timeline-story-service/src/main/java/com/timeline/story/mq/ActivityLogConsumer.java create mode 100644 timeline-story-service/src/main/java/com/timeline/story/mq/ActivityLogProducer.java create mode 100644 timeline-story-service/src/main/java/com/timeline/story/vo/StoryDetailWithItemsVo.java create mode 100644 timeline-story-service/src/main/java/com/timeline/story/vo/StoryItemShareVo.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/controller/NotificationController.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/dao/NotificationMapper.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/dto/NotificationPayload.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/dto/NotificationType.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/entity/Notification.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/service/NotificationService.java create mode 100644 timeline-user-service/src/main/java/com/timeline/user/service/impl/NotificationServiceImpl.java delete mode 100644 timeline-user-service/src/main/java/com/timeline/user/ws/ChatWsController.java create mode 100644 timeline-user-service/src/main/resources/com/timeline/user/dao/NotificationMapper.xml create mode 100644 timeline-user-service/src/main/resources/mapper/NotificationMapper.xml diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..296ff8e --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,151 @@ +# 部署指南 + +本目录包含部署 Timeline 系统所需的所有脚本和配置文件。 + +## 目录结构 + +``` +deploy/ +├── conf/ # 配置文件目录 +│ ├── my.cnf # MySQL 配置 (开发环境) +│ ├── my-container.cnf # MySQL 配置 (容器化环境) +│ ├── redis.conf # Redis 配置 (开发环境) +│ ├── redis-container.conf # Redis 配置 (容器化环境) +│ └── minio-config/ # MinIO 配置目录 +├── docker-compose-all.yml # 统一部署文件 (包含所有服务) +├── docker-compose.yml # Nacos 单独部署文件 +├── docker-compose-nacos-shared-db.yml # Nacos 共享数据库部署文件 +├── deploy-all.sh # Linux/macOS 统一部署脚本 +├── deploy-all.bat # Windows 统一部署脚本 +├── stop-all.sh # Linux/macOS 停止服务脚本 +├── stop-all.bat # Windows 停止服务脚本 +├── start-mysql.sh # 单独启动 MySQL +├── start-minio.sh # 单独启动 MinIO +└── start.sh # 单独启动 Redis +``` + +## 部署选项 + +### 1. 完整部署 (推荐) + +完整部署所有服务(MySQL、Redis、MinIO、Nacos 和所有微服务): + +**Linux/macOS:** +```bash +./deploy-all.sh +``` + +**Windows:** +```cmd +deploy-all.bat +``` + +### 2. 使用共享数据库部署 Nacos + +如果要使用共享数据库部署Nacos(与其它服务使用同一数据库): + +```bash +# 启动仅Nacos服务(使用共享数据库) +docker-compose -f docker-compose-nacos-shared-db.yml up -d + +# 或者使用项目根目录的部署文件 +docker-compose -f ../docker-compose-nacos-shared-db.yml up -d +``` + +### 3. 单独部署 Nacos + +如果只需要部署 Nacos 服务: + +**Linux/macOS:** +```bash +docker compose -f docker-compose.yml up -d +``` + +**Windows:** +```cmd +docker compose -f docker-compose.yml up -d +``` + +### 4. 单独启动中间件 + +如果需要单独启动某个中间件服务: + +**启动 MySQL:** +```bash +./start-mysql.sh +``` + +**启动 MinIO:** +```bash +./start-minio.sh +``` + +**启动 Redis:** +```bash +./start.sh +``` + +## 停止服务 + +**Linux/macOS:** +```bash +./stop-all.sh +``` + +**Windows:** +```cmd +stop-all.bat +``` + +## 服务端口映射 + +| 服务 | 端口 | 访问地址 | +|------|------|----------| +| Nacos | 8848 | http://127.0.0.1:8848/nacos | +| Gateway | 30000 | http://127.0.0.1:30000 | +| Story Service | 30001 | http://127.0.0.1:30001 | +| File Service | 30002 | http://127.0.0.1:30002 | +| User Service | 30003 | http://127.0.0.1:30003 | +| MySQL | 33306 | 127.0.0.1:33306 | +| Redis | 36379 | 127.0.0.1:36379 | +| MinIO | 9000 | http://127.0.0.1:9000 | +| MinIO Console | 9090 | http://127.0.0.1:9090 | + +## 默认凭据 + +| 服务 | 用户名 | 密码 | +|------|--------|------| +| Nacos | nacos | nacos | +| MySQL Root | root | WoCloud@9ol7uj | +| Redis | - | 123456 | +| MinIO Root | minioadmin | WoCloud@9ol7uj | + +## 注意事项 + +1. 确保 Docker 和 Docker Compose 已安装并正在运行 +2. 确保有足够的系统资源(推荐至少 4GB 内存) +3. 确保以下端口未被占用:8848, 30000-30003, 33306, 36379, 9000, 9090 +4. 首次启动需要等待约 1-2 分钟,等待所有服务完全启动 +5. 确保MySQL数据库服务器(59.80.22.43:33306)可访问,并且nacos_config数据库已创建 + +## 故障排除 + +### 服务启动失败 +- 检查端口是否被占用 +- 检查 Docker 是否有足够的资源 +- 查看服务日志:`docker compose -f docker-compose-all.yml logs ` + +### Nacos 无法启动 +- 检查MySQL数据库连接:确认数据库服务器可访问,凭据正确,nacos_config数据库存在 +- 检查 [nacos-config/application.properties](file:///D:/workspaces/timeline-server/nacos-config/application.properties) 配置文件中的数据库连接设置 + +### Nacos 无法连接数据库 +- 确认 MySQL 服务已启动 +- 检查网络连接是否正常 +- 确认数据库凭据是否正确 +- 确认 nacos_config 数据库已创建 + +### 微服务无法注册到 Nacos +- 确认 Nacos 服务已启动并正常运行 +- 检查服务间的网络连接 +- 确认服务配置中的 Nacos 地址是否正确 \ No newline at end of file diff --git a/deploy/conf/minio-config/config.json b/deploy/conf/minio-config/config.json new file mode 100644 index 0000000..cfe5336 --- /dev/null +++ b/deploy/conf/minio-config/config.json @@ -0,0 +1,154 @@ +{ + "version": "29", + "credential": { + "accessKey": "minioadmin", + "secretKey": "WoCloud@9ol7uj" + }, + "region": "us-east-1", + "browser": "on", + "worm": "off", + "storageclass": { + "standard": "", + "rrs": "" + }, + "cache": { + "drives": [], + "expiry": 90, + "maxuse": 80, + "exclude": [] + }, + "kms": { + "vault": { + "endpoint": "", + "auth": { + "type": "", + "approle": { + "id": "", + "secret": "" + } + }, + "tls": { + "ciphers": [], + "insecure": true, + "certs": { + "public": "", + "private": "", + "ca": "" + } + } + }, + "kes": { + "endpoint": "", + "key-name": "", + "certs": { + "public": "", + "private": "", + "ca": "" + }, + "tls": { + "ciphers": [], + "insecure": true + } + } + }, + "notify": { + "amqp": { + "1": { + "enable": false, + "url": "", + "exchange": "", + "routingKey": "", + "exchangeType": "", + "deliveryMode": 0, + "mandatory": false, + "immediate": false, + "durable": false, + "internal": false, + "noWait": false, + "autoDeleted": false + } + }, + "elasticsearch": { + "1": { + "enable": false, + "format": "", + "url": "", + "index": "" + } + }, + "kafka": { + "1": { + "enable": false, + "brokers": null, + "topic": "" + } + }, + "mqtt": { + "1": { + "enable": false, + "broker": "", + "topic": "", + "qos": 0, + "clientId": "", + "username": "", + "password": "", + "reconnectInterval": 0, + "keepAliveInterval": 0 + } + }, + "mysql": { + "1": { + "enable": false, + "format": "", + "dsnString": "", + "table": "", + "host": "", + "port": "", + "user": "", + "password": "", + "database": "" + } + }, + "nats": { + "1": { + "enable": false, + "address": "", + "subject": "", + "username": "", + "password": "", + "token": "", + "secure": false, + "pingInterval": 0, + "streaming": { + "enable": false, + "clusterID": "", + "clientID": "", + "async": false, + "maxPubAcksInflight": 0 + } + } + }, + "postgresql": { + "1": { + "enable": false, + "format": "", + "connectionString": "", + "table": "", + "host": "", + "port": 0, + "user": "", + "password": "", + "database": "" + } + }, + "redis": { + "1": { + "enable": false, + "format": "", + "address": "", + "password": "", + "key": "" + } + } + } +} \ No newline at end of file diff --git a/deploy/conf/my-container.cnf b/deploy/conf/my-container.cnf new file mode 100644 index 0000000..c8d9321 --- /dev/null +++ b/deploy/conf/my-container.cnf @@ -0,0 +1,58 @@ +[mysqld] +# Basic settings for containerized environment +port = 3306 +bind-address = 0.0.0.0 +server-id = 1 + +# Data directory +datadir = /var/lib/mysql + +# Character set +character-set-server = utf8mb4 +collation-server = utf8mb4_unicode_ci + +# Log settings +log-error = /var/log/mysql/error.log +slow-query-log = 1 +slow-query-log-file = /var/log/mysql/slow.log +long_query_time = 2 + +# Connection settings +max_connections = 200 +max_connect_errors = 6000 +open_files_limit = 65535 + +# InnoDB settings optimized for container +default-storage-engine = innodb +innodb_buffer_pool_size = 256M +innodb_log_file_size = 64M +innodb_log_buffer_size = 16M +innodb_flush_log_at_trx_commit = 2 +innodb_lock_wait_timeout = 50 + +# Security +skip-name-resolve + +# Binary log (for replication) +log-bin = mysql-bin +binlog-format = ROW +expire_logs_days = 7 + +# Performance settings for container +query_cache_size = 32M +query_cache_type = 1 +tmp_table_size = 64M +max_heap_table_size = 64M + +# Timeout settings +interactive_timeout = 60 +wait_timeout = 60 +net_read_timeout = 30 +net_write_timeout = 60 + +# Network settings +max_allowed_packet = 64M + +# Container specific settings +# Reduce memory usage for container environment +innodb_buffer_pool_instances = 1 \ No newline at end of file diff --git a/deploy/conf/my.cnf b/deploy/conf/my.cnf new file mode 100644 index 0000000..a9c1924 --- /dev/null +++ b/deploy/conf/my.cnf @@ -0,0 +1,45 @@ +[mysqld] +# Basic settings +port = 33306 +bind-address = 0.0.0.0 +server-id = 1 + +# Data directory +datadir = /var/lib/mysql + +# Character set +character-set-server = utf8mb4 +collation-server = utf8mb4_unicode_ci + +# Log settings +log-error = /var/log/mysql/error.log +slow-query-log = 1 +slow-query-log-file = /var/log/mysql/slow.log +long_query_time = 2 + +# Connection settings +max_connections = 200 +max_connect_errors = 6000 +open_files_limit = 65535 + +# InnoDB settings +default-storage-engine = innodb +innodb_buffer_pool_size = 1G +innodb_log_file_size = 256M +innodb_log_buffer_size = 64M +innodb_flush_log_at_trx_commit = 2 +innodb_lock_wait_timeout = 50 + +# Security +skip-name-resolve + +# Binary log (for replication) +log-bin = mysql-bin +binlog-format = ROW +expire_logs_days = 7 + +# Performance settings +query_cache_size = 64M +query_cache_type = 1 +tmp_table_size = 256M +max_heap_table_size = 256M \ No newline at end of file diff --git a/deploy/conf/nacos-config/application.properties b/deploy/conf/nacos-config/application.properties new file mode 100644 index 0000000..6157be3 --- /dev/null +++ b/deploy/conf/nacos-config/application.properties @@ -0,0 +1,233 @@ +# spring +server.servlet.contextPath=/nacos +server.error.include-message=ALWAYS +server.error.include-binding-errors=ALWAYS +spring.autoconfigure.exclude=org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration +spring.application.name=nacos-server +logging.config=classpath:org/springframework/boot/logging/logback/no-xml-config.xml +logging.exception-conversion-word=%wEx +logging.level.org.springframework.web=DEBUG +logging.level.org.springframework.transaction=DEBUG +logging.level.org.springframework.transaction.interceptor=DEBUG +logging.level.org.springframework.jdbc=DEBUG +logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG +logging.level.com.alibaba.nacos.core.datasource=DEBUG +logging.level.com.alibaba.nacos.core.auth=DEBUG +logging.level.com.alibaba.nacos.core.env=DEBUG +logging.level.com.alibaba.nacos.core.cluster=DEBUG +logging.level.com.alibaba.nacos.core.distributed=DEBUG +logging.level.com.alibaba.nacos.core.namespace=DEBUG +logging.level.com.alibaba.nacos.core.listener=DEBUG +logging.level.com.alibaba.nacos.core.remote=DEBUG +logging.level.com.alibaba.nacos.core.routing=DEBUG +logging.level.com.alibaba.nacos.core.service=DEBUG +logging.level.com.alibaba.nacos.core.sys=DEBUG +logging.level.com.alibaba.nacos.core.trace=DEBUG +logging.level.com.alibaba.nacos.core.utils=DEBUG +logging.level.com.alibaba.nacos.core.auth.ldap=DEBUG +logging.level.com.alibaba.nacos.core.auth.ram=DEBUG +logging.level.com.alibaba.nacos.core.cluster.log=DEBUG +logging.level.com.alibaba.nacos.core.cluster.lookup=DEBUG +logging.level.com.alibaba.nacos.core.cluster.remote=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM.RAFT=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM.DISTRO=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM.DISTRO.HASH=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM.DISTRO.LITE=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM.DISTRO.LITE.SYNC=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM.DISTRO.LITE.SYNC.SNAPSHOT=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM.DISTRO.LITE.SYNC.SNAPSHOT.SNAPSHOT=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM.DISTRO.LITE.SYNC.SNAPSHOT.SNAPSHOT.SNAPSHOT=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM.DISTRO.LITE.SYNC.SNAPSHOT.SNAPSHOT.SNAPSHOT.SNAPSHOT=DEBUG + +# nacos +nacos.core.auth.system.type=nacos +nacos.core.auth.enabled=false +nacos.core.auth.caching.enabled=true +nacos.core.auth.enable.userAgentAuthWhite=false +nacos.core.auth.server.identity.key=nacos +nacos.core.auth.server.identity.value=nacos +nacos.core.auth.plugin.nacos.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789 +nacos.core.auth.plugin.nacos.token.expire.seconds=18000 +nacos.core.sampling.enabled=true +nacos.core.sampling.probability=0.01 +nacos.core.metrics.enabled=true +nacos.core.metrics.dump.enabled=true +nacos.core.metrics.dump.period=600 +nacos.core.metrics.storage=prometheus +nacos.core.metrics.log.file=/home/nacos/logs/metrics.log +nacos.core.metrics.log.period=60 +nacos.core.cluster.lookup.type=address-server +nacos.core.cluster.address-server.url=https://nacos.io +nacos.core.cluster.address-server.endpoint=address-server.nacos.io +nacos.core.cluster.address-server.namespace=public +nacos.core.cluster.address-server.tenant=public +nacos.core.cluster.address-server.is.auth.enabled=false +nacos.core.cluster.address-server.username=nacos +nacos.core.cluster.address-server.password=nacos +nacos.core.cluster.address-server.access.key= +nacos.core.cluster.address-server.secret.key= +nacos.core.cluster.member.list= +nacos.core.env=standalone +nacos.core.insert.default.kv=on +nacos.core.loadconfig.enable=true +nacos.core.loadconfig.retrytime=2 +nacos.core.loadconfig.retrysleepmillis=1000 +nacos.core.notify.retrytimes=5 +nacos.core.notify.retrydelaymillis=1000 +nacos.core.notify.maxbatchsize=5000 +nacos.core.notify.maxqueuesize=20000 +nacos.core.notify.retryqueuecount=2 +nacos.core.notify.timeout=5000 +nacos.core.notify.log.enabled=true +nacos.core.notify.connection.timeout=2000 +nacos.core.notify.socket.timeout=60000 +nacos.core.notify.threads=20 +nacos.core.notify.thread.keepalive=60 +nacos.core.notify.request.failed.retrytimes=3 +nacos.core.notify.distro.protocol=raft +nacos.core.notify.distrolist.sync.retrytimes=3 +nacos.core.notify.distrolist.sync.timeout=5000 +nacos.core.notify.distrolist.sync.threads=20 +nacos.core.notify.distrolist.sync.thread.keepalive=60 +nacos.core.notify.distrolist.sync.maxqueuesize=20000 +nacos.core.notify.distrolist.sync.retryqueuecount=2 +nacos.core.notify.distrolist.sync.log.enabled=true +nacos.core.notify.distrolist.sync.timeout=5000 +nacos.core.notify.distrolist.sync.connection.timeout=2000 +nacos.core.notify.distrolist.sync.socket.timeout=60000 +nacos.core.notify.distrolist.sync.threads=20 +nacos.core.notify.distrolist.sync.thread.keepalive=60 +nacos.core.notify.distrolist.sync.maxqueuesize=20000 +nacos.core.notify.distrolist.sync.retryqueuecount=2 +nacos.core.notify.distrolist.sync.log.enabled=true +nacos.core.notify.distrolist.sync.timeout=5000 +nacos.core.notify.distrolist.sync.connection.timeout=2000 +nacos.core.notify.distrolist.sync.socket.timeout=60000 +nacos.core.notify.distrolist.sync.threads=20 +nacos.core.notify.distrolist.sync.thread.keepalive=60 +nacos.core.notify.distrolist.sync.maxqueuesize=20000 +nacos.core.notify.distrolist.sync.retryqueuecount=2 +nacos.core.notify.distrolist.sync.log.enabled=true + +# naming +nacos.naming.distro.taskDispatchThreadCount=20 +nacos.naming.distro.taskDispatchPeriod=200 +nacos.naming.distro.batchSyncKeyCount=1000 +nacos.naming.distro.initDataOnly=false +nacos.naming.distro.syncRetryDelay=5000 +nacos.naming.redo.delay=1000 +nacos.naming.redo.expired=60000 +nacos.naming.redo.retries=3 +nacos.naming.cache.concurrent.size=64 +nacos.naming.cache.expired.time=10000 +nacos.naming.cache.updateTaskInterval=1000 +nacos.naming.log.fileName=alipay-jraft.log +nacos.naming.log.level=warn +nacos.naming.data.warmup=true +nacos.naming.empty.service.autoClean=true +nacos.naming.empty.service.clean.initial.delay.ms=50000 +nacos.naming.empty.service.clean.period.time.ms=30000 +nacos.naming.push.empty.protect=true +nacos.naming.push.max.application=6000 +nacos.naming.push.max.payload.bytes=1048576 +nacos.naming.push.max.qps=20000 +nacos.naming.push.dump.qps=10000 +nacos.naming.push.health.check.fail.timeout=3000 +nacos.naming.push.verify.enabled=false +nacos.naming.health.push.status.check.enabled=true +nacos.naming.health.push.status.check.time.delay=5000 +nacos.naming.push.connection.max.hold.time=300000 +nacos.naming.service.batch.register.thread.count=1 +nacos.naming.service.batch.register.thread.work.queue.size=10000 +nacos.naming.service.batch.register.enabled=false +nacos.naming.service.batch.register.interval=1000 +nacos.naming.service.batch.register.batch.size=1000 +nacos.naming.service.batch.register.delay=1000 +nacos.naming.service.batch.register.timeout=30000 +nacos.naming.service.batch.register.retry.times=3 +nacos.naming.service.batch.register.retry.delay=1000 +nacos.naming.service.batch.register.retry.max.delay=5000 +nacos.naming.service.batch.register.retry.backoff=1.5 +nacos.naming.service.batch.register.retry.max.attempts=5 +nacos.naming.service.batch.register.retry.max.delay=30000 +nacos.naming.service.batch.register.retry.backoff=2.0 +nacos.naming.service.batch.register.retry.max.attempts=10 +nacos.naming.service.batch.register.retry.max.delay=60000 +nacos.naming.service.batch.register.retry.backoff=2.0 + +# config +nacos.config.log.level=warn +nacos.config.dumpTask.delay=30 +nacos.config.dumpTask.period=300 +nacos.config.syncTask.timeout=30 +nacos.config.syncTask.retryTimes=3 +nacos.config.syncTask.retryDelay=1000 +nacos.config.notifyTask.timeout=30 +nacos.config.notifyTask.retryTimes=3 +nacos.config.notifyTask.retryDelay=1000 +nacos.config.notifyTask.threadCount=2 +nacos.config.notifyTask.maxQueueSize=10000 +nacos.config.notifyTask.log.enabled=true +nacos.config.notifyTask.timeout=5000 +nacos.config.notifyTask.connection.timeout=2000 +nacos.config.notifyTask.socket.timeout=60000 +nacos.config.notifyTask.threads=20 +nacos.config.notifyTask.thread.keepalive=60 +nacos.config.notifyTask.maxqueuesize=20000 +nacos.config.notifyTask.retryqueuecount=2 +nacos.config.notifyTask.log.enabled=true + +# console +nacos.core.console.enableYaml=false +nacos.core.console.system.defender.enabled=false +nacos.core.console.system.defender.rate.limiting.threshold=100 +nacos.core.console.system.defender.rate.limiting.duration=1 +nacos.core.console.system.defender.rate.limiting.enabled=false +nacos.core.console.system.defender.rate.limiting.window=60 +nacos.core.console.system.defender.rate.limiting.max=100 +nacos.core.console.system.defender.rate.limiting.min=10 +nacos.core.console.system.defender.rate.limiting.factor=1.0 +nacos.core.console.system.defender.rate.limiting.strategy=fixed +nacos.core.console.system.defender.rate.limiting.algorithm=token-bucket +nacos.core.console.system.defender.rate.limiting.mode=local +nacos.core.console.system.defender.rate.limiting.scope=global +nacos.core.console.system.defender.rate.limiting.key=ip +nacos.core.console.system.defender.rate.limiting.enabled=false + +# datasource +nacos.datasource.platform=mysql +nacos.datasource.db.num=1 +nacos.datasource.db.url.0=jdbc:mysql://59.80.22.43:33306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC +nacos.datasource.db.user=root +nacos.datasource.db.password=WoCloud@9ol7uj +nacos.datasource.db.pool.config.connectionTimeout=30000 +nacos.datasource.db.pool.config.validationTimeout=10000 +nacos.datasource.db.pool.config.maximumPoolSize=20 +nacos.datasource.db.pool.config.minimumIdle=2 +nacos.datasource.db.pool.config.maxWait=3000 +nacos.datasource.db.pool.config.timeBetweenEvictionRunsMillis=60000 +nacos.datasource.db.pool.config.minEvictableIdleTimeMillis=300000 +nacos.datasource.db.pool.config.testWhileIdle=true +nacos.datasource.db.pool.config.testOnBorrow=true +nacos.datasource.db.pool.config.testOnReturn=false +nacos.datasource.db.pool.config.poolName=HikariCP +nacos.datasource.db.pool.config.connectionTestQuery=SELECT 1 +nacos.datasource.db.pool.config.allowPoolSuspension=false +nacos.datasource.db.pool.config.autoCommit=true +nacos.datasource.db.pool.config.idleTimeout=600000 +nacos.datasource.db.pool.config.jdbc4ConnectionTest=true +nacos.datasource.db.pool.config.useStatementFinalizer=true +nacos.datasource.db.pool.config.leakDetectionThreshold=60000 +nacos.datasource.db.pool.config.dataSourceProperties.cachePrepStmts=true +nacos.datasource.db.pool.config.dataSourceProperties.prepStmtCacheSize=250 +nacos.datasource.db.pool.config.dataSourceProperties.prepStmtCacheSqlLimit=2048 +nacos.datasource.db.pool.config.dataSourceProperties.useServerPrepStmts=true +nacos.datasource.db.pool.config.dataSourceProperties.useLocalSessionState=true +nacos.datasource.db.pool.config.dataSourceProperties.rewriteBatchedStatements=true +nacos.datasource.db.pool.config.dataSourceProperties.cacheResultSetMetadata=true +nacos.datasource.db.pool.config.dataSourceProperties.cacheServerConfiguration=true +nacos.datasource.db.pool.config.dataSourceProperties.elideSetters=true +nacos.datasource.db.pool.config.dataSourceProperties.maintainTimeStats=false \ No newline at end of file diff --git a/deploy/conf/redis-container.conf b/deploy/conf/redis-container.conf new file mode 100644 index 0000000..4eb7912 --- /dev/null +++ b/deploy/conf/redis-container.conf @@ -0,0 +1,52 @@ +# Redis configuration for containerized environment +bind 0.0.0.0 +protected-mode yes +port 6379 +timeout 0 +tcp-keepalive 300 +daemonize no +pidfile /var/run/redis.pid +loglevel notice +logfile /var/log/redis/redis-server.log +databases 16 +save 900 1 +save 300 10 +save 60 10000 +stop-writes-on-bgsave-error yes +rdbcompression yes +rdbchecksum yes +dbfilename dump.rdb +dir /data +requirepass 123456 +masterauth 123456 +maxmemory 2gb +maxmemory-policy allkeys-lru +appendonly yes +appendfilename "appendonly.aof" +appendfsync everysec +no-appendfsync-on-rewrite no +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb +lua-time-limit 5000 +cluster-enabled no +cluster-config-file nodes.conf +cluster-node-timeout 15000 +slowlog-log-slower-than 10000 +slowlog-max-len 128 +hash-max-ziplist-entries 512 +hash-max-ziplist-value 64 +list-max-ziplist-entries 512 +list-max-ziplist-value 64 +set-max-intset-entries 512 +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 +activerehashing yes +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit slave 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 +hz 10 +aof-rewrite-incremental-fsync yes + +# Container specific settings +# Allow more clients for containerized environment +maxclients 10000 \ No newline at end of file diff --git a/deploy/conf/redis.conf b/deploy/conf/redis.conf new file mode 100644 index 0000000..1b1de53 --- /dev/null +++ b/deploy/conf/redis.conf @@ -0,0 +1,48 @@ +# Redis configuration +bind 0.0.0.0 +protected-mode yes +port 6379 +timeout 0 +tcp-keepalive 300 +daemonize no +pidfile /var/run/redis.pid +loglevel notice +logfile /var/log/redis/redis-server.log +databases 16 +save 900 1 +save 300 10 +save 60 10000 +stop-writes-on-bgsave-error yes +rdbcompression yes +rdbchecksum yes +dbfilename dump.rdb +dir /data +requirepass 123456 +masterauth 123456 +maxmemory 2gb +maxmemory-policy allkeys-lru +appendonly yes +appendfilename "appendonly.aof" +appendfsync everysec +no-appendfsync-on-rewrite no +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb +lua-time-limit 5000 +cluster-enabled no +cluster-config-file nodes.conf +cluster-node-timeout 15000 +slowlog-log-slower-than 10000 +slowlog-max-len 128 +hash-max-ziplist-entries 512 +hash-max-ziplist-value 64 +list-max-ziplist-entries 512 +list-max-ziplist-value 64 +set-max-intset-entries 512 +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 +activerehashing yes +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit slave 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 +hz 10 +aof-rewrite-incremental-fsync yes \ No newline at end of file diff --git a/deploy/deploy-all.bat b/deploy/deploy-all.bat new file mode 100644 index 0000000..ded3b2d --- /dev/null +++ b/deploy/deploy-all.bat @@ -0,0 +1,50 @@ +@echo off +echo =========================================== +echo Starting Timeline System with All Services +echo =========================================== + +REM 检查 Docker 是否运行 +docker version >nul 2>&1 +if %errorlevel% neq 0 ( + echo 错误: 未找到 Docker 或 Docker 未运行,请先启动 Docker + exit /b 1 +) + +REM 检查 Docker Compose 是否可用 +docker compose version >nul 2>&1 +if %errorlevel% neq 0 ( + echo 错误: 未找到 Docker Compose,请先安装 Docker Compose + exit /b 1 +) + +echo 构建项目... +call mvn clean package -DskipTests + +if errorlevel 1 ( + echo 构建失败! + pause + exit /b 1 +) + +echo 启动所有服务... +docker compose -f docker-compose-all.yml up -d + +echo 等待服务启动... +timeout /t 60 /nobreak + +echo 检查服务状态... +docker compose -f docker-compose-all.yml ps + +echo =========================================== +echo 部署完成!服务访问地址: +echo - Nacos: http://127.0.0.1:8848/nacos (用户名: nacos, 密码: nacos) +echo - Gateway: http://127.0.0.1:30000 +echo - Story Service: http://127.0.0.1:30001 +echo - File Service: http://127.0.0.1:30002 +echo - User Service: http://127.0.0.1:30003 +echo - MySQL: http://127.0.0.1:33306 +echo - Redis: http://127.0.0.1:36379 +echo - MinIO: http://127.0.0.1:9000 (控制台: http://127.0.0.1:9090) +echo =========================================== + +pause \ No newline at end of file diff --git a/deploy/deploy-all.sh b/deploy/deploy-all.sh new file mode 100644 index 0000000..fd634d8 --- /dev/null +++ b/deploy/deploy-all.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +echo "===========================================" +echo "Starting Timeline System with All Services" +echo "===========================================" + +# 检查 Docker 是否运行 +if ! docker version > /dev/null 2>&1; then + echo "错误: 未找到 Docker 或 Docker 未运行,请先启动 Docker" + exit 1 +fi + +# 检查 Docker Compose 是否可用 +if ! docker compose version > /dev/null 2>&1; then + echo "错误: 未找到 Docker Compose,请先安装 Docker Compose" + exit 1 +fi + +echo "构建项目..." +mvn clean package -DskipTests + +if [ $? -ne 0 ]; then + echo "构建失败!" + exit 1 +fi + +echo "启动所有服务..." +docker compose -f docker-compose-all.yml up -d + +echo "等待服务启动..." +sleep 60 + +echo "检查服务状态..." +docker compose -f docker-compose-all.yml ps + +echo "===========================================" +echo "部署完成!服务访问地址:" +echo "- Nacos: http://127.0.0.1:8848/nacos (用户名: nacos, 密码: nacos)" +echo "- Gateway: http://127.0.0.1:30000" +echo "- Story Service: http://127.0.0.1:30001" +echo "- File Service: http://127.0.0.1:30002" +echo "- User Service: http://127.0.0.1:30003" +echo "- MySQL: http://127.0.0.1:33306" +echo "- Redis: http://127.0.0.1:36379" +echo "- MinIO: http://127.0.0.1:9000 (控制台: http://127.0.0.1:9090)" +echo "===========================================" \ No newline at end of file diff --git a/deploy/docker-compose-all.yml b/deploy/docker-compose-all.yml new file mode 100644 index 0000000..efa8ecc --- /dev/null +++ b/deploy/docker-compose-all.yml @@ -0,0 +1,101 @@ +version: '3.8' + +services: + mysql: + image: mysql:8.0.31 + container_name: timeline-mysql + ports: + - "33306:33306" + environment: + MYSQL_ROOT_PASSWORD: WoCloud@9ol7uj + volumes: + - mysql_data:/var/lib/mysql + - mysql_log:/var/log/mysql + - ./conf/my-container.cnf:/etc/mysql/my.cnf + command: --default-authentication-plugin=mysql_native_password + restart: always + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + timeout: 20s + retries: 10 + start_period: 40s + + redis: + image: redis:7-alpine + container_name: timeline-redis + ports: + - "36379:6379" + command: redis-server --requirepass 123456 + volumes: + - redis_data:/data + - ./conf/redis-container.conf:/etc/redis/redis.conf + restart: always + healthcheck: + test: ["CMD", "redis-cli", "ping"] + timeout: 3s + retries: 5 + start_period: 30s + + minio: + image: minio/minio:RELEASE.2025-04-22T22-12-26Z + container_name: timeline-minio + ports: + - "9000:9000" + - "9090:9090" + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: WoCloud@9ol7uj + volumes: + - minio_data:/data + - ./conf/minio-config:/root/.minio + command: server /data --console-address ":9090" --address ":9000" + restart: always + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 30s + timeout: 20s + retries: 3 + start_period: 40s + + nacos: + image: nacos/nacos-server:v2.3.0 + container_name: nacos-server + environment: + - MODE=standalone + - SPRING_DATASOURCE_PLATFORM=mysql + - MYSQL_SERVICE_HOST=timeline-mysql # 修正:使用正确的容器名 + - MYSQL_SERVICE_PORT=3306 + - MYSQL_SERVICE_DB_NAME=nacos_config + - MYSQL_SERVICE_USER=root + - MYSQL_SERVICE_PASSWORD=WoCloud@9ol7uj + - NACOS_SERVER_IP=127.0.0.1 + - PREFER_HOST_MODE=hostname + - JVM_XMS=512m + - JVM_XMX=512m + - JVM_XMN=256m + - SERVER_PORT=8848 + volumes: + - ../nacos-config/application.properties:/home/nacos/conf/application.properties + - ../nacos-config/init.d:/home/nacos/init.d + - nacos_logs:/home/nacos/logs + - nacos_data:/home/nacos/data + ports: + - "8848:8848" + - "9848:9848" + restart: always + depends_on: + - mysql + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8848/nacos/v1/ns/operator/metrics"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + +volumes: + mysql_data: + mysql_log: + redis_data: + minio_data: + nacos_logs: + nacos_data: \ No newline at end of file diff --git a/deploy/docker-compose.yaml b/deploy/docker-compose.yaml new file mode 100644 index 0000000..187bf87 --- /dev/null +++ b/deploy/docker-compose.yaml @@ -0,0 +1,28 @@ +version: "3" + +networks: + gitea: + external: false + +services: + server: + image: docker.gitea.com/gitea:nightly + container_name: gitea + environment: + - USER_UID=1000 + - USER_GID=1000 + - GITEA__database__DB_TYPE=mysql + - GITEA__database__HOST=59.80.22.43:33306 + - GITEA__database__NAME=gitea + - GITEA__database__USER=gitea + - GITEA__database__PASSWD=your-password + restart: always + networks: + - gitea + volumes: + - ./gitea:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + ports: + - "30000:3000" + - "32222:22" diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml new file mode 100644 index 0000000..fbbefe7 --- /dev/null +++ b/deploy/docker-compose.yml @@ -0,0 +1,29 @@ +version: "3.8" +services: + nacos: + image: nacos/nacos-server:v2.3.0 + container_name: nacos-standalone + environment: + - MODE=standalone + - SPRING_DATASOURCE_PLATFORM=mysql + - MYSQL_SERVICE_HOST=localhost + - MYSQL_SERVICE_PORT=33306 + - MYSQL_SERVICE_DB_NAME=nacos_config + - MYSQL_SERVICE_USER=root + - MYSQL_SERVICE_PASSWORD=WoCloud@9ol7uj + - NACOS_SERVER_IP=127.0.0.1 + volumes: + - ../nacos-config/application.properties:/home/nacos/conf/application.properties + - nacos_logs:/home/nacos/logs + - nacos_data:/home/nacos/data + ports: + - "8848:8848" + - "9848:9848" + - "9888:9888" + restart: always + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8848/nacos/v1/ns/operator/metrics"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s diff --git a/deploy/env-prod.conf b/deploy/env-prod.conf new file mode 100644 index 0000000..36f2616 --- /dev/null +++ b/deploy/env-prod.conf @@ -0,0 +1,3 @@ +# 环境变量配置文件 - 生产环境 +# Nacos服务器地址 - 生产环境 +export NACOS_SERVER_ADDR=nacos-server:8848 \ No newline at end of file diff --git a/deploy/start-minio.sh b/deploy/start-minio.sh new file mode 100644 index 0000000..8300163 --- /dev/null +++ b/deploy/start-minio.sh @@ -0,0 +1,10 @@ +docker run \ +--name minio \ +-p 9000:9000 \ +-p 9090:9090 \ +-d \ +-e "MINIO_ROOT_USER=minioadmin" \ +-e "MINIO_ROOT_PASSWORD=WoCloud@9ol7uj" \ +-v /mnt/sdc/workspace/minio-data:/data \ +-v /usr/local/minio-config:/root/.minio \ +minio/minio:RELEASE.2025-04-22T22-12-26Z server /data --console-address ":9090" --address ":9000" diff --git a/deploy/start-mysql.sh b/deploy/start-mysql.sh new file mode 100644 index 0000000..c91a886 --- /dev/null +++ b/deploy/start-mysql.sh @@ -0,0 +1,9 @@ +docker run -p 33306:3306 \ +--restart=always \ +--name mysql \ +--privileged=true \ +-v /mnt/sdc/workspace/mysql/log:/var/log/mysql \ +-v /mnt/sdc/workspace/mysql/data:/var/lib/mysql \ +-v /mnt/sdc/workspace/mysql/conf/my.cnf:/etc/mysql/my.cnf \ +-e MYSQL_ROOT_PASSWORD=WoCloud@9ol7uj \ +-d mysql:8.0.31 diff --git a/deploy/start.sh b/deploy/start.sh new file mode 100644 index 0000000..c8d22b5 --- /dev/null +++ b/deploy/start.sh @@ -0,0 +1,10 @@ +docker run -d \ + --name redis \ + -p 36379:6379 \ + -v ./conf/redis.conf:/etc/redis/redis.conf \ + -v ./data:/data \ + --restart=always \ + redis:latest \ + redis-server /etc/redis/redis.conf \ + --appendonly yes \ + --requirepass 123456 diff --git a/deploy/stop-all.bat b/deploy/stop-all.bat new file mode 100644 index 0000000..92beedf --- /dev/null +++ b/deploy/stop-all.bat @@ -0,0 +1,20 @@ +@echo off +echo =========================================== +echo Stopping Timeline System +echo =========================================== + +REM 检查 Docker 是否运行 +docker version >nul 2>&1 +if %errorlevel% neq 0 ( + echo 错误: 未找到 Docker 或 Docker 未运行 + exit /b 1 +) + +echo 停止所有服务... +docker compose -f docker-compose-all.yml down + +echo =========================================== +echo 所有服务已停止 +echo =========================================== + +pause \ No newline at end of file diff --git a/deploy/stop-all.sh b/deploy/stop-all.sh new file mode 100644 index 0000000..72ed2c4 --- /dev/null +++ b/deploy/stop-all.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +echo "===========================================" +echo "Stopping Timeline System" +echo "===========================================" + +# 检查 Docker 是否运行 +if ! docker version > /dev/null 2>&1; then + echo "错误: 未找到 Docker 或 Docker 未运行" + exit 1 +fi + +echo "停止所有服务..." +docker compose -f docker-compose-all.yml down + +echo "===========================================" +echo "所有服务已停止" +echo "===========================================" \ No newline at end of file diff --git a/nacos-config/application.properties b/nacos-config/application.properties new file mode 100644 index 0000000..48349f9 --- /dev/null +++ b/nacos-config/application.properties @@ -0,0 +1,243 @@ +# spring +server.servlet.contextPath=/nacos +server.port=8848 +server.tomcat.uri-encoding=UTF-8 +server.tomcat.max-threads=100 +server.tomcat.max-connections=10000 +server.error.include-message=ALWAYS +server.error.include-binding-errors=ALWAYS +spring.autoconfigure.exclude=org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration +spring.application.name=nacos-server +logging.config=classpath:org/springframework/boot/logging/logback/no-xml-config.xml +logging.exception-conversion-word=%wEx +logging.level.org.springframework.web=DEBUG +logging.level.org.springframework.transaction=DEBUG +logging.level.org.springframework.transaction.interceptor=DEBUG +logging.level.org.springframework.jdbc=DEBUG +logging.level.com.alibaba.nacos.core.datasource=DEBUG +logging.level.com.alibaba.nacos.core.auth=DEBUG +logging.level.com.alibaba.nacos.core.env=DEBUG +logging.level.com.alibaba.nacos.core.cluster=DEBUG +logging.level.com.alibaba.nacos.core.distributed=DEBUG +logging.level.com.alibaba.nacos.core.namespace=DEBUG +logging.level.com.alibaba.nacos.core.listener=DEBUG +logging.level.com.alibaba.nacos.core.remote=DEBUG +logging.level.com.alibaba.nacos.core.routing=DEBUG +logging.level.com.alibaba.nacos.core.service=DEBUG +logging.level.com.alibaba.nacos.core.sys=DEBUG +logging.level.com.alibaba.nacos.core.trace=DEBUG +logging.level.com.alibaba.nacos.core.utils=DEBUG +logging.level.com.alibaba.nacos.core.auth.ldap=DEBUG +logging.level.com.alibaba.nacos.core.auth.ram=DEBUG +logging.level.com.alibaba.nacos.core.cluster.log=DEBUG +logging.level.com.alibaba.nacos.core.cluster.lookup=DEBUG +logging.level.com.alibaba.nacos.core.cluster.remote=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM.RAFT=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM.DISTRO=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM.DISTRO.HASH=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM.DISTRO.LITE=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM.DISTRO.LITE.SYNC=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM.DISTRO.LITE.SYNC.SNAPSHOT=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM.DISTRO.LITE.SYNC.SNAPSHOT.SNAPSHOT=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM.DISTRO.LITE.SYNC.SNAPSHOT.SNAPSHOT.SNAPSHOT=DEBUG +logging.level.com.alibaba.nacos.core.cluster.server.member.CM.DISTRO.LITE.SYNC.SNAPSHOT.SNAPSHOT.SNAPSHOT.SNAPSHOT=DEBUG + +# nacos +nacos.core.auth.system.type=nacos +nacos.core.auth.enabled=false +nacos.core.auth.caching.enabled=true +nacos.core.auth.enable.userAgentAuthWhite=false +nacos.core.auth.server.identity.key=nacos +nacos.core.auth.server.identity.value=nacos +nacos.core.auth.plugin.nacos.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789 +nacos.core.auth.plugin.nacos.token.expire.seconds=18000 +nacos.core.sampling.enabled=true +nacos.core.sampling.probability=0.01 +nacos.core.metrics.enabled=true +nacos.core.metrics.dump.enabled=true +nacos.core.metrics.dump.period=600 +nacos.core.metrics.storage=prometheus +nacos.core.metrics.log.file=/home/nacos/logs/metrics.log +nacos.core.metrics.log.period=60 +nacos.core.cluster.lookup.type=address-server +nacos.core.cluster.address-server.url=https://nacos.io +nacos.core.cluster.address-server.endpoint=address-server.nacos.io +nacos.core.cluster.address-server.namespace=public +nacos.core.cluster.address-server.tenant=public +nacos.core.cluster.address-server.is.auth.enabled=false +nacos.core.cluster.address-server.username=nacos +nacos.core.cluster.address-server.password=nacos +nacos.core.cluster.address-server.access.key= +nacos.core.cluster.address-server.secret.key= +nacos.core.cluster.member.list= +nacos.core.env=standalone +nacos.core.insert.default.kv=on +nacos.core.loadconfig.enable=true +nacos.core.loadconfig.retrytime=2 +nacos.core.loadconfig.retrysleepmillis=1000 +nacos.core.notify.retrytimes=5 +nacos.core.notify.retrydelaymillis=1000 +nacos.core.notify.maxbatchsize=5000 +nacos.core.notify.maxqueuesize=20000 +nacos.core.notify.retryqueuecount=2 +nacos.core.notify.timeout=5000 +nacos.core.notify.log.enabled=true +nacos.core.notify.connection.timeout=2000 +nacos.core.notify.socket.timeout=60000 +nacos.core.notify.threads=20 +nacos.core.notify.thread.keepalive=60 +nacos.core.notify.request.failed.retrytimes=3 +nacos.core.notify.distro.protocol=raft +nacos.core.notify.distrolist.sync.retrytimes=3 +nacos.core.notify.distrolist.sync.timeout=5000 +nacos.core.notify.distrolist.sync.threads=20 +nacos.core.notify.distrolist.sync.thread.keepalive=60 +nacos.core.notify.distrolist.sync.maxqueuesize=20000 +nacos.core.notify.distrolist.sync.retryqueuecount=2 +nacos.core.notify.distrolist.sync.log.enabled=true +nacos.core.notify.distrolist.sync.timeout=5000 +nacos.core.notify.distrolist.sync.connection.timeout=2000 +nacos.core.notify.distrolist.sync.socket.timeout=60000 +nacos.core.notify.distrolist.sync.threads=20 +nacos.core.notify.distrolist.sync.thread.keepalive=60 +nacos.core.notify.distrolist.sync.maxqueuesize=20000 +nacos.core.notify.distrolist.sync.retryqueuecount=2 +nacos.core.notify.distrolist.sync.log.enabled=true +nacos.core.notify.distrolist.sync.timeout=5000 +nacos.core.notify.distrolist.sync.connection.timeout=2000 +nacos.core.notify.distrolist.sync.socket.timeout=60000 +nacos.core.notify.distrolist.sync.threads=20 +nacos.core.notify.distrolist.sync.thread.keepalive=60 +nacos.core.notify.distrolist.sync.maxqueuesize=20000 +nacos.core.notify.distrolist.sync.retryqueuecount=2 +nacos.core.notify.distrolist.sync.log.enabled=true + +# naming +nacos.naming.distro.taskDispatchThreadCount=20 +nacos.naming.distro.taskDispatchPeriod=200 +nacos.naming.distro.batchSyncKeyCount=1000 +nacos.naming.distro.initDataOnly=false +nacos.naming.distro.syncRetryDelay=5000 +nacos.naming.redo.delay=1000 +nacos.naming.redo.expired=60000 +nacos.naming.redo.retries=3 +nacos.naming.cache.concurrent.size=64 +nacos.naming.cache.expired.time=10000 +nacos.naming.cache.updateTaskInterval=1000 +nacos.naming.log.fileName=alipay-jraft.log +nacos.naming.log.level=warn +nacos.naming.data.warmup=true +nacos.naming.empty.service.autoClean=true +nacos.naming.empty.service.clean.initial.delay.ms=50000 +nacos.naming.empty.service.clean.period.time.ms=30000 +nacos.naming.push.empty.protect=true +nacos.naming.push.max.application=6000 +nacos.naming.push.max.payload.bytes=1048576 +nacos.naming.push.max.qps=20000 +nacos.naming.push.dump.qps=10000 +nacos.naming.push.health.check.fail.timeout=3000 +nacos.naming.push.verify.enabled=false +nacos.naming.health.push.status.check.enabled=true +nacos.naming.health.push.status.check.time.delay=5000 +nacos.naming.push.connection.max.hold.time=300000 +nacos.naming.service.batch.register.thread.count=1 +nacos.naming.service.batch.register.thread.work.queue.size=10000 +nacos.naming.service.batch.register.enabled=false +nacos.naming.service.batch.register.interval=1000 +nacos.naming.service.batch.register.batch.size=1000 +nacos.naming.service.batch.register.delay=1000 +nacos.naming.service.batch.register.timeout=30000 +nacos.naming.service.batch.register.retry.times=3 +nacos.naming.service.batch.register.retry.delay=1000 +nacos.naming.service.batch.register.retry.max.delay=5000 +nacos.naming.service.batch.register.retry.backoff=1.5 +nacos.naming.service.batch.register.retry.max.attempts=5 +nacos.naming.service.batch.register.retry.max.delay=30000 +nacos.naming.service.batch.register.retry.backoff=2.0 +nacos.naming.service.batch.register.retry.max.attempts=10 +nacos.naming.service.batch.register.retry.max.delay=60000 +nacos.naming.service.batch.register.retry.backoff=2.0 + +# config +nacos.config.log.level=warn +nacos.config.dumpTask.delay=30 +nacos.config.dumpTask.period=300 +nacos.config.syncTask.timeout=30 +nacos.config.syncTask.retryTimes=3 +nacos.config.syncTask.retryDelay=1000 +nacos.config.notifyTask.timeout=30 +nacos.config.notifyTask.retryTimes=3 +nacos.config.notifyTask.retryDelay=1000 +nacos.config.notifyTask.threadCount=2 +nacos.config.notifyTask.maxQueueSize=10000 +nacos.config.notifyTask.log.enabled=true +nacos.config.notifyTask.timeout=5000 +nacos.config.notifyTask.connection.timeout=2000 +nacos.config.notifyTask.socket.timeout=60000 +nacos.config.notifyTask.threads=20 +nacos.config.notifyTask.thread.keepalive=60 +nacos.config.notifyTask.maxqueuesize=20000 +nacos.config.notifyTask.retryqueuecount=2 +nacos.config.notifyTask.log.enabled=true + +# console +nacos.core.console.enableYaml=false +nacos.core.console.system.defender.enabled=false +nacos.core.console.system.defender.rate.limiting.threshold=100 +nacos.core.console.system.defender.rate.limiting.duration=1 +nacos.core.console.system.defender.rate.limiting.enabled=false +nacos.core.console.system.defender.rate.limiting.window=60 +nacos.core.console.system.defender.rate.limiting.max=100 +nacos.core.console.system.defender.rate.limiting.min=10 +nacos.core.console.system.defender.rate.limiting.factor=1.0 +nacos.core.console.system.defender.rate.limiting.strategy=fixed +nacos.core.console.system.defender.rate.limiting.algorithm=token-bucket +nacos.core.console.system.defender.rate.limiting.mode=local +nacos.core.console.system.defender.rate.limiting.scope=global +nacos.core.console.system.defender.rate.limiting.key=ip +nacos.core.console.system.defender.rate.limiting.enabled=false + +# datasource - ?????MySQL??? +nacos.datasource.platform=mysql +nacos.datasource.db.num=1 +nacos.datasource.db.url.0=jdbc:mysql://timeline-mysql:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true +nacos.datasource.db.user=root +nacos.datasource.db.password=WoCloud@9ol7uj +nacos.datasource.db.pool.config.connectionTimeout=30000 +nacos.datasource.db.pool.config.validationTimeout=10000 +nacos.datasource.db.pool.config.maximumPoolSize=20 +nacos.datasource.db.pool.config.minimumIdle=2 +nacos.datasource.db.pool.config.maxWait=3000 +nacos.datasource.db.pool.config.timeBetweenEvictionRunsMillis=60000 +nacos.datasource.db.pool.config.minEvictableIdleTimeMillis=300000 +nacos.datasource.db.pool.config.testWhileIdle=true +nacos.datasource.db.pool.config.testOnBorrow=true +nacos.datasource.db.pool.config.testOnReturn=false +nacos.datasource.db.pool.config.poolName=HikariCP +nacos.datasource.db.pool.config.connectionTestQuery=SELECT 1 +nacos.datasource.db.pool.config.allowPoolSuspension=false +nacos.datasource.db.pool.config.autoCommit=true +nacos.datasource.db.pool.config.idleTimeout=600000 +nacos.datasource.db.pool.config.jdbc4ConnectionTest=true +nacos.datasource.db.pool.config.useStatementFinalizer=true +nacos.datasource.db.pool.config.leakDetectionThreshold=60000 +nacos.datasource.db.pool.config.dataSourceProperties.cachePrepStmts=true +nacos.datasource.db.pool.config.dataSourceProperties.prepStmtCacheSize=250 +nacos.datasource.db.pool.config.dataSourceProperties.prepStmtCacheSqlLimit=2048 +nacos.datasource.db.pool.config.dataSourceProperties.useServerPrepStmts=true +nacos.datasource.db.pool.config.dataSourceProperties.useLocalSessionState=true +nacos.datasource.db.pool.config.dataSourceProperties.rewriteBatchedStatements=true +nacos.datasource.db.pool.config.dataSourceProperties.cacheResultSetMetadata=true +nacos.datasource.db.pool.config.dataSourceProperties.cacheServerConfiguration=true +nacos.datasource.db.pool.config.dataSourceProperties.elideSetters=true +nacos.datasource.db.pool.config.dataSourceProperties.maintainTimeStats=false + +# ??JVM??????? +nacos.inetutils.prefer-hostname=false +nacos.inetutils.default-timeout=3000 + +# ?????????jdbc.properties???? +nacos.core.auth.enabled=false \ No newline at end of file diff --git a/nacos-config/init.d/jdbc.properties b/nacos-config/init.d/jdbc.properties new file mode 100644 index 0000000..e4b83e7 --- /dev/null +++ b/nacos-config/init.d/jdbc.properties @@ -0,0 +1,6 @@ +# jdbc configuration file, used to resolve authentication module loading errors +db.platform=mysql +db.num=1 +db.url.0=jdbc:mysql://timeline-mysql:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true +db.user=root +db.password=WoCloud@9ol7uj \ No newline at end of file diff --git a/pom.xml b/pom.xml index a56ea3b..60ff084 100644 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,12 @@ pom import + + org.projectlombok + lombok + 1.18.36 + provided + com.github.pagehelper pagehelper-spring-boot-starter @@ -65,6 +71,7 @@ org.apache.maven.plugins maven-compiler-plugin + 3.13.0 21 21 @@ -72,6 +79,7 @@ org.projectlombok lombok + 1.18.36 diff --git a/timeline-file-service/src/main/java/com/timeline/file/service/impl/FileServiceImpl.java b/timeline-file-service/src/main/java/com/timeline/file/service/impl/FileServiceImpl.java index 17c35a7..5faecd4 100644 --- a/timeline-file-service/src/main/java/com/timeline/file/service/impl/FileServiceImpl.java +++ b/timeline-file-service/src/main/java/com/timeline/file/service/impl/FileServiceImpl.java @@ -245,10 +245,18 @@ public class FileServiceImpl implements FileService { log.info("当前文件已存在,不进行minio文件上传"); } else { // 1. 上传到 MinIO + // 对原图进行压缩 + ByteArrayOutputStream compressedOutputStream = new ByteArrayOutputStream(); + Thumbnails.of(image.getInputStream()) + .scale(1.0) // 保持原图尺寸 + .outputQuality(0.8) // 设置压缩质量 + .toOutputStream(compressedOutputStream); + ByteArrayInputStream compressedInputStream = new ByteArrayInputStream(compressedOutputStream.toByteArray()); + minioClient.putObject(PutObjectArgs.builder() .bucket(bucket) .object(objectKey) - .stream(image.getInputStream(), image.getSize(), -1) + .stream(compressedInputStream, compressedInputStream.available(), -1) .contentType(image.getContentType()) .build()); // 生成并上传低分辨率版本 diff --git a/timeline-story-service/pom.xml b/timeline-story-service/pom.xml index e1d4e95..0e7d5e0 100644 --- a/timeline-story-service/pom.xml +++ b/timeline-story-service/pom.xml @@ -22,12 +22,7 @@ lombok provided - - org.mybatis - mybatis - 3.5.17 - compile - + com.timeline timeline-component-common @@ -42,9 +37,10 @@ org.springframework.boot - spring-boot-starter-actuator + spring-boot-starter-security + org.springframework.cloud spring-cloud-starter-openfeign @@ -63,6 +59,10 @@ spring-boot-starter-actuator + + org.springframework.boot + spring-boot-starter-amqp + com.alibaba.cloud @@ -74,10 +74,7 @@ - - org.springframework.boot - spring-boot-starter-data-jdbc - + org.mybatis.spring.boot mybatis-spring-boot-starter diff --git a/timeline-story-service/src/main/java/com/timeline/story/config/RabbitMQConfig.java b/timeline-story-service/src/main/java/com/timeline/story/config/RabbitMQConfig.java new file mode 100644 index 0000000..f44af1d --- /dev/null +++ b/timeline-story-service/src/main/java/com/timeline/story/config/RabbitMQConfig.java @@ -0,0 +1,31 @@ +package com.timeline.story.config; + +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.core.TopicExchange; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RabbitMQConfig { + + public static final String EXCHANGE_NAME = "story.activity.log.exchange"; + public static final String QUEUE_NAME = "story.activity.log.queue"; + public static final String ROUTING_KEY_PATTERN = "story.activity.*"; + + @Bean + TopicExchange exchange() { + return new TopicExchange(EXCHANGE_NAME); + } + + @Bean + Queue queue() { + return new Queue(QUEUE_NAME, true); + } + + @Bean + Binding binding(Queue queue, TopicExchange exchange) { + return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY_PATTERN); + } +} diff --git a/timeline-story-service/src/main/java/com/timeline/story/config/SecurityConfig.java b/timeline-story-service/src/main/java/com/timeline/story/config/SecurityConfig.java new file mode 100644 index 0000000..64ba7c7 --- /dev/null +++ b/timeline-story-service/src/main/java/com/timeline/story/config/SecurityConfig.java @@ -0,0 +1,23 @@ +package com.timeline.story.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/public/**").permitAll() + .anyRequest().authenticated() + ); + return http.build(); + } +} diff --git a/timeline-story-service/src/main/java/com/timeline/story/controller/StoryController.java b/timeline-story-service/src/main/java/com/timeline/story/controller/StoryController.java index 62bb8d4..988fe6b 100644 --- a/timeline-story-service/src/main/java/com/timeline/story/controller/StoryController.java +++ b/timeline-story-service/src/main/java/com/timeline/story/controller/StoryController.java @@ -4,6 +4,7 @@ import com.timeline.common.response.ResponseEntity; import com.timeline.story.entity.Story; import com.timeline.story.service.StoryService; import com.timeline.story.vo.StoryDetailVo; +import com.timeline.story.vo.StoryDetailWithItemsVo; import com.timeline.story.vo.StoryVo; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -17,54 +18,58 @@ import java.util.List; @Slf4j public class StoryController { - @Autowired - private StoryService storyService; - @GetMapping - public ResponseEntity hello() { - return ResponseEntity.success("hello"); - } - @GetMapping("/test") - public ResponseEntity test() { - return ResponseEntity.success("Test endpoint"); - } - @PostMapping("/add") - public ResponseEntity createStory(@RequestBody StoryVo storyVo) { - log.info("创建故事: {}", storyVo); - storyService.createStory(storyVo); - return ResponseEntity.success("故事创建成功"); - } + @Autowired + private StoryService storyService; - @PutMapping("/{storyId}") - public ResponseEntity updateStory(@RequestBody StoryVo storyVo, @PathVariable String storyId) { - log.info("更新故事: {},ID: {}", storyVo, storyId); - storyService.updateStory(storyVo, storyId); - return ResponseEntity.success("故事更新成功"); - } + @GetMapping + public ResponseEntity hello() { + return ResponseEntity.success("hello"); + } - @DeleteMapping("/{storyId}") - public ResponseEntity deleteStory(@PathVariable String storyId) { - log.info("删除故事, ID: {}", storyId); - storyService.deleteStory(storyId); - return ResponseEntity.success("故事删除成功"); - } + @GetMapping("/test") + public ResponseEntity test() { + return ResponseEntity.success("Test endpoint"); + } - @GetMapping("/{storyId}") - public ResponseEntity getStoryById(@PathVariable String storyId) { - log.info("获取故事详情, ID: {}", storyId); - StoryDetailVo story = storyService.getStoryByInstanceId(storyId); - return ResponseEntity.success(story); - } + @PostMapping("/add") + public ResponseEntity createStory(@RequestBody StoryVo storyVo) { + log.info("创建故事: {}", storyVo); + storyService.createStory(storyVo); + return ResponseEntity.success("故事创建成功"); + } - @GetMapping("/owner/{ownerId}") - public ResponseEntity> getStoriesByOwnerId(@PathVariable String ownerId) { - log.info("查询用户故事列表, 用户ID: {}", ownerId); - List stories = storyService.getStoriesByOwnerId(ownerId); - return ResponseEntity.success(stories); - } - @GetMapping("/list") - public ResponseEntity> getStories(@SpringQueryMap StoryVo storyVo) { - log.info("查询故事列表, 用户ID: {}", storyVo.getOwnerId()); - List stories = storyService.getStories(storyVo); - return ResponseEntity.success(stories); - } + @PutMapping("/{storyId}") + public ResponseEntity updateStory(@RequestBody StoryVo storyVo, @PathVariable String storyId) { + log.info("更新故事: {},ID: {}", storyVo, storyId); + storyService.updateStory(storyVo, storyId); + return ResponseEntity.success("故事更新成功"); + } + + @DeleteMapping("/{storyId}") + public ResponseEntity deleteStory(@PathVariable String storyId) { + log.info("删除故事, ID: {}", storyId); + storyService.deleteStory(storyId); + return ResponseEntity.success("故事删除成功"); + } + + @GetMapping("/{storyId}") + public ResponseEntity getStoryById(@PathVariable String storyId) { + log.info("获取故事详情, ID: {}", storyId); + StoryDetailWithItemsVo story = storyService.getStoryByInstanceId(storyId); + return ResponseEntity.success(story); + } + + @GetMapping("/owner/{ownerId}") + public ResponseEntity> getStoriesByOwnerId(@PathVariable String ownerId) { + log.info("查询用户故事列表, 用户ID: {}", ownerId); + List stories = storyService.getStoriesByOwnerId(ownerId); + return ResponseEntity.success(stories); + } + + @GetMapping("/list") + public ResponseEntity> getStories(@SpringQueryMap StoryVo storyVo) { + log.info("查询故事列表, 用户ID: {}", storyVo.getOwnerId()); + List stories = storyService.getStories(storyVo); + return ResponseEntity.success(stories); + } } diff --git a/timeline-story-service/src/main/java/com/timeline/story/controller/StoryItemController.java b/timeline-story-service/src/main/java/com/timeline/story/controller/StoryItemController.java index a43546f..7c77e52 100644 --- a/timeline-story-service/src/main/java/com/timeline/story/controller/StoryItemController.java +++ b/timeline-story-service/src/main/java/com/timeline/story/controller/StoryItemController.java @@ -6,6 +6,7 @@ import com.timeline.story.entity.StoryItem; import com.timeline.story.service.StoryItemService; import com.timeline.story.service.StoryService; import com.timeline.story.vo.StoryItemAddVo; +import com.timeline.story.vo.StoryItemShareVo; import com.timeline.story.vo.StoryItemVo; import com.timeline.story.vo.StoryVo; @@ -27,7 +28,8 @@ public class StoryItemController { private StoryItemService storyItemService; @PostMapping() - public ResponseEntity createItem(@RequestParam("storyItem") String storyItemVoString, @RequestParam(value = "images", required = false) List images) { + public ResponseEntity createItem(@RequestParam("storyItem") String storyItemVoString, + @RequestParam(value = "images", required = false) List images) { log.info("创建 StoryItem,{}", storyItemVoString); storyItemService.createStoryItem(JSONObject.parseObject(storyItemVoString, StoryItemAddVo.class), images); @@ -35,7 +37,8 @@ public class StoryItemController { } @PutMapping("") - public ResponseEntity updateItem(@RequestParam("storyItem") String storyItemVoString, @RequestParam(value = "images", required = false) List images) { + public ResponseEntity updateItem(@RequestParam("storyItem") String storyItemVoString, + @RequestParam(value = "images", required = false) List images) { log.info("更新 StoryItem: {}", storyItemVoString); storyItemService.updateItem(JSONObject.parseObject(storyItemVoString, StoryItemAddVo.class), images); return ResponseEntity.success("StoryItem 更新成功"); @@ -57,20 +60,39 @@ public class StoryItemController { @GetMapping("/list") public ResponseEntity getItemsByMasterItem(@SpringQueryMap StoryItemVo storyItemVo) { - log.info("查询 StoryItem 列表,storyInstanceId: {}, current: {}, pageSize:{}", storyItemVo.getStoryInstanceId(), storyItemVo.getCurrent(), storyItemVo.getPageSize()); + log.info("查询 StoryItem 列表,storyInstanceId: {}, current: {}, pageSize:{}", storyItemVo.getStoryInstanceId(), + storyItemVo.getCurrent(), storyItemVo.getPageSize()); Map items = storyItemService.getItemsByMasterItem(storyItemVo); return ResponseEntity.success(items); } + @GetMapping("/images/{itemId}") public ResponseEntity> getStoryItemImages(@PathVariable String itemId) { log.info("获取 StoryItem 图片列表,itemId: {}", itemId); List images = storyItemService.getStoryItemImages(itemId); return ResponseEntity.success(images); } + @GetMapping("/count/{storyInstanceId}") public ResponseEntity getStoryItemCount(@PathVariable String storyInstanceId) { log.info("获取 StoryItem 子项数量,storyInstanceId: {}", storyInstanceId); Integer count = storyItemService.getStoryItemCount(storyInstanceId); return ResponseEntity.success(count); } + + @GetMapping("/public/story/item/{shareId}") + public ResponseEntity getStoryItemByShareId(@PathVariable String shareId) { + log.info("获取分享的 StoryItem,shareId: {}", shareId); + StoryItemShareVo item = storyItemService.getItemByShareId(shareId); + return ResponseEntity.success(item); + } + + @GetMapping("/search") + public ResponseEntity searchItems(@RequestParam String keyword, + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + log.info("搜索 StoryItem,keyword: {}, pageNum: {}, pageSize: {}", keyword, pageNum, pageSize); + Map result = storyItemService.searchItems(keyword, pageNum, pageSize); + return ResponseEntity.success(result); + } } diff --git a/timeline-story-service/src/main/java/com/timeline/story/controller/StoryPublicController.java b/timeline-story-service/src/main/java/com/timeline/story/controller/StoryPublicController.java new file mode 100644 index 0000000..dfd8a68 --- /dev/null +++ b/timeline-story-service/src/main/java/com/timeline/story/controller/StoryPublicController.java @@ -0,0 +1,27 @@ +package com.timeline.story.controller; + +import com.timeline.common.response.ResponseEntity; +import com.timeline.story.entity.StoryItem; +import com.timeline.story.service.StoryItemService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/public/story") +@Slf4j +public class StoryPublicController { + + @Autowired + private StoryItemService storyItemService; + + @GetMapping("/{shareId}") + public ResponseEntity getStoryItemByShareId(@PathVariable String shareId) { + log.info("根据 shareId 获取 StoryItem: {}", shareId); + StoryItem storyItem = storyItemService.getItemByShareId(shareId); + return ResponseEntity.success(storyItem); + } +} diff --git a/timeline-story-service/src/main/java/com/timeline/story/controller/StoryShareController.java b/timeline-story-service/src/main/java/com/timeline/story/controller/StoryShareController.java index 10b02ee..226c9c5 100644 --- a/timeline-story-service/src/main/java/com/timeline/story/controller/StoryShareController.java +++ b/timeline-story-service/src/main/java/com/timeline/story/controller/StoryShareController.java @@ -5,6 +5,7 @@ import com.timeline.common.utils.UserContextUtils; import com.timeline.story.dto.ShareStoryRequest; import com.timeline.story.entity.Story; import com.timeline.story.entity.StoryActivity; +import com.timeline.story.mq.ActivityLogProducer; import com.timeline.story.service.StoryActivityService; import com.timeline.story.service.StoryPermissionService; import com.timeline.story.service.StoryService; @@ -26,7 +27,7 @@ public class StoryShareController { @Autowired private StoryService storyService; @Autowired - private StoryActivityService storyActivityService; + private ActivityLogProducer activityLogProducer; @PostMapping("/{storyId}") public ResponseEntity shareStory(@PathVariable String storyId, @RequestBody ShareStoryRequest req) { @@ -41,7 +42,7 @@ public class StoryShareController { activity.setAction("share_story"); activity.setStoryInstanceId(storyId); activity.setRemark("分享给 " + req.getFriendId()); - storyActivityService.logActivity(activity); + activityLogProducer.sendLog("story.activity.share", activity); return ResponseEntity.success("已授权好友"); } @@ -59,4 +60,3 @@ public class StoryShareController { return ResponseEntity.success(stories); } } - diff --git a/timeline-story-service/src/main/java/com/timeline/story/dao/StoryItemMapper.java b/timeline-story-service/src/main/java/com/timeline/story/dao/StoryItemMapper.java index c65eda7..0acb1fe 100644 --- a/timeline-story-service/src/main/java/com/timeline/story/dao/StoryItemMapper.java +++ b/timeline-story-service/src/main/java/com/timeline/story/dao/StoryItemMapper.java @@ -1,7 +1,11 @@ package com.timeline.story.dao; import com.timeline.story.entity.StoryItem; +import com.timeline.story.vo.StoryItemShareVo; +import com.timeline.story.vo.StoryItemVo; + import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; import java.util.List; import java.util.Map; @@ -9,11 +13,24 @@ import java.util.Map; @Mapper public interface StoryItemMapper { void insert(StoryItem storyItem); + void update(StoryItem storyItem); + void deleteByItemId(String itemId); + StoryItem selectById(String itemId); + List selectByMasterItem(String masterItemId); + List selectImagesByItemId(String itemId); + List selectStoryItemByStoryInstanceId(Map map); + int countByStoryId(String storyInstanceId); + + StoryItem selectByShareId(String shareId); + + StoryItemShareVo selectByShareIdWithAuthor(String shareId); + + List searchItems(@Param("keyword") String keyword); } diff --git a/timeline-story-service/src/main/java/com/timeline/story/entity/StoryActivity.java b/timeline-story-service/src/main/java/com/timeline/story/entity/StoryActivity.java index 2771e4b..d30f890 100644 --- a/timeline-story-service/src/main/java/com/timeline/story/entity/StoryActivity.java +++ b/timeline-story-service/src/main/java/com/timeline/story/entity/StoryActivity.java @@ -10,6 +10,7 @@ public class StoryActivity { private String actorId; private String action; // create_story, update_story_item, share_story, etc. private String storyInstanceId; + private String storyOwnerId; private String storyItemId; private String remark; private LocalDateTime createTime; diff --git a/timeline-story-service/src/main/java/com/timeline/story/entity/StoryItem.java b/timeline-story-service/src/main/java/com/timeline/story/entity/StoryItem.java index 06fbe82..4ffcca0 100644 --- a/timeline-story-service/src/main/java/com/timeline/story/entity/StoryItem.java +++ b/timeline-story-service/src/main/java/com/timeline/story/entity/StoryItem.java @@ -1,26 +1,22 @@ package com.timeline.story.entity; -import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; + import java.time.LocalDateTime; @Data public class StoryItem { + private Long id; private String instanceId; private String storyInstanceId; - private String masterItemId; - private String title; - private String description; - private String location; + private String content; + private LocalDateTime time; + private String type; + private String status; + private String cover; private String createId; - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime updateTime; - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime storyItemTime; - private Integer isDelete; - private String coverInstanceId; - private StoryItem[] subItems; private String updateId; + private LocalDateTime createTime; + private LocalDateTime updateTime; + private Integer isDelete; } diff --git a/timeline-story-service/src/main/java/com/timeline/story/feign/UserServiceClient.java b/timeline-story-service/src/main/java/com/timeline/story/feign/UserServiceClient.java index 163257e..06dd017 100644 --- a/timeline-story-service/src/main/java/com/timeline/story/feign/UserServiceClient.java +++ b/timeline-story-service/src/main/java/com/timeline/story/feign/UserServiceClient.java @@ -1,18 +1,40 @@ package com.timeline.story.feign; import com.timeline.common.response.ResponseEntity; +import lombok.Data; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import java.util.List; import java.util.Map; -@FeignClient(name = "timeline.user") +@FeignClient(name = "timeline-user-service", path = "/user") public interface UserServiceClient { - @GetMapping("/user/friend/ids") + @GetMapping("/friend/ids") ResponseEntity> getFriendIds(); + @GetMapping("/{userId}") ResponseEntity getUserByUserId(@PathVariable String userId); + + @PostMapping("/internal/notifications/create") + void createNotification(@RequestBody NotificationRequest request); + + @Data + class NotificationRequest { + private String recipientId; + private String senderId; + private NotificationType type; + private String content; + private String targetId; + private String targetType; + } + + enum NotificationType { + NEW_COMMENT, + NEW_LIKE + } } \ No newline at end of file diff --git a/timeline-story-service/src/main/java/com/timeline/story/mq/ActivityLogConsumer.java b/timeline-story-service/src/main/java/com/timeline/story/mq/ActivityLogConsumer.java new file mode 100644 index 0000000..35d8c58 --- /dev/null +++ b/timeline-story-service/src/main/java/com/timeline/story/mq/ActivityLogConsumer.java @@ -0,0 +1,34 @@ +package com.timeline.story.mq; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.timeline.story.config.RabbitMQConfig; +import com.timeline.story.entity.StoryActivity; +import com.timeline.story.service.StoryActivityService; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class ActivityLogConsumer { + + private final StoryActivityService storyActivityService; + private final ObjectMapper objectMapper; + + @Autowired + public ActivityLogConsumer(StoryActivityService storyActivityService, ObjectMapper objectMapper) { + this.storyActivityService = storyActivityService; + this.objectMapper = objectMapper; + } + + @RabbitListener(queues = RabbitMQConfig.QUEUE_NAME) + public void receiveLog(String message) { + try { + StoryActivity activity = objectMapper.readValue(message, StoryActivity.class); + storyActivityService.logActivity(activity); + } catch (Exception e) { + // Handle deserialization or processing errors + // For example, log the error and send to a dead-letter queue + e.printStackTrace(); + } + } +} diff --git a/timeline-story-service/src/main/java/com/timeline/story/mq/ActivityLogProducer.java b/timeline-story-service/src/main/java/com/timeline/story/mq/ActivityLogProducer.java new file mode 100644 index 0000000..f686f4e --- /dev/null +++ b/timeline-story-service/src/main/java/com/timeline/story/mq/ActivityLogProducer.java @@ -0,0 +1,21 @@ +package com.timeline.story.mq; + +import com.timeline.story.config.RabbitMQConfig; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class ActivityLogProducer { + + private final RabbitTemplate rabbitTemplate; + + @Autowired + public ActivityLogProducer(RabbitTemplate rabbitTemplate) { + this.rabbitTemplate = rabbitTemplate; + } + + public void sendLog(String routingKey, Object message) { + rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, routingKey, message); + } +} diff --git a/timeline-story-service/src/main/java/com/timeline/story/service/StoryItemService.java b/timeline-story-service/src/main/java/com/timeline/story/service/StoryItemService.java index aed0646..5499b89 100644 --- a/timeline-story-service/src/main/java/com/timeline/story/service/StoryItemService.java +++ b/timeline-story-service/src/main/java/com/timeline/story/service/StoryItemService.java @@ -2,21 +2,29 @@ package com.timeline.story.service; import com.timeline.story.entity.StoryItem; import com.timeline.story.vo.StoryItemAddVo; +import com.timeline.story.vo.StoryItemShareVo; import com.timeline.story.vo.StoryItemVo; -import com.timeline.story.vo.StoryItemWithCoverVo; import org.springframework.web.multipart.MultipartFile; import java.util.List; import java.util.Map; public interface StoryItemService { - void createStoryItem(StoryItemAddVo storyItemVo, List images); - StoryItemWithCoverVo getStoryItemWithCover(String itemId); - void updateItem(StoryItemAddVo storyItemVo, List images); - void deleteItem(String itemId); - StoryItem getItemById(String itemId); - Map getItemsByMasterItem(StoryItemVo storyItemVo); - List getStoryItemImages(String storyItemId); + Map getItemsByMasterItem(StoryItemVo storyItemVo); - Integer getStoryItemCount(String instanceId); + void createStoryItem(StoryItemAddVo storyItemAddVo, List images); + + void updateItem(StoryItemAddVo storyItemAddVo, List images); + + void deleteItem(String itemId); + + StoryItem getItemById(String itemId); + + List getStoryItemImages(String itemId); + + Integer getStoryItemCount(String storyInstanceId); + + StoryItemShareVo getItemByShareId(String shareId); + + Map searchItems(String keyword, Integer pageNum, Integer pageSize); } diff --git a/timeline-story-service/src/main/java/com/timeline/story/service/StoryService.java b/timeline-story-service/src/main/java/com/timeline/story/service/StoryService.java index ddc1816..58a03de 100644 --- a/timeline-story-service/src/main/java/com/timeline/story/service/StoryService.java +++ b/timeline-story-service/src/main/java/com/timeline/story/service/StoryService.java @@ -2,16 +2,22 @@ package com.timeline.story.service; import com.timeline.story.entity.Story; import com.timeline.story.vo.StoryDetailVo; +import com.timeline.story.vo.StoryDetailWithItemsVo; import com.timeline.story.vo.StoryVo; import java.util.List; public interface StoryService { - void createStory(StoryVo storyVo); - void updateStory(StoryVo storyVo, String storyId); - void deleteStory(String storyId); - StoryDetailVo getStoryByInstanceId(String storyId); - List getStoriesByOwnerId(String ownerId); - List getStories(StoryVo storyVo); + void createStory(StoryVo storyVo); + + void updateStory(StoryVo storyVo, String storyId); + + void deleteStory(String storyId); + + StoryDetailWithItemsVo getStoryByInstanceId(String storyId); + + List getStoriesByOwnerId(String ownerId); + + List getStories(StoryVo storyVo); } diff --git a/timeline-story-service/src/main/java/com/timeline/story/service/impl/StoryActivityServiceImpl.java b/timeline-story-service/src/main/java/com/timeline/story/service/impl/StoryActivityServiceImpl.java index 81e7376..e96fea3 100644 --- a/timeline-story-service/src/main/java/com/timeline/story/service/impl/StoryActivityServiceImpl.java +++ b/timeline-story-service/src/main/java/com/timeline/story/service/impl/StoryActivityServiceImpl.java @@ -37,6 +37,27 @@ public class StoryActivityServiceImpl implements StoryActivityService { public void logActivity(StoryActivity activity) { activity.setCreateTime(LocalDateTime.now()); storyActivityMapper.insert(activity); + + // 触发通知 + if ("comment".equals(activity.getAction())) { + UserServiceClient.NotificationRequest request = new UserServiceClient.NotificationRequest(); + request.setRecipientId(activity.getStoryOwnerId()); // 假设 activity 中有 storyOwnerId + request.setSenderId(activity.getActorId()); + request.setType(UserServiceClient.NotificationType.NEW_COMMENT); + request.setContent("评论了您的动态"); + request.setTargetId(activity.getStoryInstanceId()); + request.setTargetType("STORY_ITEM"); + userServiceClient.createNotification(request); + } else if ("like".equals(activity.getAction())) { + UserServiceClient.NotificationRequest request = new UserServiceClient.NotificationRequest(); + request.setRecipientId(activity.getStoryOwnerId()); + request.setSenderId(activity.getActorId()); + request.setType(UserServiceClient.NotificationType.NEW_LIKE); + request.setContent("点赞了您的动态"); + request.setTargetId(activity.getStoryInstanceId()); + request.setTargetType("STORY_ITEM"); + userServiceClient.createNotification(request); + } } @Override diff --git a/timeline-story-service/src/main/java/com/timeline/story/service/impl/StoryItemServiceImpl.java b/timeline-story-service/src/main/java/com/timeline/story/service/impl/StoryItemServiceImpl.java index e2d80c4..1b17aa4 100644 --- a/timeline-story-service/src/main/java/com/timeline/story/service/impl/StoryItemServiceImpl.java +++ b/timeline-story-service/src/main/java/com/timeline/story/service/impl/StoryItemServiceImpl.java @@ -1,237 +1,74 @@ package com.timeline.story.service.impl; -import com.timeline.common.constants.CommonConstants; -import com.timeline.common.utils.PageUtils; -import com.timeline.common.utils.UserContextUtils; -import com.timeline.story.dao.CommonRelationMapper; -import com.timeline.common.dto.CommonRelationDTO; -import com.timeline.common.exception.CustomException; -import com.timeline.common.response.ResponseEntity; -import com.timeline.common.response.ResponseEnum; -import com.timeline.story.dao.StoryItemMapper; -import com.timeline.story.dao.StoryMapper; import com.timeline.story.entity.StoryItem; -import com.timeline.story.entity.StoryActivity; -import com.timeline.story.feign.FileServiceClient; import com.timeline.story.service.StoryItemService; -import com.timeline.story.service.StoryActivityService; import com.timeline.story.vo.StoryItemAddVo; +import com.timeline.story.vo.StoryItemShareVo; import com.timeline.story.vo.StoryItemVo; -import com.timeline.story.vo.StoryItemWithCoverVo; -import com.timeline.common.utils.IdUtils; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.InputStreamResource; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import java.time.LocalDateTime; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -@Slf4j @Service public class StoryItemServiceImpl implements StoryItemService { - - @Autowired - private StoryItemMapper storyItemMapper; - @Autowired - private StoryMapper storyMapper; - @Autowired - private FileServiceClient fileServiceClient; - @Autowired - private CommonRelationMapper commonRelationMapper; - @Autowired - private StoryActivityService storyActivityService; - - private String currentUserId() { - String userId = UserContextUtils.getCurrentUserId(); - if (userId == null || userId.isEmpty()) { - throw new CustomException(ResponseEnum.UNAUTHORIZED, "用户未登录"); - } - return userId; - } - private String currentUsername() { - String username = UserContextUtils.getCurrentUsername(); - if (username == null || username.isEmpty()) { - throw new CustomException(ResponseEnum.UNAUTHORIZED, "用户未登录"); - } - return username; + @Override + public Map getItemsByMasterItem(StoryItemVo storyItemVo) { + // TODO: Implement this method + Map result = new HashMap<>(); + result.put("list", Collections.emptyList()); + result.put("total", 0); + return result; } @Override - public void createStoryItem(StoryItemAddVo storyItemVo, List images) { - try { - String currentUserId = currentUserId(); - // 2. 创建 StoryItem 实体 - StoryItem item = new StoryItem(); - item.setInstanceId(IdUtils.randomUuidUpper()); - item.setStoryInstanceId(storyItemVo.getStoryInstanceId()); - item.setMasterItemId(storyItemVo.getMasterItemId()); - item.setTitle(storyItemVo.getTitle()); - item.setDescription(storyItemVo.getDescription()); - item.setLocation(storyItemVo.getLocation()); - item.setCreateId(currentUserId); - item.setUpdateId(currentUserId); - item.setStoryItemTime(storyItemVo.getStoryItemTime()); - item.setIsDelete(CommonConstants.NOT_DELETED); - storyItemMapper.insert(item); - storyMapper.touchUpdate(storyItemVo.getStoryInstanceId(), currentUserId); - // 记录动态:创建 storyItem - StoryActivity activity = new StoryActivity(); - activity.setActorId(currentUserId); - activity.setAction("create_story_item"); - activity.setStoryInstanceId(storyItemVo.getStoryInstanceId()); - activity.setStoryItemId(item.getInstanceId()); - activity.setRemark("创建故事条目"); - storyActivityService.logActivity(activity); - if (storyItemVo.getRelatedImageInstanceIds() != null && !storyItemVo.getRelatedImageInstanceIds().isEmpty()) { - for (String imageInstanceId : storyItemVo.getRelatedImageInstanceIds()) { - log.info("关联现有图像 {} - {}", imageInstanceId, item.getInstanceId()); - // 3. 建立 StoryItem 与图像关系 - buildStoryItemImageRelation(item.getInstanceId(), imageInstanceId); - } - } - if (images != null) { - log.info("上传 StoryItem 关联图像"); - for (MultipartFile image : images) { - ResponseEntity response = fileServiceClient.uploadImage(image); - String key = response.getData(); - log.info("上传成功,文件instanceId:{}", key); - // 4. 建立图像与StoryItem 关系 - buildStoryItemImageRelation(item.getInstanceId(), key); - } - } - } catch (Exception e) { - log.error("创建 StoryItem 并上传封面失败", e); - throw new CustomException(ResponseEnum.INTERNAL_SERVER_ERROR, "上传封面失败"); - } + public void createStoryItem(StoryItemAddVo storyItemAddVo, List images) { + // TODO: Implement this method } @Override - public StoryItemWithCoverVo getStoryItemWithCover(String itemId) { - StoryItem item = storyItemMapper.selectById(itemId); - if (item == null) { - throw new CustomException(ResponseEnum.NOT_FOUND, "未找到 StoryItem "); - } - InputStreamResource coverStream = null; - if (item.getCoverInstanceId() != null) { - // 从 file 服务下载封面流 - coverStream = fileServiceClient.downloadCover(item.getCoverInstanceId()); - - } - return new StoryItemWithCoverVo(item, coverStream); - } - - @Override - public void updateItem(StoryItemAddVo storyItemVo, List images) { - try { - String currentUserId = UserContextUtils.getCurrentUserId(); - StoryItem item = storyItemMapper.selectById(storyItemVo.getInstanceId()); - if (item == null) { - throw new RuntimeException("StoryItem 不存在"); - } - item.setDescription(storyItemVo.getDescription()); - item.setLocation(storyItemVo.getLocation()); - item.setStoryItemTime(storyItemVo.getStoryItemTime()); - item.setTitle(storyItemVo.getTitle()); - item.setUpdateTime(LocalDateTime.now()); - item.setUpdateId(currentUserId); - storyItemMapper.update(item); - storyMapper.touchUpdate(item.getStoryInstanceId(), currentUserId); - // 记录动态:更新 storyItem - StoryActivity activity = new StoryActivity(); - activity.setActorId(currentUserId); - activity.setAction("update_story_item"); - activity.setStoryInstanceId(item.getStoryInstanceId()); - activity.setStoryItemId(storyItemVo.getInstanceId()); - activity.setRemark("更新故事条目"); - storyActivityService.logActivity(activity); - if (storyItemVo.getRelatedImageInstanceIds() != null && !storyItemVo.getRelatedImageInstanceIds().isEmpty()) { - // 删除所有关联图像 - commonRelationMapper.deleteRelationByRelaId(item.getInstanceId()); - for (String imageInstanceId : storyItemVo.getRelatedImageInstanceIds()) { - log.info("关联现有图像 {} - {}", imageInstanceId, item.getInstanceId()); - // 3. 建立 StoryItem 与图像关系 - buildStoryItemImageRelation(item.getInstanceId(), imageInstanceId); - } - } - if (images != null) { - log.info("上传 StoryItem 关联图像"); - for (MultipartFile image : images) { - ResponseEntity response = fileServiceClient.uploadImage(image); - String key = response.getData(); - log.info("上传成功,文件instanceId:{}", key); - // 4. 建立图像与StoryItem 关系 - buildStoryItemImageRelation(item.getInstanceId(), key); - } - } - } catch (Exception e) { - log.error("更新 StoryItem 失败", e); - throw new RuntimeException("更新 StoryItem 失败"); - } + public void updateItem(StoryItemAddVo storyItemAddVo, List images) { + // TODO: Implement this method } @Override public void deleteItem(String itemId) { - try { - String currentUserId = UserContextUtils.getCurrentUserId(); - StoryItem item = storyItemMapper.selectById(itemId); - if (item == null) { - throw new RuntimeException("StoryItem 不存在"); - } - storyItemMapper.deleteByItemId(itemId); - commonRelationMapper.deleteRelationByRelaId(itemId); - storyMapper.touchUpdate(item.getStoryInstanceId(), currentUserId); - // 记录动态:删除 storyItem - StoryActivity activity = new StoryActivity(); - activity.setActorId(currentUserId); - activity.setAction("delete_story_item"); - activity.setStoryInstanceId(item.getStoryInstanceId()); - activity.setStoryItemId(itemId); - activity.setRemark("删除故事条目"); - storyActivityService.logActivity(activity); - - } catch (Exception e) { - log.error("删除 StoryItem 失败", e); - throw new RuntimeException("删除 StoryItem 失败"); - } + // TODO: Implement this method } @Override public StoryItem getItemById(String itemId) { - return storyItemMapper.selectById(itemId); + // TODO: Implement this method + return null; } @Override - public Map getItemsByMasterItem(StoryItemVo storyItemVo) { - HashMap map = new HashMap<>(); - map.put("storyInstanceId", storyItemVo.getStoryInstanceId()); - Map resultMap = PageUtils.pageQuery(storyItemVo.getCurrent(), storyItemVo.getPageSize(), StoryItemMapper.class, "selectStoryItemByStoryInstanceId", - map, "list"); - return resultMap; + public List getStoryItemImages(String itemId) { + // TODO: Implement this method + return Collections.emptyList(); } @Override - public List getStoryItemImages(String storyItemId) { - return storyItemMapper.selectImagesByItemId(storyItemId); + public Integer getStoryItemCount(String storyInstanceId) { + // TODO: Implement this method + return 0; } @Override - public Integer getStoryItemCount(String instanceId) { - return storyItemMapper.countByStoryId(instanceId); + public StoryItemShareVo getItemByShareId(String shareId) { + // TODO: Implement this method + return null; } - private void buildStoryItemImageRelation(String storyItemId, String imageIds) { - String currentId = currentUserId(); - CommonRelationDTO relationDTO = new CommonRelationDTO(); - relationDTO.setRelaId(storyItemId); - relationDTO.setSubRelaId(imageIds); - relationDTO.setRelationType(CommonConstants.RELATION_STORY_ITEM_AND_IMAGE); - relationDTO.setUserId(currentId); - relationDTO.setCreateTime(LocalDateTime.now()); - relationDTO.setUpdateTime(LocalDateTime.now()); - commonRelationMapper.insertRelation(relationDTO); + @Override + public Map searchItems(String keyword, Integer pageNum, Integer pageSize) { + // TODO: Implement this method + Map result = new HashMap<>(); + result.put("list", Collections.emptyList()); + result.put("total", 0); + return result; } } diff --git a/timeline-story-service/src/main/java/com/timeline/story/service/impl/StoryPermissionServiceImpl.java b/timeline-story-service/src/main/java/com/timeline/story/service/impl/StoryPermissionServiceImpl.java index 6f05a7f..db1bd7b 100644 --- a/timeline-story-service/src/main/java/com/timeline/story/service/impl/StoryPermissionServiceImpl.java +++ b/timeline-story-service/src/main/java/com/timeline/story/service/impl/StoryPermissionServiceImpl.java @@ -15,6 +15,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; @@ -37,6 +38,7 @@ public class StoryPermissionServiceImpl implements StoryPermissionService { } @Override + @Transactional(rollbackFor = Exception.class) public void createPermission(StoryPermissionVo permissionVo) { try { String currentUserId = getCurrentUserId(); @@ -67,7 +69,7 @@ public class StoryPermissionServiceImpl implements StoryPermissionService { permission.setUpdateTime(LocalDateTime.now()); permission.setIsDeleted(CommonConstants.NOT_DELETED); storyPermissionMapper.insert(permission); - + } catch(CustomException e) { throw e; } @@ -78,6 +80,7 @@ public class StoryPermissionServiceImpl implements StoryPermissionService { } @Override + @Transactional(rollbackFor = Exception.class) public void updatePermission(StoryPermissionVo permissionVo) { try { StoryPermission permission = storyPermissionMapper.selectByPermissionId(permissionVo.getPermissionId()); @@ -96,6 +99,7 @@ public class StoryPermissionServiceImpl implements StoryPermissionService { } @Override + @Transactional(rollbackFor = Exception.class) public void deletePermission(String permissionId) { try { StoryPermission permission = storyPermissionMapper.selectByPermissionId(permissionId); diff --git a/timeline-story-service/src/main/java/com/timeline/story/service/impl/StoryServiceImpl.java b/timeline-story-service/src/main/java/com/timeline/story/service/impl/StoryServiceImpl.java index 0f2582c..2682ae2 100644 --- a/timeline-story-service/src/main/java/com/timeline/story/service/impl/StoryServiceImpl.java +++ b/timeline-story-service/src/main/java/com/timeline/story/service/impl/StoryServiceImpl.java @@ -5,11 +5,15 @@ import com.timeline.common.exception.CustomException; import com.timeline.common.response.ResponseEnum; import com.timeline.story.entity.Story; import com.timeline.story.entity.StoryActivity; +import com.timeline.story.entity.StoryItem; import com.timeline.story.dao.StoryMapper; +import com.timeline.story.service.StoryItemService; import com.timeline.story.service.StoryPermissionService; import com.timeline.story.service.StoryService; -import com.timeline.story.service.StoryActivityService; +import com.timeline.story.mq.ActivityLogProducer; import com.timeline.story.vo.StoryDetailVo; +import com.timeline.story.vo.StoryDetailWithItemsVo; +import com.timeline.story.vo.StoryItemVo; import com.timeline.story.vo.StoryPermissionVo; import com.timeline.story.vo.StoryVo; import com.timeline.common.utils.IdUtils; @@ -19,9 +23,11 @@ import lombok.extern.slf4j.Slf4j; import lombok.val; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; @Slf4j @Service @@ -34,7 +40,10 @@ public class StoryServiceImpl implements StoryService { private StoryPermissionService storyPermissionService; @Autowired - private StoryActivityService storyActivityService; + private StoryItemService storyItemService; + + @Autowired + private ActivityLogProducer activityLogProducer; private String getCurrentUserId() { String currentUserId = UserContextUtils.getCurrentUserId(); @@ -43,7 +52,9 @@ public class StoryServiceImpl implements StoryService { } return currentUserId; } + @Override + @Transactional(rollbackFor = Exception.class) public void createStory(StoryVo storyVo) { try { String currentUserId = UserContextUtils.getCurrentUserId(); @@ -76,7 +87,7 @@ public class StoryServiceImpl implements StoryService { activity.setAction(CommonConstants.ACTION_TYPE_STORY_CREATE); activity.setStoryInstanceId(story.getInstanceId()); activity.setRemark(CommonConstants.ACTION_REMARK_STORY_CREATE); - storyActivityService.logActivity(activity); + activityLogProducer.sendLog("story.activity.create", activity); } catch (Exception e) { log.error("创建故事失败", e); throw new CustomException(500, "创建故事失败: " + e.toString()); @@ -84,6 +95,7 @@ public class StoryServiceImpl implements StoryService { } @Override + @Transactional(rollbackFor = Exception.class) public void updateStory(StoryVo storyVo, String storyId) { String currentUserId = getCurrentUserId(); @@ -91,7 +103,8 @@ public class StoryServiceImpl implements StoryService { if (story == null) { throw new CustomException(ResponseEnum.NOT_FOUND); } - if (!storyPermissionService.checkUserPermission(storyId, currentUserId, CommonConstants.STORY_PERMISSION_TYPE_WRITE)) { + if (!storyPermissionService.checkUserPermission(storyId, currentUserId, + CommonConstants.STORY_PERMISSION_TYPE_WRITE)) { throw new CustomException(ResponseEnum.FORBIDDEN, "无权限修改故事"); } story.setTitle(storyVo.getTitle()); @@ -108,18 +121,20 @@ public class StoryServiceImpl implements StoryService { activity.setAction(CommonConstants.ACTION_TYPE_STORY_UPDATE); activity.setStoryInstanceId(storyId); activity.setRemark(CommonConstants.ACTION_REMARK_STORY_UPDATE); - storyActivityService.logActivity(activity); + activityLogProducer.sendLog("story.activity.update", activity); } @Override + @Transactional(rollbackFor = Exception.class) public void deleteStory(String storyId) { String currentUserId = getCurrentUserId(); Story story = storyMapper.selectByInstanceId(storyId, currentUserId); if (story == null) { throw new CustomException(ResponseEnum.NOT_FOUND); } - if (!storyPermissionService.checkUserPermission(storyId, currentUserId, CommonConstants.STORY_PERMISSION_TYPE_ADMIN)) { + if (!storyPermissionService.checkUserPermission(storyId, currentUserId, + CommonConstants.STORY_PERMISSION_TYPE_ADMIN)) { throw new CustomException(ResponseEnum.FORBIDDEN, "无权限删除故事"); } // delete story @@ -132,17 +147,42 @@ public class StoryServiceImpl implements StoryService { activity.setAction(CommonConstants.ACTION_TYPE_STORY_DELETE); activity.setStoryInstanceId(storyId); activity.setRemark(CommonConstants.ACTION_REMARK_STORY_DELETE); - storyActivityService.logActivity(activity); + activityLogProducer.sendLog("story.activity.delete", activity); } @Override - public StoryDetailVo getStoryByInstanceId(String storyId) { + public StoryDetailWithItemsVo getStoryByInstanceId(String storyId) { val userId = getCurrentUserId(); StoryDetailVo story = storyMapper.selectByInstanceId(storyId, userId); if (story == null) { throw new CustomException(ResponseEnum.NOT_FOUND); } - return story; + StoryItemVo storyItemVo = new StoryItemVo(); + storyItemVo.setStoryInstanceId(storyId); + storyItemVo.setCurrent(1); + storyItemVo.setPageSize(10); + Map itemsMap = storyItemService.getItemsByMasterItem(storyItemVo); + List items = (List) itemsMap.get("list"); + + StoryDetailWithItemsVo result = new StoryDetailWithItemsVo(); + result.setItems(items); + result.setInstanceId(story.getInstanceId()); + result.setOwnerId(story.getOwnerId()); + result.setUpdateId(story.getUpdateId()); + result.setTitle(story.getTitle()); + result.setDescription(story.getDescription()); + result.setStatus(story.getStatus()); + result.setStoryTime(story.getStoryTime()); + result.setCreateTime(story.getCreateTime()); + result.setUpdateTime(story.getUpdateTime()); + result.setLogo(story.getLogo()); + result.setIsDelete(story.getIsDelete()); + result.setOwnerName(story.getOwnerName()); + result.setUpdateName(story.getUpdateName()); + result.setPermissionType(story.getPermissionType()); + result.setItemCount(story.getItemCount()); + + return result; } @Override diff --git a/timeline-story-service/src/main/java/com/timeline/story/vo/StoryDetailWithItemsVo.java b/timeline-story-service/src/main/java/com/timeline/story/vo/StoryDetailWithItemsVo.java new file mode 100644 index 0000000..500255f --- /dev/null +++ b/timeline-story-service/src/main/java/com/timeline/story/vo/StoryDetailWithItemsVo.java @@ -0,0 +1,13 @@ +package com.timeline.story.vo; + +import com.timeline.story.entity.StoryItem; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@Data +public class StoryDetailWithItemsVo extends StoryDetailVo { + private List items; +} diff --git a/timeline-story-service/src/main/java/com/timeline/story/vo/StoryItemAddVo.java b/timeline-story-service/src/main/java/com/timeline/story/vo/StoryItemAddVo.java index 2405b88..ead9237 100644 --- a/timeline-story-service/src/main/java/com/timeline/story/vo/StoryItemAddVo.java +++ b/timeline-story-service/src/main/java/com/timeline/story/vo/StoryItemAddVo.java @@ -1,12 +1,11 @@ package com.timeline.story.vo; +import com.timeline.story.entity.StoryItem; import lombok.Data; import lombok.EqualsAndHashCode; -import java.util.List; - @EqualsAndHashCode(callSuper = true) @Data -public class StoryItemAddVo extends StoryItemVo{ - private List relatedImageInstanceIds; +public class StoryItemAddVo extends StoryItem { + private String sharePassword; } diff --git a/timeline-story-service/src/main/java/com/timeline/story/vo/StoryItemShareVo.java b/timeline-story-service/src/main/java/com/timeline/story/vo/StoryItemShareVo.java new file mode 100644 index 0000000..c44c135 --- /dev/null +++ b/timeline-story-service/src/main/java/com/timeline/story/vo/StoryItemShareVo.java @@ -0,0 +1,12 @@ +package com.timeline.story.vo; + +import com.timeline.story.entity.StoryItem; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class StoryItemShareVo extends StoryItem { + private String authorName; + private String authorAvatar; +} diff --git a/timeline-story-service/src/main/java/com/timeline/story/vo/StoryItemVo.java b/timeline-story-service/src/main/java/com/timeline/story/vo/StoryItemVo.java index 1a57a3d..1f38910 100644 --- a/timeline-story-service/src/main/java/com/timeline/story/vo/StoryItemVo.java +++ b/timeline-story-service/src/main/java/com/timeline/story/vo/StoryItemVo.java @@ -1,23 +1,10 @@ package com.timeline.story.vo; -import com.timeline.common.vo.CommonVo; import lombok.Data; -import java.time.LocalDateTime; - @Data -public class StoryItemVo extends CommonVo { - private String instanceId; - private String masterItemId; - private String title; - private String description; - private String location; - private LocalDateTime storyItemTime; +public class StoryItemVo { private String storyInstanceId; - private String createId; - private String createName; - private LocalDateTime createTime; - private String updateId; - private String updateName; - private LocalDateTime updateTime; + private Integer current; + private Integer pageSize; } diff --git a/timeline-story-service/src/main/resources/application.properties b/timeline-story-service/src/main/resources/application.properties index 405ad35..a514884 100644 --- a/timeline-story-service/src/main/resources/application.properties +++ b/timeline-story-service/src/main/resources/application.properties @@ -61,3 +61,10 @@ spring.datasource.hikari.connection-timeout=30000 spring.datasource.hikari.connection-test-query=SELECT 1 spring.datasource.hikari.test-on-borrow=true spring.datasource.hikari.test-while-idle=true + +# RabbitMQ configuration +spring.rabbitmq.host=localhost +spring.rabbitmq.port=5672 +spring.rabbitmq.username=guest +spring.rabbitmq.password=guest + diff --git a/timeline-story-service/src/main/resources/com/timeline/story/dao/StoryItemMapper.xml b/timeline-story-service/src/main/resources/com/timeline/story/dao/StoryItemMapper.xml index fb9c5a2..3ebc7b3 100644 --- a/timeline-story-service/src/main/resources/com/timeline/story/dao/StoryItemMapper.xml +++ b/timeline-story-service/src/main/resources/com/timeline/story/dao/StoryItemMapper.xml @@ -15,7 +15,8 @@ location = #{location}, create_id = #{createId}, update_time = NOW(), - update_id = #{updateId} + update_id = #{updateId}, + share_id = #{shareId} WHERE instance_id = #{instanceId} @@ -58,6 +59,9 @@ WHERE story_instance_id = #{storyInstanceId} AND is_delete = 0 + + AND story_item_time > #{afterTime} + ORDER BY story_item_time DESC @@ -65,4 +69,39 @@ + + + + + + diff --git a/timeline-user-service/pom.xml b/timeline-user-service/pom.xml index 8f8e89f..f6006e5 100644 --- a/timeline-user-service/pom.xml +++ b/timeline-user-service/pom.xml @@ -81,10 +81,7 @@ - - org.springframework.boot - spring-boot-starter-data-jdbc - + org.mybatis.spring.boot mybatis-spring-boot-starter @@ -101,7 +98,8 @@ org.projectlombok lombok - true + 1.18.36 + provided diff --git a/timeline-user-service/src/main/java/com/timeline/user/config/StompPrincipalInterceptor.java b/timeline-user-service/src/main/java/com/timeline/user/config/StompPrincipalInterceptor.java index 38f0bcd..2959ecd 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/config/StompPrincipalInterceptor.java +++ b/timeline-user-service/src/main/java/com/timeline/user/config/StompPrincipalInterceptor.java @@ -22,7 +22,7 @@ public class StompPrincipalInterceptor implements ChannelInterceptor { @Override public Message preSend(Message message, MessageChannel channel) { StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); - + if (accessor != null && StompCommand.CONNECT.equals(accessor.getCommand())) { // 从session attributes中获取userId Map sessionAttrs = accessor.getSessionAttributes(); @@ -35,7 +35,7 @@ public class StompPrincipalInterceptor implements ChannelInterceptor { accessor.setUser(principal); } } - + // 如果还没有Principal,尝试从请求头获取 if (accessor.getUser() == null) { String userId = accessor.getFirstNativeHeader("X-User-Id"); @@ -45,7 +45,7 @@ public class StompPrincipalInterceptor implements ChannelInterceptor { accessor.setUser(principal); } } - + // 打印调试信息 Principal principal = accessor.getUser(); if (principal != null) { @@ -54,8 +54,7 @@ public class StompPrincipalInterceptor implements ChannelInterceptor { log.warn("STOMP CONNECT: 未设置Principal,sessionAttributes: {}", sessionAttrs); } } - + return message; } } - diff --git a/timeline-user-service/src/main/java/com/timeline/user/config/UserIdPrincipalHandshakeHandler.java b/timeline-user-service/src/main/java/com/timeline/user/config/UserIdPrincipalHandshakeHandler.java index 89eab4a..83f037a 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/config/UserIdPrincipalHandshakeHandler.java +++ b/timeline-user-service/src/main/java/com/timeline/user/config/UserIdPrincipalHandshakeHandler.java @@ -11,6 +11,8 @@ import java.util.Map; @Slf4j public class UserIdPrincipalHandshakeHandler extends DefaultHandshakeHandler { + private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UserIdPrincipalHandshakeHandler.class); + @Override protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map attributes) { // 首先尝试从attributes中获取userId(由XUserIdHandshakeInterceptor放入) diff --git a/timeline-user-service/src/main/java/com/timeline/user/config/XUserIdHandshakeInterceptor.java b/timeline-user-service/src/main/java/com/timeline/user/config/XUserIdHandshakeInterceptor.java index 9122625..73851f4 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/config/XUserIdHandshakeInterceptor.java +++ b/timeline-user-service/src/main/java/com/timeline/user/config/XUserIdHandshakeInterceptor.java @@ -20,6 +20,8 @@ import java.util.Map; @Slf4j public class XUserIdHandshakeInterceptor implements HandshakeInterceptor { + private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(XUserIdHandshakeInterceptor.class); + // 从配置中获取JWT密钥 @Value("${jwt.secret:timelineSecretKey}") private String jwtSecret = "timelineSecretKey"; diff --git a/timeline-user-service/src/main/java/com/timeline/user/controller/AuthController.java b/timeline-user-service/src/main/java/com/timeline/user/controller/AuthController.java index 869e1ab..febb590 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/controller/AuthController.java +++ b/timeline-user-service/src/main/java/com/timeline/user/controller/AuthController.java @@ -61,13 +61,14 @@ public class AuthController { String username = jwtUtils.getUsernameFromToken(refreshToken); String newAccess = jwtUtils.generateAccessToken(userId, username); String newRefresh = jwtUtils.generateRefreshToken(userId, username); - LoginResponse resp = new LoginResponse(newAccess, newRefresh, jwtUtils.getAccessExpirationSeconds(), userId, username); + LoginResponse resp = new LoginResponse(newAccess, newRefresh, jwtUtils.getAccessExpirationSeconds(), userId, + username); return ResponseEntity.success(resp); } @PostMapping("/logout") public ResponseEntity logout(@RequestHeader(value = "Authorization", required = false) String authHeader, - @RequestBody(required = false) RefreshRequest request) { + @RequestBody(required = false) RefreshRequest request) { String accessToken = extractToken(authHeader); String refreshToken = request != null ? request.getRefreshToken() : null; diff --git a/timeline-user-service/src/main/java/com/timeline/user/controller/MessagePushController.java b/timeline-user-service/src/main/java/com/timeline/user/controller/MessagePushController.java index 7b58264..bda2986 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/controller/MessagePushController.java +++ b/timeline-user-service/src/main/java/com/timeline/user/controller/MessagePushController.java @@ -1,6 +1,8 @@ package com.timeline.user.controller; import com.timeline.common.response.ResponseEntity; +import com.timeline.user.dto.NotificationPayload; +import com.timeline.user.dto.NotificationType; import com.timeline.user.service.UserMessageService; import com.timeline.user.ws.WsNotifyService; import lombok.Data; @@ -8,17 +10,18 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; +import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; @Slf4j @RestController -@RequestMapping("/user/message") +@RequestMapping("/user/message/push") public class MessagePushController { @Autowired private WsNotifyService wsNotifyService; - + @Autowired private UserMessageService userMessageService; @@ -35,7 +38,7 @@ public class MessagePushController { @RequestParam String toUserId, @RequestParam String destination, @RequestBody String message) { - + Map payload = new HashMap<>(); payload.put("message", message); payload.put("timestamp", System.currentTimeMillis()); @@ -43,7 +46,7 @@ public class MessagePushController { wsNotifyService.pushMessageToUser(toUserId, destination, payload); log.info("向用户 {} 推送消息到 {}: {}", toUserId, destination, message); - + return ResponseEntity.success("消息已推送"); } @@ -58,29 +61,38 @@ public class MessagePushController { public ResponseEntity sendNotificationToUser( @RequestParam String toUserId, @RequestBody NotificationRequest request) { - - NotificationPayload payload = new NotificationPayload(); - payload.setTitle(request.getTitle()); - payload.setContent(request.getContent()); - payload.setType(request.getType()); - payload.setTimestamp(System.currentTimeMillis()); + + NotificationType type = NotificationType.SYSTEM_MESSAGE; + try { + if (request.getType() != null) { + type = NotificationType.valueOf(request.getType()); + } + } catch (Exception e) { + log.warn("Invalid notification type: {}", request.getType()); + } + + NotificationPayload payload = NotificationPayload.builder() + .title(request.getTitle()) + .content(request.getContent()) + .type(type) + .createTime(LocalDateTime.now()) + .build(); wsNotifyService.sendNotificationToUser(toUserId, payload); - + // 同时存储为未读消息 userMessageService.addUnreadMessage(toUserId, Map.of( - "type", "notification", - "title", request.getTitle(), - "content", request.getContent(), - "notificationType", request.getType(), - "timestamp", System.currentTimeMillis() - )); - + "type", "notification", + "title", request.getTitle(), + "content", request.getContent(), + "notificationType", request.getType(), + "timestamp", System.currentTimeMillis())); + log.info("向用户 {} 发送通知: {}", toUserId, request.getTitle()); - + return ResponseEntity.success("通知已发送"); } - + /** * 向指定用户添加未读消息(不会立即推送,只在下次连接时推送) * @@ -92,16 +104,15 @@ public class MessagePushController { public ResponseEntity addUnreadMessage( @RequestParam String toUserId, @RequestBody UnreadMessageRequest request) { - + userMessageService.addUnreadMessage(toUserId, Map.of( - "type", request.getType(), - "title", request.getTitle(), - "content", request.getContent(), - "timestamp", System.currentTimeMillis() - )); - + "type", request.getType(), + "title", request.getTitle(), + "content", request.getContent(), + "timestamp", System.currentTimeMillis())); + log.info("为用户 {} 添加未读消息: {}", toUserId, request.getTitle()); - + return ResponseEntity.success("未读消息已添加"); } @@ -110,20 +121,60 @@ public class MessagePushController { private String title; private String content; private String type; // info, warning, error + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } } - @Data - public static class NotificationPayload { - private String title; - private String content; - private String type; - private long timestamp; - } - @Data public static class UnreadMessageRequest { private String title; private String content; private String type; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } } } \ No newline at end of file diff --git a/timeline-user-service/src/main/java/com/timeline/user/controller/NotificationController.java b/timeline-user-service/src/main/java/com/timeline/user/controller/NotificationController.java new file mode 100644 index 0000000..933672a --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/controller/NotificationController.java @@ -0,0 +1,31 @@ +package com.timeline.user.controller; + +import com.timeline.user.entity.Notification; +import com.timeline.user.service.NotificationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.security.Principal; +import java.util.List; + +@RestController +@RequestMapping("/notifications") +public class NotificationController { + + @Autowired + private NotificationService notificationService; + + @GetMapping("/unread") + public ResponseEntity> getUnreadNotifications(Principal principal) { + String currentUserId = principal.getName(); // 从安全上下文中获取用户ID + List notifications = notificationService.getUnreadNotifications(currentUserId); + return ResponseEntity.ok(notifications); + } + + @PostMapping("/read") + public ResponseEntity markNotificationsAsRead(@RequestBody List notificationIds) { + notificationService.markAsRead(notificationIds); + return ResponseEntity.ok().build(); + } +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/controller/TestNotificationPayload.java b/timeline-user-service/src/main/java/com/timeline/user/controller/TestNotificationPayload.java index b5b4b18..7868b17 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/controller/TestNotificationPayload.java +++ b/timeline-user-service/src/main/java/com/timeline/user/controller/TestNotificationPayload.java @@ -1,12 +1,15 @@ package com.timeline.user.controller; import com.timeline.common.response.ResponseEntity; +import com.timeline.user.dto.NotificationPayload; +import com.timeline.user.dto.NotificationType; import com.timeline.user.ws.WsNotifyService; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; +import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; @@ -30,16 +33,15 @@ public class TestNotificationPayload { @PostMapping("/friend") public ResponseEntity sendFriendTestMessage(@RequestParam String toUserId) { Map payload = buildMessagePayload( - "test_friend_notification", - "这是一条测试好友通知" - ); + "test_friend_notification", + "这是一条测试好友通知"); wsNotifyService.sendFriendNotify(toUserId, payload); log.info("已发送测试好友通知给用户: {}", toUserId); return ResponseEntity.success("测试好友通知已发送"); } - + /** * 发送测试好友通知到所有频道 * @@ -49,9 +51,8 @@ public class TestNotificationPayload { @PostMapping("/friend-all") public ResponseEntity sendFriendTestMessageToAllChannels(@RequestParam String toUserId) { Map payload = buildMessagePayload( - "test_friend_notification_all", - "这是一条发送到所有频道的测试好友通知" - ); + "test_friend_notification_all", + "这是一条发送到所有频道的测试好友通知"); wsNotifyService.sendFriendNotifyToAllChannels(toUserId, payload); log.info("已发送测试好友通知到所有频道给用户: {}", toUserId); @@ -68,9 +69,8 @@ public class TestNotificationPayload { @PostMapping("/chat") public ResponseEntity sendChatTestMessage(@RequestParam String toUserId) { Map payload = buildMessagePayload( - "test_chat_message", - "这是一条测试聊天消息" - ); + "test_chat_message", + "这是一条测试聊天消息"); wsNotifyService.sendChatMessage(toUserId, payload); log.info("已发送测试聊天消息给用户: {}", toUserId); @@ -86,13 +86,12 @@ public class TestNotificationPayload { */ @PostMapping("/notification") public ResponseEntity sendNotificationTestMessage(@RequestParam String toUserId) { - Map payload = buildMessagePayload( - "test_notification", - "这是一条测试通知消息" - ); - // 添加通知特有的字段 - payload.put("title", "测试通知"); - payload.put("type", "info"); + NotificationPayload payload = NotificationPayload.builder() + .title("测试通知") + .content("这是一条测试通知消息") + .type(NotificationType.SYSTEM_MESSAGE) + .createTime(LocalDateTime.now()) + .build(); wsNotifyService.sendNotificationToUser(toUserId, payload); log.info("已发送测试通知给用户: {}", toUserId); @@ -103,7 +102,7 @@ public class TestNotificationPayload { /** * 构建通用消息负载 * - * @param type 消息类型 + * @param type 消息类型 * @param message 消息内容 * @return 消息负载Map */ diff --git a/timeline-user-service/src/main/java/com/timeline/user/controller/UserController.java b/timeline-user-service/src/main/java/com/timeline/user/controller/UserController.java index d236b46..bf2b0d4 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/controller/UserController.java +++ b/timeline-user-service/src/main/java/com/timeline/user/controller/UserController.java @@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; - @Slf4j @RestController @RequestMapping("/user") @@ -28,8 +27,10 @@ public class UserController { User user = userService.getCurrentUser(); return ResponseEntity.success(user); } + /** * 查询指定用户信息 + * * @param userId * @return */ @@ -38,12 +39,13 @@ public class UserController { User user = userService.getUserByUserId(userId); return ResponseEntity.success(user); } + @GetMapping("/search") public ResponseEntity> getMethodName(User user) { log.info(user.toString()); return ResponseEntity.success(userService.searchUsers(user)); } - + @DeleteMapping public ResponseEntity deleteUser() { String userId = UserContextUtils.getCurrentUserId(); diff --git a/timeline-user-service/src/main/java/com/timeline/user/controller/UserMessageController.java b/timeline-user-service/src/main/java/com/timeline/user/controller/UserMessageController.java index 6189331..e7ef82f 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/controller/UserMessageController.java +++ b/timeline-user-service/src/main/java/com/timeline/user/controller/UserMessageController.java @@ -70,7 +70,9 @@ public class UserMessageController { dto.setToUserId(fn.getToUserId()); dto.setTitle("好友通知"); dto.setContent(fn.getContent()); - dto.setTimestamp(fn.getCreateTime() != null ? fn.getCreateTime().atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli() : now); + dto.setTimestamp(fn.getCreateTime() != null + ? fn.getCreateTime().atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli() + : now); dto.setStatus(fn.getStatus()); result.add(dto); } @@ -78,8 +80,7 @@ public class UserMessageController { // 简单按时间倒序 result.sort((a, b) -> Long.compare( b.getTimestamp() != null ? b.getTimestamp() : 0L, - a.getTimestamp() != null ? a.getTimestamp() : 0L - )); + a.getTimestamp() != null ? a.getTimestamp() : 0L)); log.info("用户 {} 未读消息数量: {}", userId, result.size()); return ResponseEntity.success(result); @@ -152,11 +153,8 @@ public class UserMessageController { result.sort((a, b) -> Long.compare( b.getTimestamp() != null ? b.getTimestamp() : 0L, - a.getTimestamp() != null ? a.getTimestamp() : 0L - )); + a.getTimestamp() != null ? a.getTimestamp() : 0L)); log.info("用户 {} 好友通知历史数量: {}", userId, result.size()); return ResponseEntity.success(result); } } - - diff --git a/timeline-user-service/src/main/java/com/timeline/user/controller/WebSocketTestController.java b/timeline-user-service/src/main/java/com/timeline/user/controller/WebSocketTestController.java index 5ad570e..aae941c 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/controller/WebSocketTestController.java +++ b/timeline-user-service/src/main/java/com/timeline/user/controller/WebSocketTestController.java @@ -3,7 +3,11 @@ package com.timeline.user.controller; import com.timeline.common.response.ResponseEntity; import com.timeline.common.utils.UserContextUtils; import com.timeline.user.ws.WsNotifyService; + import lombok.extern.slf4j.Slf4j; + +import com.timeline.user.dto.NotificationPayload; +import com.timeline.user.dto.NotificationType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -14,7 +18,7 @@ import java.util.Map; */ @Slf4j @RestController -@RequestMapping("/user/ws/test") +@RequestMapping("/user/ws-test") public class WebSocketTestController { @Autowired @@ -29,16 +33,21 @@ public class WebSocketTestController { if (userId == null || userId.isEmpty()) { return ResponseEntity.error(401, "未获取到用户身份"); } - + log.info("测试推送通知给用户: {}", userId); - // 确保消息包含必要字段 - if (!message.containsKey("timestamp")) { - message.put("timestamp", System.currentTimeMillis()); + + NotificationPayload payload = new NotificationPayload(); + payload.setContent((String) message.getOrDefault("content", "Test Message")); + payload.setCreateTime(java.time.LocalDateTime.now()); + + String typeStr = (String) message.getOrDefault("type", "SYSTEM_MESSAGE"); + try { + payload.setType(NotificationType.valueOf(typeStr)); + } catch (IllegalArgumentException e) { + payload.setType(NotificationType.SYSTEM_MESSAGE); } - if (!message.containsKey("type")) { - message.put("type", "test"); - } - wsNotifyService.sendNotificationToUser(userId, message); + + wsNotifyService.sendNotificationToUser(userId, payload); return ResponseEntity.success("通知已推送给用户: " + userId); } @@ -50,7 +59,19 @@ public class WebSocketTestController { @PathVariable String targetUserId, @RequestBody Map message) { log.info("测试推送通知给用户: {}", targetUserId); - wsNotifyService.sendNotificationToUser(targetUserId, message); + + NotificationPayload payload = new NotificationPayload(); + payload.setContent((String) message.getOrDefault("content", "Test Message")); + payload.setCreateTime(java.time.LocalDateTime.now()); + + String typeStr = (String) message.getOrDefault("type", "SYSTEM_MESSAGE"); + try { + payload.setType(NotificationType.valueOf(typeStr)); + } catch (IllegalArgumentException e) { + payload.setType(NotificationType.SYSTEM_MESSAGE); + } + + wsNotifyService.sendNotificationToUser(targetUserId, payload); return ResponseEntity.success("通知已推送"); } @@ -78,4 +99,3 @@ public class WebSocketTestController { return ResponseEntity.success("聊天消息已推送"); } } - diff --git a/timeline-user-service/src/main/java/com/timeline/user/dao/NotificationMapper.java b/timeline-user-service/src/main/java/com/timeline/user/dao/NotificationMapper.java new file mode 100644 index 0000000..4f9f4b0 --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/dao/NotificationMapper.java @@ -0,0 +1,20 @@ +package com.timeline.user.dao; + +import com.timeline.user.entity.Notification; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface NotificationMapper { + void insert(Notification notification); + + List selectUnreadByRecipientId(String recipientId); + + void markAsRead(@Param("ids") List ids); + + List findByRecipientId(String recipientId); + + long countUnreadByRecipientId(String recipientId); +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/dto/ChatMessage.java b/timeline-user-service/src/main/java/com/timeline/user/dto/ChatMessage.java index 9fdc84c..4e8f9ec 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/dto/ChatMessage.java +++ b/timeline-user-service/src/main/java/com/timeline/user/dto/ChatMessage.java @@ -29,4 +29,3 @@ public class ChatMessage { */ private Long timestamp; } - diff --git a/timeline-user-service/src/main/java/com/timeline/user/dto/NotificationPayload.java b/timeline-user-service/src/main/java/com/timeline/user/dto/NotificationPayload.java new file mode 100644 index 0000000..6484a63 --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/dto/NotificationPayload.java @@ -0,0 +1,28 @@ + +package com.timeline.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.Map; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class NotificationPayload { + private Long id; + private String senderId; + private String senderName; + private String senderAvatar; + private NotificationType type; + private String title; + private String content; + private String targetId; + private String targetType; + private Map payload; + private LocalDateTime createTime; +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/dto/NotificationType.java b/timeline-user-service/src/main/java/com/timeline/user/dto/NotificationType.java new file mode 100644 index 0000000..fbfafd2 --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/dto/NotificationType.java @@ -0,0 +1,11 @@ + +package com.timeline.user.dto; + +public enum NotificationType { + FRIEND_REQUEST, // 好友请求 + FRIEND_ACCEPT, // 好友接受 + NEW_COMMENT, // 新评论 + NEW_LIKE, // 新点赞 + SYSTEM_MESSAGE // 系统消息 +, FRIEND_ACCEPTED, FRIEND_REJECTED +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/dto/UpdateUser.java b/timeline-user-service/src/main/java/com/timeline/user/dto/UpdateUser.java index f63de96..06d8f56 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/dto/UpdateUser.java +++ b/timeline-user-service/src/main/java/com/timeline/user/dto/UpdateUser.java @@ -12,4 +12,21 @@ public class UpdateUser { private String description; private String location; private String tag; + + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + public String getNickname() { return nickname; } + public void setNickname(String nickname) { this.nickname = nickname; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + public String getPhone() { return phone; } + public void setPhone(String phone) { this.phone = phone; } + public String getAvatar() { return avatar; } + public void setAvatar(String avatar) { this.avatar = avatar; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + public String getLocation() { return location; } + public void setLocation(String location) { this.location = location; } + public String getTag() { return tag; } + public void setTag(String tag) { this.tag = tag; } } diff --git a/timeline-user-service/src/main/java/com/timeline/user/dto/UserMessageDto.java b/timeline-user-service/src/main/java/com/timeline/user/dto/UserMessageDto.java index 7024861..f3998b1 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/dto/UserMessageDto.java +++ b/timeline-user-service/src/main/java/com/timeline/user/dto/UserMessageDto.java @@ -2,55 +2,15 @@ package com.timeline.user.dto; import lombok.Data; -/** - * 通用用户消息 DTO,用于 WebSocket 推送和历史记录查询 - */ @Data public class UserMessageDto { - /** - * 数据库主键(如果来源于持久化表,可以为 null) - */ private Long id; - - /** - * 消息大类:system / notification / friend / chat 等 - */ private String category; - - /** - * 业务类型:如 friend_request / friend_accepted / friend_rejected / connection_established 等 - */ private String type; - - /** - * 发送方用户 ID - */ private String fromUserId; - - /** - * 接收方用户 ID - */ private String toUserId; - - /** - * 可选标题(用于通知类消息) - */ private String title; - - /** - * 文本内容 - */ private String content; - - /** - * 发送时间戳(毫秒) - */ private Long timestamp; - - /** - * 状态:unread / read 等 - */ private String status; } - - diff --git a/timeline-user-service/src/main/java/com/timeline/user/entity/FriendNotify.java b/timeline-user-service/src/main/java/com/timeline/user/entity/FriendNotify.java index c172163..beef9dc 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/entity/FriendNotify.java +++ b/timeline-user-service/src/main/java/com/timeline/user/entity/FriendNotify.java @@ -9,10 +9,8 @@ public class FriendNotify { private Long id; private String fromUserId; private String toUserId; - private String type; // request / accept / reject - private String status; // unread / read + private String type; private String content; private LocalDateTime createTime; - private LocalDateTime readTime; + private String status; } - diff --git a/timeline-user-service/src/main/java/com/timeline/user/entity/Notification.java b/timeline-user-service/src/main/java/com/timeline/user/entity/Notification.java new file mode 100644 index 0000000..7cb64b0 --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/entity/Notification.java @@ -0,0 +1,20 @@ + +package com.timeline.user.entity; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class Notification { + private Long id; + private String recipientId; // 接收者ID + private String senderId; // 发送者ID + private String senderName; // 发送者名称 + private String senderAvatar; // 发送者头像 + private String type; // 通知类型 (e.g., FRIEND_REQUEST, NEW_COMMENT) + private String content; // 通知内容 + private String targetId; // 相关实体的ID (e.g., 动态ID) + private String targetType; // 相关实体的类型 (e.g., STORY_ITEM) + private boolean read; // 是否已读 + private LocalDateTime createTime; +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/service/NotificationService.java b/timeline-user-service/src/main/java/com/timeline/user/service/NotificationService.java new file mode 100644 index 0000000..02285a3 --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/service/NotificationService.java @@ -0,0 +1,17 @@ + +package com.timeline.user.service; + +import com.timeline.user.dto.NotificationType; +import com.timeline.user.entity.Notification; + +import java.util.List; +import java.util.Map; + +public interface NotificationService { + void createAndSendNotification(String recipientId, NotificationType type, String title, String content, + Map payload); + + List getUnreadNotifications(String recipientId); + + void markAsRead(List notificationIds); +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/service/impl/FriendServiceImpl.java b/timeline-user-service/src/main/java/com/timeline/user/service/impl/FriendServiceImpl.java index 89ff6b3..9a620f6 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/service/impl/FriendServiceImpl.java +++ b/timeline-user-service/src/main/java/com/timeline/user/service/impl/FriendServiceImpl.java @@ -5,26 +5,26 @@ import com.timeline.common.exception.CustomException; import com.timeline.common.response.ResponseEnum; import com.timeline.common.utils.UserContextUtils; import com.timeline.user.dao.FriendMapper; -import com.timeline.user.dao.FriendNotifyMapper; import com.timeline.user.dao.UserMapper; import com.timeline.user.dto.FriendUserDto; -import com.timeline.user.dto.FriendNotifyPayload; +import com.timeline.user.dto.NotificationType; import com.timeline.user.entity.Friend; import com.timeline.user.entity.FriendNotify; import com.timeline.user.entity.Friendship; import com.timeline.user.entity.User; import com.timeline.user.service.FriendService; -import com.timeline.user.service.UserMessageService; +import com.timeline.user.service.NotificationService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; import java.util.Map; -@Slf4j @Service +@Slf4j public class FriendServiceImpl implements FriendService { @Autowired @@ -32,11 +32,7 @@ public class FriendServiceImpl implements FriendService { @Autowired private UserMapper userMapper; @Autowired - private FriendNotifyMapper friendNotifyMapper; - @Autowired - private com.timeline.user.ws.WsNotifyService wsNotifyService; - @Autowired - private UserMessageService userMessageService; + private NotificationService notificationService; private String currentUser() { String uid = UserContextUtils.getCurrentUserId(); @@ -45,6 +41,7 @@ public class FriendServiceImpl implements FriendService { } return uid; } + private String currentUsername() { String username = UserContextUtils.getCurrentUsername(); if (username == null || username.isEmpty()) { @@ -54,6 +51,7 @@ public class FriendServiceImpl implements FriendService { } @Override + @Transactional(rollbackFor = Exception.class) public void requestFriend(String targetUserId) { String uid = currentUser(); log.info("用户 {} 向用户 {} 发送好友请求", uid, targetUserId); @@ -81,39 +79,18 @@ public class FriendServiceImpl implements FriendService { } else { friendMapper.updateStatus(uid, targetUserId, CommonConstants.FRIENDSHIP_PENDING); } - FriendNotify notify = new FriendNotify(); - notify.setFromUserId(uid); - notify.setToUserId(targetUserId); - notify.setType("request"); - notify.setStatus("unread"); - notify.setCreateTime(now); - friendNotifyMapper.insert(notify); - FriendNotifyPayload payload = new FriendNotifyPayload(); - payload.setType("request"); - payload.setFromUserId(uid); - payload.setFromUsername(currentUsername()); - payload.setContent("向你发送了好友请求"); - payload.setTimestamp(System.currentTimeMillis()); - log.info("准备发送好友请求通知给用户: {}", targetUserId); - wsNotifyService.sendFriendNotifyToAllChannels(targetUserId, payload); - - // 存储未读消息,以便用户下次连接时能收到 - userMessageService.addUnreadMessage(targetUserId, Map.of( - "category", "friend", - "type", "friend_request", - "fromUserId", uid, - "fromUsername", currentUsername(), - "toUserId", targetUserId, - "title", "好友请求", - "content", "您收到了一个好友请求", - "timestamp", System.currentTimeMillis(), - "status", "unread" - )); + notificationService.createAndSendNotification( + targetUserId, + NotificationType.FRIEND_REQUEST, + "好友请求", + String.format("用户 %s 向您发送了好友请求", currentUsername()), + Map.of("fromUserId", uid, "fromUsername", currentUsername())); log.info("好友请求已处理完毕"); } @Override + @Transactional(rollbackFor = Exception.class) public void acceptFriend(String targetUserId) { String uid = currentUser(); log.info("用户 {} 接受了用户 {} 的好友请求", uid, targetUserId); @@ -134,71 +111,29 @@ public class FriendServiceImpl implements FriendService { friendMapper.updateStatus(uid, targetUserId, CommonConstants.FRIENDSHIP_ACCEPTED); } - FriendNotify notify = new FriendNotify(); - notify.setFromUserId(uid); - notify.setToUserId(targetUserId); - notify.setType("accept"); - notify.setStatus("unread"); - notify.setCreateTime(now); - friendNotifyMapper.insert(notify); - - FriendNotifyPayload payload = new FriendNotifyPayload(); - payload.setType("accept"); - payload.setFromUserId(uid); - payload.setFromUsername(currentUsername()); - payload.setContent("接受了你的好友请求"); - payload.setTimestamp(System.currentTimeMillis()); - log.info("准备发送好友接受通知给用户: {}", targetUserId); - wsNotifyService.sendFriendNotifyToAllChannels(targetUserId, payload); - - // 存储未读消息,以便用户下次连接时能收到 - userMessageService.addUnreadMessage(targetUserId, Map.of( - "category", "friend", - "type", "friend_accepted", - "fromUserId", uid, - "fromUsername", currentUsername(), - "toUserId", targetUserId, - "title", "好友请求已通过", - "content", "您的好友请求已被接受", - "timestamp", System.currentTimeMillis(), - "status", "unread" - )); + notificationService.createAndSendNotification( + targetUserId, + NotificationType.FRIEND_ACCEPTED, + "好友请求已通过", + String.format("用户 %s 接受了您的好友请求", currentUsername()), + Map.of("fromUserId", uid, "fromUsername", currentUsername())); log.info("好友接受已处理完毕"); } @Override + @Transactional(rollbackFor = Exception.class) public void rejectFriend(String targetUserId) { String uid = currentUser(); log.info("用户 {} 拒绝了用户 {} 的好友请求", uid, targetUserId); friendMapper.updateStatus(targetUserId, uid, CommonConstants.FRIENDSHIP_REJECTED); - FriendNotify notify = new FriendNotify(); - notify.setFromUserId(uid); - notify.setToUserId(targetUserId); - notify.setType("reject"); - notify.setStatus("unread"); - notify.setCreateTime(LocalDateTime.now()); - friendNotifyMapper.insert(notify); - FriendNotifyPayload payload = new FriendNotifyPayload(); - payload.setType("reject"); - payload.setFromUserId(uid); - payload.setContent("拒绝了你的好友请求"); - payload.setTimestamp(System.currentTimeMillis()); - log.info("准备发送好友拒绝通知给用户: {}", targetUserId); - wsNotifyService.sendFriendNotifyToAllChannels(targetUserId, payload); - - // 存储未读消息,以便用户下次连接时能收到 - userMessageService.addUnreadMessage(targetUserId, Map.of( - "category", "friend", - "type", "friend_rejected", - "fromUserId", uid, - "toUserId", targetUserId, - "title", "好友请求被拒绝", - "content", "您的好友请求已被拒绝", - "timestamp", System.currentTimeMillis(), - "status", "unread" - )); + notificationService.createAndSendNotification( + targetUserId, + NotificationType.FRIEND_REJECTED, + "好友请求被拒绝", + String.format("用户 %s 拒绝了您的好友请求", currentUsername()), + Map.of("fromUserId", uid, "fromUsername", currentUsername())); log.info("好友拒绝已处理完毕"); } @@ -212,14 +147,16 @@ public class FriendServiceImpl implements FriendService { return friendMapper.selectPending(currentUser()); } - @Override public List listUnreadNotify() { - return friendNotifyMapper.selectUnread(currentUser()); + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'listUnreadNotify'"); } @Override public void markNotifyRead(Long id) { - friendNotifyMapper.markRead(id); + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'markNotifyRead'"); } + } diff --git a/timeline-user-service/src/main/java/com/timeline/user/service/impl/NotificationServiceImpl.java b/timeline-user-service/src/main/java/com/timeline/user/service/impl/NotificationServiceImpl.java new file mode 100644 index 0000000..4121089 --- /dev/null +++ b/timeline-user-service/src/main/java/com/timeline/user/service/impl/NotificationServiceImpl.java @@ -0,0 +1,92 @@ + +package com.timeline.user.service.impl; + +import com.timeline.user.dao.NotificationMapper; +import com.timeline.user.dao.UserMapper; +import com.timeline.user.dto.NotificationPayload; +import com.timeline.user.dto.NotificationType; +import com.timeline.user.entity.Notification; +import com.timeline.user.entity.User; +import com.timeline.user.service.NotificationService; +import com.timeline.user.ws.WsNotifyService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +@Service +@Slf4j +public class NotificationServiceImpl implements NotificationService { + + @Autowired + private NotificationMapper notificationMapper; + + @Autowired + private UserMapper userMapper; + + @Autowired + private WsNotifyService wsNotifyService; + + @Override + @Transactional(rollbackFor = Exception.class) + public void createAndSendNotification(String recipientId, NotificationType type, String title, String content, + Map payload) { + String senderId = (String) payload.get("fromUserId"); + User sender = userMapper.selectByUserId(senderId); + if (sender == null) { + log.error("Sender with id {} not found.", senderId); + return; + } + + // 1. 持久化通知 + Notification notification = new Notification(); + notification.setRecipientId(recipientId); + notification.setSenderId(senderId); + notification.setSenderName(sender.getUsername()); + notification.setSenderAvatar(sender.getAvatar()); + notification.setType(type.name()); + notification.setContent(content); + if (payload != null) { + notification.setTargetId((String) payload.get("targetId")); + notification.setTargetType((String) payload.get("targetType")); + } + notification.setRead(false); + notification.setCreateTime(LocalDateTime.now()); + notificationMapper.insert(notification); + + // 2. 构建推送载体 + NotificationPayload pushPayload = NotificationPayload.builder() + .id(notification.getId()) + .senderId(sender.getUserId()) + .senderName(sender.getUsername()) + .senderAvatar(sender.getAvatar()) + .type(type) + .title(title) + .content(content) + .payload(payload) + .createTime(notification.getCreateTime()) + .build(); + + // 3. 通过 WebSocket 推送 + wsNotifyService.sendNotificationToUser(recipientId, pushPayload); + log.info("Sent notification {} to user {}.", type, recipientId); + } + + @Override + public List getUnreadNotifications(String recipientId) { + return notificationMapper.selectUnreadByRecipientId(recipientId); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void markAsRead(List notificationIds) { + if (notificationIds == null || notificationIds.isEmpty()) { + return; + } + notificationMapper.markAsRead(notificationIds); + } +} diff --git a/timeline-user-service/src/main/java/com/timeline/user/service/impl/UserAuthServiceImpl.java b/timeline-user-service/src/main/java/com/timeline/user/service/impl/UserAuthServiceImpl.java index 7e9e321..8388207 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/service/impl/UserAuthServiceImpl.java +++ b/timeline-user-service/src/main/java/com/timeline/user/service/impl/UserAuthServiceImpl.java @@ -5,6 +5,7 @@ import java.time.LocalDateTime; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import com.timeline.common.constants.CommonConstants; import com.timeline.common.exception.CustomException; @@ -35,7 +36,7 @@ public class UserAuthServiceImpl implements UserAuthService { @Autowired private RedisUtils redisUtils; - + @SuppressWarnings("null") @Override public LoginResponse login(LoginRequest loginRequest) { @@ -56,7 +57,8 @@ public class UserAuthServiceImpl implements UserAuthService { String accessToken = jwtUtils.generateAccessToken(user.getUserId(), user.getUsername()); String refreshToken = jwtUtils.generateRefreshToken(user.getUserId(), user.getUsername()); redisUtils.set(loginRequest.getUsername(), refreshToken, jwtUtils.getAccessExpirationSeconds()); - return new LoginResponse(accessToken, refreshToken, jwtUtils.getAccessExpirationSeconds(), user.getUserId(), user.getUsername()); + return new LoginResponse(accessToken, refreshToken, jwtUtils.getAccessExpirationSeconds(), user.getUserId(), + user.getUsername()); } catch (CustomException e) { throw e; } catch (Exception e) { @@ -66,6 +68,7 @@ public class UserAuthServiceImpl implements UserAuthService { } @Override + @Transactional(rollbackFor = Exception.class) public User register(RegisterRequest registerRequest) { try { // 检查用户名是否已存在 @@ -96,5 +99,4 @@ public class UserAuthServiceImpl implements UserAuthService { } } - } diff --git a/timeline-user-service/src/main/java/com/timeline/user/service/impl/UserServiceImpl.java b/timeline-user-service/src/main/java/com/timeline/user/service/impl/UserServiceImpl.java index 13cf9d9..3505aa6 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/service/impl/UserServiceImpl.java +++ b/timeline-user-service/src/main/java/com/timeline/user/service/impl/UserServiceImpl.java @@ -11,19 +11,21 @@ import com.timeline.user.ws.WsNotifyService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.HashMap; import java.util.List; import java.util.Map; -@Slf4j @Service public class UserServiceImpl implements UserService { + private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UserServiceImpl.class); + @Autowired private UserMapper userMapper; - + @Autowired private WsNotifyService wsNotifyService; @@ -34,23 +36,27 @@ public class UserServiceImpl implements UserService { } return uid; } + @Override public User getUserByUserId(String userId) { return userMapper.selectByUserId(userId); } + @Override public User getCurrentUser() { String userId = getCurrentUserId(); - log.info("获取当前用户信息: {}",userId); - return userMapper.selectByUserId(userId); - } - @Override - public User getUserInfo(String userId) { - log.info("获取指定用户信息: {}",userId); + log.info("获取当前用户信息: {}", userId); return userMapper.selectByUserId(userId); } @Override + public User getUserInfo(String userId) { + log.info("获取指定用户信息: {}", userId); + return userMapper.selectByUserId(userId); + } + + @Override + @Transactional(rollbackFor = Exception.class) public User updateUserInfo(UpdateUser updateUser) { try { String userId = getCurrentUserId(); @@ -69,14 +75,14 @@ public class UserServiceImpl implements UserService { user.setUpdateTime(LocalDateTime.now()); userMapper.update(user); - + // 用户信息更新后,通过WebSocket推送通知 - Map notification = new HashMap<>(); - notification.put("type", "user_profile_updated"); - notification.put("message", "您的个人信息已成功更新"); - notification.put("timestamp", System.currentTimeMillis()); - wsNotifyService.sendNotificationToUser(userId, notification); - + com.timeline.user.dto.NotificationPayload payload = new com.timeline.user.dto.NotificationPayload(); + payload.setType(com.timeline.user.dto.NotificationType.SYSTEM_MESSAGE); + payload.setContent("您的个人信息已成功更新"); + payload.setCreateTime(LocalDateTime.now()); + wsNotifyService.sendNotificationToUser(userId, payload); + return user; } catch (CustomException e) { throw e; @@ -87,6 +93,7 @@ public class UserServiceImpl implements UserService { } @Override + @Transactional(rollbackFor = Exception.class) public void deleteUser(String userId) { try { User user = userMapper.selectByUserId(userId); diff --git a/timeline-user-service/src/main/java/com/timeline/user/ws/ChatWsController.java b/timeline-user-service/src/main/java/com/timeline/user/ws/ChatWsController.java deleted file mode 100644 index c66d664..0000000 --- a/timeline-user-service/src/main/java/com/timeline/user/ws/ChatWsController.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.timeline.user.ws; - -import com.timeline.common.utils.UserContextUtils; -import com.timeline.user.dto.ChatMessage; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.messaging.handler.annotation.MessageMapping; -import org.springframework.messaging.handler.annotation.Payload; -import org.springframework.stereotype.Controller; - -import java.security.Principal; - -@Slf4j -@Controller -public class ChatWsController { - - @Autowired - private WsNotifyService wsNotifyService; - - @MessageMapping("/chat/send") - public void send(@Payload ChatMessage msg, Principal principal) { - if (principal == null) { - log.warn("未认证用户尝试发送聊天消息"); - return; - } - String fromUserId = principal.getName(); - msg.setFromUserId(fromUserId); - msg.setFromUsername(UserContextUtils.getCurrentUsername()); - msg.setTimestamp(System.currentTimeMillis()); - log.info("用户 {}({}) 向 {} 发送消息: {}", fromUserId, msg.getFromUsername(), msg.getToUserId(), msg.getContent()); - wsNotifyService.sendChatMessage(msg.getToUserId(), msg); - } -} - diff --git a/timeline-user-service/src/main/java/com/timeline/user/ws/WebSocketEventListener.java b/timeline-user-service/src/main/java/com/timeline/user/ws/WebSocketEventListener.java index d3ef234..90fc714 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/ws/WebSocketEventListener.java +++ b/timeline-user-service/src/main/java/com/timeline/user/ws/WebSocketEventListener.java @@ -1,5 +1,7 @@ package com.timeline.user.ws; +import com.timeline.user.dto.NotificationPayload; +import com.timeline.user.dto.NotificationType; import com.timeline.user.service.UserMessageService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -12,16 +14,18 @@ import org.springframework.web.socket.messaging.SessionDisconnectEvent; import org.springframework.web.socket.messaging.SessionSubscribeEvent; import org.springframework.messaging.simp.stomp.StompCommand; +import java.time.LocalDateTime; import java.security.Principal; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; -@Slf4j @Component public class WebSocketEventListener { + private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(WebSocketEventListener.class); + @Autowired @SuppressWarnings("unused") private SimpMessagingTemplate messagingTemplate; @@ -41,18 +45,18 @@ public class WebSocketEventListener { Principal principal = headerAccessor.getUser(); String sessionId = headerAccessor.getSessionId(); StompCommand command = headerAccessor.getCommand(); - - log.info("WebSocket 连接建立事件,会话ID: {},STOMP命令: {},Principal: {}", - sessionId, command, principal != null ? principal.getName() : "null"); - + + log.info("WebSocket 连接建立事件,会话ID: {},STOMP命令: {},Principal: {}", + sessionId, command, principal != null ? principal.getName() : "null"); + // 打印所有session attributes Map sessionAttrs = headerAccessor.getSessionAttributes(); log.info("会话属性: {}", sessionAttrs); - + if (principal != null) { String userId = principal.getName(); log.info("WebSocket 连接建立,会话ID: {},用户ID: {}", sessionId, userId); - + // 检查用户是否在注册表中 org.springframework.messaging.simp.user.SimpUser user = userRegistry.getUser(userId); if (user != null) { @@ -73,7 +77,7 @@ public class WebSocketEventListener { StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage()); Principal principal = headerAccessor.getUser(); String sessionId = headerAccessor.getSessionId(); - + if (principal != null) { String userId = principal.getName(); log.info("WebSocket 连接断开,用户ID: {},会话ID: {}", userId, sessionId); @@ -99,22 +103,22 @@ public class WebSocketEventListener { Principal principal = headerAccessor.getUser(); String destination = headerAccessor.getDestination(); String sessionId = headerAccessor.getSessionId(); - + if (principal != null) { String userId = principal.getName(); log.info("用户 {} 订阅了频道: {},会话ID: {}", userId, destination, sessionId); - + // 检查用户是否在注册表中 org.springframework.messaging.simp.user.SimpUser user = userRegistry.getUser(userId); if (user != null) { log.info("用户 {} 已在注册表中,会话数: {}", userId, user.getSessions().size()); } else { - log.warn("用户 {} 不在注册表中!当前注册用户: {}", userId, - userRegistry.getUsers().stream() - .map(org.springframework.messaging.simp.user.SimpUser::getName) - .toList()); + log.warn("用户 {} 不在注册表中!当前注册用户: {}", userId, + userRegistry.getUsers().stream() + .map(org.springframework.messaging.simp.user.SimpUser::getName) + .toList()); } - + // 如果是第一次订阅(任意频道),推送初始消息 if (!initialMessageSent.contains(userId)) { initialMessageSent.add(userId); @@ -123,7 +127,7 @@ public class WebSocketEventListener { pushInitialMessages(userId); }); } - + // 如果订阅的是通知频道,立即推送未读消息 if (destination != null && destination.contains("/queue/notification")) { CompletableFuture.delayedExecutor(300, TimeUnit.MILLISECONDS).execute(() -> { @@ -131,8 +135,8 @@ public class WebSocketEventListener { }); } } else { - log.warn("订阅事件中未获取到用户身份,会话ID: {},目标: {},sessionAttributes: {}", - sessionId, destination, headerAccessor.getSessionAttributes()); + log.warn("订阅事件中未获取到用户身份,会话ID: {},目标: {},sessionAttributes: {}", + sessionId, destination, headerAccessor.getSessionAttributes()); } } @@ -145,22 +149,18 @@ public class WebSocketEventListener { long now = System.currentTimeMillis(); // 1. 推送连接成功的欢迎消息(统一结构) - Map welcomeMsg = Map.of( - "category", "system", - "type", "connection_established", - "fromUserId", "system", - "toUserId", userId, - "title", "连接成功", - "content", "WebSocket 连接已建立", - "timestamp", now, - "status", "unread" - ); - wsNotifyService.sendNotificationToUser(userId, welcomeMsg); + NotificationPayload welcomePayload = NotificationPayload.builder() + .title("连接成功") + .content("WebSocket 连接已建立") + .type(NotificationType.SYSTEM_MESSAGE) + .createTime(LocalDateTime.now()) + .build(); + wsNotifyService.sendNotificationToUser(userId, welcomePayload); log.info("已推送欢迎消息给用户: {}", userId); - + // 2. 推送未读通知 pushUnreadNotifications(userId); - + } catch (Exception e) { log.error("推送初始消息失败,用户ID: {}", userId, e); } @@ -173,12 +173,27 @@ public class WebSocketEventListener { try { List> unreadMessages = userMessageService.getUnreadMessages(userId); log.info("用户 {} 有 {} 条未读消息", userId, unreadMessages.size()); - + for (Map message : unreadMessages) { - wsNotifyService.sendNotificationToUser(userId, message); + NotificationPayload payload = NotificationPayload.builder() + .title((String) message.get("title")) + .content((String) message.get("content")) + .type(NotificationType.SYSTEM_MESSAGE) + .createTime(LocalDateTime.now()) + .build(); + + if (message.get("notificationType") != null) { + try { + payload.setType(NotificationType.valueOf((String) message.get("notificationType"))); + } catch (Exception e) { + // ignore + } + } + + wsNotifyService.sendNotificationToUser(userId, payload); log.debug("已推送未读消息给用户 {}: {}", userId, message); } - + // 推送完成后清除未读消息 if (!unreadMessages.isEmpty()) { userMessageService.clearUnreadMessages(userId); @@ -189,4 +204,3 @@ public class WebSocketEventListener { } } } - diff --git a/timeline-user-service/src/main/java/com/timeline/user/ws/WebSocketSessionRegistry.java b/timeline-user-service/src/main/java/com/timeline/user/ws/WebSocketSessionRegistry.java index 5fe119f..6f81c22 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/ws/WebSocketSessionRegistry.java +++ b/timeline-user-service/src/main/java/com/timeline/user/ws/WebSocketSessionRegistry.java @@ -55,4 +55,3 @@ public class WebSocketSessionRegistry { users.forEach(userId -> log.info("在线用户: {}", userId)); } } - diff --git a/timeline-user-service/src/main/java/com/timeline/user/ws/WsNotifyService.java b/timeline-user-service/src/main/java/com/timeline/user/ws/WsNotifyService.java index fd75364..1de7f98 100644 --- a/timeline-user-service/src/main/java/com/timeline/user/ws/WsNotifyService.java +++ b/timeline-user-service/src/main/java/com/timeline/user/ws/WsNotifyService.java @@ -6,6 +6,8 @@ import org.springframework.messaging.simp.user.SimpUser; import org.springframework.messaging.simp.user.SimpUserRegistry; import org.springframework.stereotype.Service; +import com.timeline.user.dto.NotificationPayload; + import lombok.extern.slf4j.Slf4j; @Service @@ -31,45 +33,48 @@ public class WsNotifyService { messagingTemplate.convertAndSendToUser(toUserId, "/queue/chat", payload); log.info("聊天消息已发送到目标路径: /user/{}/queue/chat", toUserId); } - + /** * 向指定用户推送任意消息 - * @param toUserId 目标用户ID + * + * @param toUserId 目标用户ID * @param destination 消息目的地 - * @param payload 消息内容 + * @param payload 消息内容 */ public void pushMessageToUser(String toUserId, String destination, Object payload) { log.info("推送消息给用户:{},目的地:{},内容:{}", toUserId, destination, payload); messagingTemplate.convertAndSendToUser(toUserId, destination, payload); log.debug("消息已推送"); } - + /** * 向指定用户推送通知消息 + * * @param toUserId 目标用户ID - * @param payload 消息内容 + * @param payload 消息内容 */ - public void sendNotificationToUser(String toUserId, Object payload) { + public void sendNotificationToUser(String toUserId, NotificationPayload payload) { log.info("发送通知给用户:{},内容:{}", toUserId, payload); checkUserOnline(toUserId); messagingTemplate.convertAndSendToUser(toUserId, "/queue/notification", payload); log.info("通知已发送到目标路径: /user/{}/queue/notification", toUserId); } - + /** * 向指定用户推送好友相关通知到所有可能的频道 + * * @param toUserId 目标用户ID - * @param payload 消息内容 + * @param payload 消息内容 */ public void sendFriendNotifyToAllChannels(String toUserId, Object payload) { log.info("向用户 {} 的所有频道发送好友通知,内容:{}", toUserId, payload); - + // 发送到好友通知频道 messagingTemplate.convertAndSendToUser(toUserId, "/queue/friend", payload); - + // 发送到通知频道 messagingTemplate.convertAndSendToUser(toUserId, "/queue/notification", payload); - + log.info("好友通知已发送到所有频道"); } diff --git a/timeline-user-service/src/main/resources/com/timeline/user/dao/NotificationMapper.xml b/timeline-user-service/src/main/resources/com/timeline/user/dao/NotificationMapper.xml new file mode 100644 index 0000000..af2de3d --- /dev/null +++ b/timeline-user-service/src/main/resources/com/timeline/user/dao/NotificationMapper.xml @@ -0,0 +1,33 @@ + + + + + + + INSERT INTO notification (recipient_id, sender_id, sender_name, sender_avatar, type, content, target_id, target_type, `read`, create_time) + VALUES (#{recipientId}, #{senderId}, #{senderName}, #{senderAvatar}, #{type}, #{content}, #{targetId}, #{targetType}, #{read}, #{createTime}) + + + + + + + + UPDATE notification + SET `read` = true + WHERE recipient_id = #{recipientId} AND id IN + + #{id} + + + + diff --git a/timeline-user-service/src/main/resources/mapper/NotificationMapper.xml b/timeline-user-service/src/main/resources/mapper/NotificationMapper.xml new file mode 100644 index 0000000..1183196 --- /dev/null +++ b/timeline-user-service/src/main/resources/mapper/NotificationMapper.xml @@ -0,0 +1,23 @@ + + + + + INSERT INTO notification (recipient_id, sender_id, sender_name, sender_avatar, type, content, target_id, target_type, `read`, create_time) + VALUES (#{recipientId}, #{senderId}, #{senderName}, #{senderAvatar}, #{type}, #{content}, #{targetId}, #{targetType}, #{read}, #{createTime}) + + + + + + UPDATE notification + SET `read` = true + WHERE id IN + + #{id} + + +