From 8d98c876d257b1eddda0056afb4321c95691fb50 Mon Sep 17 00:00:00 2001 From: Tim <135014430+nagisa77@users.noreply.github.com> Date: Sun, 17 Aug 2025 02:06:47 +0800 Subject: [PATCH] feat: add point mall redemption --- .../openisle/config/PointGoodInitializer.java | 31 ++++++ .../controller/PointMallController.java | 39 +++++++ .../java/com/openisle/dto/PointGoodDto.java | 12 ++ .../com/openisle/dto/PointRedeemRequest.java | 10 ++ .../com/openisle/mapper/PointGoodMapper.java | 18 +++ .../java/com/openisle/model/PointGood.java | 26 +++++ .../repository/PointGoodRepository.java | 8 ++ .../openisle/service/PointMallService.java | 37 +++++++ .../components/MilkTeaActivityComponent.vue | 78 ++----------- frontend_nuxt/components/RedeemPopup.vue | 103 ++++++++++++++++++ frontend_nuxt/pages/points.vue | 74 ++++++++++--- 11 files changed, 352 insertions(+), 84 deletions(-) create mode 100644 backend/src/main/java/com/openisle/config/PointGoodInitializer.java create mode 100644 backend/src/main/java/com/openisle/controller/PointMallController.java create mode 100644 backend/src/main/java/com/openisle/dto/PointGoodDto.java create mode 100644 backend/src/main/java/com/openisle/dto/PointRedeemRequest.java create mode 100644 backend/src/main/java/com/openisle/mapper/PointGoodMapper.java create mode 100644 backend/src/main/java/com/openisle/model/PointGood.java create mode 100644 backend/src/main/java/com/openisle/repository/PointGoodRepository.java create mode 100644 backend/src/main/java/com/openisle/service/PointMallService.java create mode 100644 frontend_nuxt/components/RedeemPopup.vue diff --git a/backend/src/main/java/com/openisle/config/PointGoodInitializer.java b/backend/src/main/java/com/openisle/config/PointGoodInitializer.java new file mode 100644 index 000000000..f7f1e4cff --- /dev/null +++ b/backend/src/main/java/com/openisle/config/PointGoodInitializer.java @@ -0,0 +1,31 @@ +package com.openisle.config; + +import com.openisle.model.PointGood; +import com.openisle.repository.PointGoodRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +/** Initialize default point mall goods. */ +@Component +@RequiredArgsConstructor +public class PointGoodInitializer implements CommandLineRunner { + private final PointGoodRepository pointGoodRepository; + + @Override + public void run(String... args) { + if (pointGoodRepository.count() == 0) { + PointGood g1 = new PointGood(); + g1.setName("GPT Plus 1 个月"); + g1.setCost(20000); + g1.setImage("https://openisle-1307107697.cos.ap-guangzhou.myqcloud.com/assert/icons/chatgpt.png"); + pointGoodRepository.save(g1); + + PointGood g2 = new PointGood(); + g2.setName("奶茶"); + g2.setCost(5000); + g2.setImage("https://openisle-1307107697.cos.ap-guangzhou.myqcloud.com/assert/icons/coffee.png"); + pointGoodRepository.save(g2); + } + } +} diff --git a/backend/src/main/java/com/openisle/controller/PointMallController.java b/backend/src/main/java/com/openisle/controller/PointMallController.java new file mode 100644 index 000000000..eb6066f52 --- /dev/null +++ b/backend/src/main/java/com/openisle/controller/PointMallController.java @@ -0,0 +1,39 @@ +package com.openisle.controller; + +import com.openisle.dto.PointGoodDto; +import com.openisle.dto.PointRedeemRequest; +import com.openisle.mapper.PointGoodMapper; +import com.openisle.model.User; +import com.openisle.service.PointMallService; +import com.openisle.service.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** REST controller for point mall. */ +@RestController +@RequestMapping("/api/point-goods") +@RequiredArgsConstructor +public class PointMallController { + private final PointMallService pointMallService; + private final UserService userService; + private final PointGoodMapper pointGoodMapper; + + @GetMapping + public List list() { + return pointMallService.listGoods().stream() + .map(pointGoodMapper::toDto) + .collect(Collectors.toList()); + } + + @PostMapping("/redeem") + public Map redeem(@RequestBody PointRedeemRequest req, Authentication auth) { + User user = userService.findByIdentifier(auth.getName()).orElseThrow(); + int point = pointMallService.redeem(user, req.getGoodId(), req.getContact()); + return Map.of("point", point); + } +} diff --git a/backend/src/main/java/com/openisle/dto/PointGoodDto.java b/backend/src/main/java/com/openisle/dto/PointGoodDto.java new file mode 100644 index 000000000..cf7384283 --- /dev/null +++ b/backend/src/main/java/com/openisle/dto/PointGoodDto.java @@ -0,0 +1,12 @@ +package com.openisle.dto; + +import lombok.Data; + +/** Point mall good info. */ +@Data +public class PointGoodDto { + private Long id; + private String name; + private int cost; + private String image; +} diff --git a/backend/src/main/java/com/openisle/dto/PointRedeemRequest.java b/backend/src/main/java/com/openisle/dto/PointRedeemRequest.java new file mode 100644 index 000000000..6bbefdba6 --- /dev/null +++ b/backend/src/main/java/com/openisle/dto/PointRedeemRequest.java @@ -0,0 +1,10 @@ +package com.openisle.dto; + +import lombok.Data; + +/** Request to redeem a point mall good. */ +@Data +public class PointRedeemRequest { + private Long goodId; + private String contact; +} diff --git a/backend/src/main/java/com/openisle/mapper/PointGoodMapper.java b/backend/src/main/java/com/openisle/mapper/PointGoodMapper.java new file mode 100644 index 000000000..f0f2f2770 --- /dev/null +++ b/backend/src/main/java/com/openisle/mapper/PointGoodMapper.java @@ -0,0 +1,18 @@ +package com.openisle.mapper; + +import com.openisle.dto.PointGoodDto; +import com.openisle.model.PointGood; +import org.springframework.stereotype.Component; + +/** Mapper for point mall goods. */ +@Component +public class PointGoodMapper { + public PointGoodDto toDto(PointGood good) { + PointGoodDto dto = new PointGoodDto(); + dto.setId(good.getId()); + dto.setName(good.getName()); + dto.setCost(good.getCost()); + dto.setImage(good.getImage()); + return dto; + } +} diff --git a/backend/src/main/java/com/openisle/model/PointGood.java b/backend/src/main/java/com/openisle/model/PointGood.java new file mode 100644 index 000000000..b93d73d14 --- /dev/null +++ b/backend/src/main/java/com/openisle/model/PointGood.java @@ -0,0 +1,26 @@ +package com.openisle.model; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** Item available in the point mall. */ +@Entity +@Getter +@Setter +@NoArgsConstructor +@Table(name = "point_goods") +public class PointGood { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private int cost; + + private String image; +} diff --git a/backend/src/main/java/com/openisle/repository/PointGoodRepository.java b/backend/src/main/java/com/openisle/repository/PointGoodRepository.java new file mode 100644 index 000000000..b76a62476 --- /dev/null +++ b/backend/src/main/java/com/openisle/repository/PointGoodRepository.java @@ -0,0 +1,8 @@ +package com.openisle.repository; + +import com.openisle.model.PointGood; +import org.springframework.data.jpa.repository.JpaRepository; + +/** Repository for point mall goods. */ +public interface PointGoodRepository extends JpaRepository { +} diff --git a/backend/src/main/java/com/openisle/service/PointMallService.java b/backend/src/main/java/com/openisle/service/PointMallService.java new file mode 100644 index 000000000..6ca552953 --- /dev/null +++ b/backend/src/main/java/com/openisle/service/PointMallService.java @@ -0,0 +1,37 @@ +package com.openisle.service; + +import com.openisle.exception.FieldException; +import com.openisle.exception.NotFoundException; +import com.openisle.model.PointGood; +import com.openisle.model.User; +import com.openisle.repository.PointGoodRepository; +import com.openisle.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** Service for point mall operations. */ +@Service +@RequiredArgsConstructor +public class PointMallService { + private final PointGoodRepository pointGoodRepository; + private final UserRepository userRepository; + private final NotificationService notificationService; + + public List listGoods() { + return pointGoodRepository.findAll(); + } + + public int redeem(User user, Long goodId, String contact) { + PointGood good = pointGoodRepository.findById(goodId) + .orElseThrow(() -> new NotFoundException("Good not found")); + if (user.getPoint() < good.getCost()) { + throw new FieldException("point", "Insufficient points"); + } + user.setPoint(user.getPoint() - good.getCost()); + userRepository.save(user); + notificationService.createActivityRedeemNotifications(user, good.getName() + ": " + contact); + return user.getPoint(); + } +} diff --git a/frontend_nuxt/components/MilkTeaActivityComponent.vue b/frontend_nuxt/components/MilkTeaActivityComponent.vue index d72b64b9a..665fb251f 100644 --- a/frontend_nuxt/components/MilkTeaActivityComponent.vue +++ b/frontend_nuxt/components/MilkTeaActivityComponent.vue @@ -40,30 +40,22 @@ 兑换
兑换
- -
- -
-
提交
-
取消
-
-
-
+ + + diff --git a/frontend_nuxt/pages/points.vue b/frontend_nuxt/pages/points.vue index 08bc22f60..36b16673b 100644 --- a/frontend_nuxt/pages/points.vue +++ b/frontend_nuxt/pages/points.vue @@ -23,15 +23,27 @@ {{ good.cost }} 积分 -
兑换
+
兑换
+