基于 Vue + Java 的刷题优惠券项目设计方案

文章目录

基于 Vue + Java 的刷题优惠券项目设计方案

在当今数字化学习与营销相结合的趋势下,刷题优惠券项目能够有效吸引用户参与刷题学习,提升用户的活跃度与参与度。本项目采用前后端分离架构,前端基于 Vue3 + Echarts 构建用户界面,后端运用 redis + mysql + SpringBoot + Redisson 实现业务逻辑与数据存储,重点实现日常券和秒杀券的相关流程。

一、项目概述

刷题优惠券项目旨在为刷题平台用户提供优惠激励,促进用户更多地参与刷题活动。通过发放日常优惠券和定时秒杀优惠券,增加用户的刷题动力和平台的吸引力。用户可在前端界面查看优惠券信息、领取优惠券,并在刷题消费时使用优惠券享受折扣或其他优惠。

二、前端设计与实现

(一)技术选型与环境搭建

  • 前端采用 Vue3 核心框架,借助 Vue CLI 快速搭建项目结构。安装相关依赖,如 Echarts 用于数据可视化展示(若有优惠券相关数据统计展示需求),Axios 用于与后端进行数据交互。

(二)页面布局与组件设计

  • 优惠券展示组件:设计一个组件用于展示各类优惠券信息,包括日常券和秒杀券。以列表形式呈现优惠券的名称、优惠金额或折扣、有效期、领取状态等信息。对于秒杀券,突出显示倒计时信息,以吸引用户关注。
js 复制代码
<template>
  <div class="coupon-list">
    <div v-for="coupon in coupons" :key="coupon.id" class="coupon-item">
      <div class="coupon-header">
        <h3>{{ coupon.name }}</h3>
        <span v-if="coupon.isSeckill">{{ coupon.discount }} 折</span>
        <span v-else>{{ coupon.amount }} 元优惠</span>
      </div>
      <div class="coupon-body">
        <p>有效期:{{ coupon.startDate }} 至 {{ coupon.endDate }}</p>
        <p v-if="coupon.isSeckill">秒杀倒计时:{{ coupon.countdown }}</p>
        <p v-if="coupon.isReceived">已领取</p>
        <button v-else @click="receiveCoupon(coupon)" :disabled="coupon.isDisabled">领取优惠券</button>
      </div>
    </div>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      coupons: [],
    };
  },
  mounted() {
    // 页面加载时获取优惠券列表
    this.getCoupons();
  },
  methods: {
    getCoupons() {
      axios
      .get('/api/coupons')
      .then((response) => {
          this.coupons = response.data;
          // 对于秒杀券,初始化倒计时
          this.initSeckillCountdown();
        })
      .catch((error) => {
          console.error('获取优惠券列表失败', error);
        });
    },
    receiveCoupon(coupon) {
      axios
      .post('/api/receiveCoupon', { couponId: coupon.id })
      .then((response) => {
          if (response.data.success) {
            coupon.isReceived = true;
            coupon.isDisabled = true;
            // 领取成功提示
          } else {
            // 领取失败提示
          }
        })
      .catch((error) => {
          console.error('领取优惠券失败', error);
        });
    },
    initSeckillCountdown() {
      // 遍历秒杀券,设置倒计时更新函数
      this.coupons.forEach((coupon) => {
        if (coupon.isSeckill) {
          const endTime = new Date(coupon.endDate).getTime();
          const interval = setInterval(() => {
            const now = new Date().getTime();
            const timeLeft = endTime - now;
            if (timeLeft <= 0) {
              clearInterval(interval);
              coupon.countdown = '已结束';
            } else {
              const seconds = Math.floor(timeLeft / 1000);
              const minutes = Math.floor(seconds / 60);
              const hours = Math.floor(minutes / 60);
              coupon.countdown = `${hours}:${minutes % 60}:${seconds % 60}`;
            }
          }, 1000);
        }
      });
    },
  },
};
</script>

<style scoped>
.coupon-list {
  width: 400px;
  margin: 0 auto;
}
.coupon-item {
  border: 1px solid #ddd;
  margin-bottom: 10px;
  padding: 10px;
}
.coupon-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.coupon-body {
  margin-top: 10px;
}
</style>
  • 使用优惠券组件:在刷题支付或相关消费场景中,提供一个组件用于用户选择使用优惠券。该组件展示用户已领取的可用优惠券列表,用户可选择一张优惠券进行使用,并显示使用该优惠券后的应付金额等信息。
js 复制代码
<template>
  <div class="use-coupon">
    <h3>选择优惠券</h3>
    <div v-for="coupon in availableCoupons" :key="coupon.id" class="coupon-option">
      <input type="radio" :id="coupon.id" :value="coupon" v-model="selectedCoupon">
      <label :for="coupon.id">{{ coupon.name }} - {{ coupon.amount }} 元优惠</label>
    </div>
    <p>应付金额:{{ totalAmount - (selectedCoupon? selectedCoupon.amount : 0) }}</p>
    <button @click="confirmUseCoupon">确认使用</button>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  props: {
    totalAmount: {
      type: Number,
      required: true,
    },
  },
  data() {
    return {
      availableCoupons: [],
      selectedCoupon: null,
    };
  },
  mounted() {
    // 获取可用优惠券列表
    this.getAvailableCoupons();
  },
  methods: {
    getAvailableCoupons() {
      axios
      .get('/api/availableCoupons')
      .then((response) => {
          this.availableCoupons = response.data;
        })
      .catch((error) => {
          console.error('获取可用优惠券列表失败', error);
        });
    },
    confirmUseCoupon() {
      if (this.selectedCoupon) {
        axios
        .post('/api/useCoupon', { couponId: this.selectedCoupon.id })
        .then((response) => {
            if (response.data.success) {
              // 使用优惠券成功,进行后续支付或业务逻辑处理
            } else {
              // 使用优惠券失败提示
            }
          })
        .catch((error) => {
            console.error('使用优惠券失败', error);
          });
      } else {
        // 未选择优惠券提示
      }
    },
  },
};
</script>

<style scoped>
.use-coupon {
  width: 300px;
  margin: 0 auto;
}
.coupon-option {
  margin-bottom: 10px;
}
</style>

三、后端设计与实现

(一)技术选型与架构设计

  • 后端采用 Spring Boot 框架搭建项目基础,整合 Redis 用于缓存优惠券信息、用户领取记录等数据,提高数据读取速度和系统性能。引入 Redisson 框架,借助其强大的分布式锁等功能来处理秒杀券的并发问题,确保秒杀活动的公平性与稳定性。使用 MySQL 数据库存储用户信息、优惠券信息、领取记录、使用记录等持久化数据。采用分层架构设计,包括表现层(Controller)、业务逻辑层(Service)、数据访问层(DAO)和数据持久层(数据库)。

(二)数据库设计

  • 用户表(user) :
    • id:用户 ID,主键,自增长。
    • username:用户名。
    • password:密码。
sql 复制代码
CREATE TABLE user (
  id INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(50) NOT NULL,
  password VARCHAR(255) NOT NULL
);
  • 优惠券表(coupon) :
    • id:优惠券 ID,主键,自增长。
    • name:优惠券名称。
    • type:优惠券类型(1 - 日常券,2 - 秒杀券)。
    • amount:优惠金额(对于日常券)。
    • discount:折扣(对于秒杀券)。
    • start_date:有效期开始时间。
    • end_date:有效期结束时间。
    • total_quantity:总数量(对于秒杀券)。
    • remaining_quantity:剩余数量。
sql 复制代码
CREATE TABLE coupon (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  type INT NOT NULL,
  amount DECIMAL(10, 2),
  discount DECIMAL(3, 2),
  start_date TIMESTAMP NOT NULL,
  end_date TIMESTAMP NOT NULL,
  total_quantity INT,
  remaining_quantity INT
);
  • 优惠券领取表(coupon_receive) :
    • id:领取记录 ID,主键,自增长。
    • user_id:用户 ID,外键关联 user 表。
    • coupon_id:优惠券 ID,外键关联 coupon 表。
    • receive_time:领取时间。
sql 复制代码
CREATE TABLE coupon_receive (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NOT NULL,
  coupon_id INT NOT NULL,
  receive_time TIMESTAMP NOT NULL,
  FOREIGN KEY (user_id) REFERENCES user(id),
  FOREIGN KEY (coupon_id) REFERENCES coupon(id)
);
  • 优惠券使用表(coupon_use) :
    • id:使用记录 ID,主键,自增长。
    • user_id:用户 ID,外键关联 user 表。
    • coupon_id:优惠券 ID,外键关联 coupon 表。
    • use_time:使用时间。
sql 复制代码
CREATE TABLE coupon_use (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NOT NULL,
  coupon_id INT NOT NULL,
  use_time TIMESTAMP NOT NULL,
  FOREIGN KEY (user_id) REFERENCES user(id),
  FOREIGN KEY (coupon_id) REFERENCES coupon(id)
);

(三)Redis 缓存策略

  • 将优惠券的基本信息缓存到 Redis 中,以提高查询速度。设置合理的缓存过期时间,例如根据优惠券的有效期动态设置,确保缓存数据的时效性。当优惠券信息在数据库中发生更新时,同步更新 Redis 缓存。对于用户领取优惠券的记录,也可以缓存到 Redis 中,方便快速判断用户是否已领取某张优惠券。
java 复制代码
import redis.clients.jedis.Jedis;

public class RedisCacheService {
    private static final String REDIS_KEY_PREFIX = "coupon:";
    private Jedis jedis;

    public RedisCacheService() {
        // 初始化 Jedis 连接
        this.jedis = new Jedis("localhost", 6379);
    }

    public void cacheCoupon(Coupon coupon) {
        // 缓存优惠券信息,键为 coupon:couponId,值为 JSON 序列化的优惠券对象
        String key = REDIS_KEY_PREFIX + coupon.getId();
        // 将优惠券对象转换为 JSON 字符串(这里假设使用了 JSON 序列化工具)
        String value = JSONSerializer.serialize(coupon);
        // 根据优惠券有效期设置缓存过期时间
        long expirationSeconds = (coupon.getEndDate().getTime() - System.currentTimeMillis()) / 1000;
        jedis.setex(key, expirationSeconds, value);
    }

    public Coupon getCachedCoupon(int couponId) {
        // 获取缓存的优惠券信息
        String key = REDIS_KEY_PREFIX + couponId;
        String value = jedis.get(key);
        if (value!= null) {
            // 将 JSON 字符串反序列化为优惠券对象(这里假设使用了 JSON 反序列化工具)
            return JSONDeserializer.deserialize(value, Coupon.class);
        }
        return null;
    }

    public void cacheCouponReceiveRecord(int userId, int couponId) {
        // 缓存用户领取优惠券记录,键为 coupon:receive:userId:couponId,值为 1 表示已领取
        String key = REDIS_KEY_PREFIX + "receive:" + userId + ":" + couponId;
        jedis.set(key, "1");
    }

    public boolean isCouponReceived(int userId, int couponId) {
        // 判断用户是否已领取优惠券
        String key = REDIS_KEY_PREFIX + "receive:" + userId + ":" + couponId;
        return "1".equals(jedis.get(key));
    }

    public void close() {
        // 关闭 Jedis 连接
        jedis.close();
    }
}

(四)Redisson 分布式锁处理秒杀券

  • 在处理秒杀券的领取时,使用 Redisson 的分布式锁来控制并发。当用户请求领取秒杀券时,首先尝试获取对应秒杀券的分布式锁。只有获取到锁的用户才能进行领取操作的后续流程,包括检查剩余数量、更新数据库和缓存等。领取完成后释放锁,确保其他用户能够继续竞争领取。
java 复制代码
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

import java.util.concurrent.TimeUnit;

public class SeckillCouponService {
    private static final String LOCK_PREFIX = "seckill:coupon:";
    private RedissonClient redisson;

    public SeckillCouponService() {
        // 初始化 Redisson 客户端
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        redisson = Redisson.create(config);
    }

    public boolean tryReceiveSeckillCoupon(int userId, int couponId) {
        // 获取秒杀券的分布式锁
        RLock lock = redisson.getLock(LOCK_PREFIX + couponId);
        try {
            // 尝试加锁,设置锁的超时时间为 10 秒
            boolean locked = lock.tryLock(10, TimeUnit.SECONDS);
            if (locked) {
                try {
                    // 检查秒杀券剩余数量
                    Coupon coupon = couponService.getCouponById(couponId);
                    if (coupon.getRemainingQuantity() > 0) {
                        // 领取秒杀券逻辑,更新数据库和缓存
                        couponService.receiveSeckillCoupon(userId, couponId);
                        return true;
                    }
                } finally {
                    // 释放锁
                    lock.unlock();
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return false;
    }

    public void close() {
        // 关闭 Redisson 客户端
        redisson.shutdown();
    }
}

(五)接口设计与实现

  • 获取优惠券列表接口 :
    • 接收前端请求,从 Redis 缓存中获取优惠券列表信息。如果缓存中不存在或已过期,则从数据库中查询优惠券信息,缓存到 Redis 中并返回给前端。
java 复制代码
import java.util.List;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CouponController {
    private final CouponService couponService;

    public CouponController(CouponService couponService) {
        this.couponService = couponService;
    }

    @GetMapping("/api/coupons")
    public List<Coupon> getCoupons() {
        return couponService.getCoupons();
    }
}
  • 领取优惠券接口 :
    • 对于日常券,检查用户是否已领取,若未领取则更新数据库和缓存,记录领取信息。对于秒杀券,调用 Redisson 分布式锁处理的领取方法,根据返回结果告知前端领取成功或失败。
java 复制代码
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CouponReceiveController {
    private final CouponService couponService;
    private final SeckillCouponService seckillCouponService;

    public CouponReceiveController(CouponService couponService, SeckillCouponService seckillCouponService) {
        this.couponService = couponService;
        this.seckillCouponService = seckillCouponService;
    }

    @PostMapping("/api/receiveCoupon")
    public ReceiveCouponResponse receiveCoupon(@RequestBody ReceiveCouponRequest request) {
        int userId = request.getUserId();
        int couponId = request.getCouponId();
        Coupon coupon = couponService.getCouponById(couponId);
        if (coupon.getType() == 1) { // 日常券
            if (!couponService.isCouponReceived(userId, couponId)) {
                couponService.receiveCoupon(userId, couponId);
                return new ReceiveCouponResponse(true);
            } else {
                return new ReceiveCouponResponse(false);
            }
        } else if (coupon.getType() == 2) { // 秒杀券
            boolean success = seckillCouponService.tryReceiveSeckillCoupon(userId,
相关推荐
陈随易2 小时前
Postman 被曝自动往 Claude Code 和 Codex 装插件
前端·后端·程序员
AI你一生一世3 小时前
不止于时序存储:DolphinDB 如何成为 AI 时代的工业智能底座
java·人工智能·struts·时序数据库·工业智能·dolphindb·ai基础设施
云-缩影3 小时前
拆解 AI 图像生成应用的后端架构:ECS、OSS 和大模型 API 怎么配合
java·spring boot·后端
瑞克-桑切斯4 小时前
7、MySQL 慢查询排查与优化实战指南
java·服务器·数据库
Csvn5 小时前
性能监控
前端
Csvn5 小时前
构建工具
前端
FRing036 小时前
vue2干货总结3(computed计算属性、watch监听器)
开发语言·前端·vue.js·vue2
Java小卷6 小时前
JuanForm vs FormCreate!Vue3 表单引擎联动配置 PK!
vue.js·低代码
kyriewen8 小时前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员
海的透彻8 小时前
dockerfile如何快速构建容器(java服务)
java·开发语言