后端:公告√

注意数据库新建notice表
This commit is contained in:
2025-09-16 20:03:17 +08:00
parent 158b3e65b6
commit 562ec4abf9
26 changed files with 1468 additions and 35 deletions

View File

@@ -0,0 +1,82 @@
package com.example.demo.notice;
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "notices")
public class Notice {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "shop_id", nullable = false)
private Long shopId;
@Column(name = "user_id", nullable = false)
private Long userId;
@Column(name = "title", nullable = false, length = 120)
private String title;
@Column(name = "content", nullable = false, length = 500)
private String content;
@Column(name = "tag", length = 32)
private String tag;
@Column(name = "is_pinned", nullable = false)
private Boolean pinned = false;
@Column(name = "starts_at")
private LocalDateTime startsAt;
@Column(name = "ends_at")
private LocalDateTime endsAt;
@Convert(converter = NoticeStatusConverter.class)
@Column(name = "status", nullable = false, length = 16)
private NoticeStatus status = NoticeStatus.PUBLISHED;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
LocalDateTime now = LocalDateTime.now();
this.createdAt = now;
this.updatedAt = now;
}
@PreUpdate
protected void onUpdate() {
this.updatedAt = LocalDateTime.now();
}
public Long getId() { return id; }
public Long getShopId() { return shopId; }
public void setShopId(Long shopId) { this.shopId = shopId; }
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
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 getTag() { return tag; }
public void setTag(String tag) { this.tag = tag; }
public Boolean getPinned() { return pinned; }
public void setPinned(Boolean pinned) { this.pinned = pinned; }
public LocalDateTime getStartsAt() { return startsAt; }
public void setStartsAt(LocalDateTime startsAt) { this.startsAt = startsAt; }
public LocalDateTime getEndsAt() { return endsAt; }
public void setEndsAt(LocalDateTime endsAt) { this.endsAt = endsAt; }
public NoticeStatus getStatus() { return status; }
public void setStatus(NoticeStatus status) { this.status = status; }
public LocalDateTime getCreatedAt() { return createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
}

View File

@@ -0,0 +1,31 @@
package com.example.demo.notice;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/notices")
public class NoticeController {
private final NoticeService noticeService;
public NoticeController(NoticeService noticeService) {
this.noticeService = noticeService;
}
/**
* 简化:通过请求头 X-Shop-Id 传递当前店铺ID。
*/
@GetMapping
public ResponseEntity<List<Notice>> list(@RequestHeader(name = "X-Shop-Id", required = false) Long shopId) {
Long sid = (shopId == null ? 1L : shopId);
return ResponseEntity.ok(noticeService.listActive(sid));
}
}

View File

@@ -0,0 +1,17 @@
package com.example.demo.notice;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface NoticeRepository extends JpaRepository<Notice, Long> {
@Query("SELECT n FROM Notice n WHERE n.shopId = :shopId AND n.status = :status " +
"AND (n.startsAt IS NULL OR n.startsAt <= CURRENT_TIMESTAMP) AND (n.endsAt IS NULL OR n.endsAt >= CURRENT_TIMESTAMP) " +
"ORDER BY n.pinned DESC, n.createdAt DESC")
List<Notice> findActiveNotices(@Param("shopId") Long shopId, @Param("status") NoticeStatus status);
}

View File

@@ -0,0 +1,20 @@
package com.example.demo.notice;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class NoticeService {
private final NoticeRepository noticeRepository;
public NoticeService(NoticeRepository noticeRepository) {
this.noticeRepository = noticeRepository;
}
public List<Notice> listActive(Long shopId) {
return noticeRepository.findActiveNotices(shopId, NoticeStatus.PUBLISHED);
}
}

View File

@@ -0,0 +1,12 @@
package com.example.demo.notice;
/**
* 公告状态。
*/
public enum NoticeStatus {
DRAFT,
PUBLISHED,
OFFLINE
}

View File

@@ -0,0 +1,30 @@
package com.example.demo.notice;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
@Converter(autoApply = false)
public class NoticeStatusConverter implements AttributeConverter<NoticeStatus, String> {
@Override
public String convertToDatabaseColumn(NoticeStatus attribute) {
if (attribute == null) return null;
return switch (attribute) {
case DRAFT -> "draft";
case PUBLISHED -> "published";
case OFFLINE -> "offline";
};
}
@Override
public NoticeStatus convertToEntityAttribute(String dbData) {
if (dbData == null) return null;
return switch (dbData) {
case "draft" -> NoticeStatus.DRAFT;
case "published" -> NoticeStatus.PUBLISHED;
case "offline" -> NoticeStatus.OFFLINE;
default -> NoticeStatus.PUBLISHED;
};
}
}

View File

@@ -1 +1,25 @@
spring.application.name=demo
# 数据源配置(通过环境变量注入,避免硬编码)
spring.datasource.url=${SPRING_DATASOURCE_URL}
spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
# JPA 基本配置
spring.jpa.hibernate.ddl-auto=none
spring.jpa.open-in-view=false
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
# CORS 简单放开(如需跨域)
spring.web.cors.allowed-origins=${CORS_ALLOWED_ORIGINS:*}
spring.web.cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS
spring.web.cors.allowed-headers=*
# Hikari 连接池保活(避免云数据库空闲断开)
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=2
spring.datasource.hikari.idle-timeout=300000
spring.datasource.hikari.max-lifetime=600000
spring.datasource.hikari.keepalive-time=300000
spring.datasource.hikari.connection-timeout=30000