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

news2024/12/27 3:17:59

文章目录

  • 基于 Vue + Java 的刷题优惠券项目设计方案
    • 一、项目概述
    • 二、前端设计与实现
      • (一)技术选型与环境搭建
      • (二)页面布局与组件设计
    • 三、后端设计与实现
      • (一)技术选型与架构设计
      • (二)数据库设计
      • (三)Redis 缓存策略
      • (四)Redisson 分布式锁处理秒杀券
      • (五)接口设计与实现

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

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

一、项目概述

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

二、前端设计与实现

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

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

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

  • 优惠券展示组件:设计一个组件用于展示各类优惠券信息,包括日常券和秒杀券。以列表形式呈现优惠券的名称、优惠金额或折扣、有效期、领取状态等信息。对于秒杀券,突出显示倒计时信息,以吸引用户关注。
<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>
  • 使用优惠券组件:在刷题支付或相关消费场景中,提供一个组件用于用户选择使用优惠券。该组件展示用户已领取的可用优惠券列表,用户可选择一张优惠券进行使用,并显示使用该优惠券后的应付金额等信息。
<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:密码。
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:剩余数量。
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:领取时间。
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:使用时间。
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 中,方便快速判断用户是否已领取某张优惠券。
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 的分布式锁来控制并发。当用户请求领取秒杀券时,首先尝试获取对应秒杀券的分布式锁。只有获取到锁的用户才能进行领取操作的后续流程,包括检查剩余数量、更新数据库和缓存等。领取完成后释放锁,确保其他用户能够继续竞争领取。
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 中并返回给前端。
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 分布式锁处理的领取方法,根据返回结果告知前端领取成功或失败。
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,

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2251743.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mysql系列2—InnoDB数据存储方式

背景 本文将深入探讨InnoDB的底层存储机制&#xff0c;包括行格式、页结构、页目录以及表空间等核心概念。通过全面了解这些基础概念&#xff0c;有助于把握MySQL的存储架构&#xff0c;也为后续深入讨论MySQL的索引原理和查询优化策略奠定了基础。 1.行格式 mysql中数据以行…

vue实现echarts饼图自动轮播

echarts官网&#xff1a;Examples - Apache ECharts echartsFn.ts 把echarts函数封装成一个文件 import * as echarts from "echarts";const seriesData [{"value": 12,"name": "过流报警"},{"value": 102,"name&qu…

【Python数据分析五十个小案例】使用自然语言处理(NLP)技术分析 Twitter 情感

博客主页&#xff1a;小馒头学python 本文专栏: Python爬虫五十个小案例 专栏简介&#xff1a;分享五十个Python爬虫小案例 项目简介 什么是情感分析 情感分析&#xff08;Sentiment Analysis&#xff09;是文本分析的一部分&#xff0c;旨在识别文本中传递的情感信息&…

网络安全防护指南:筑牢网络安全防线(5/10)

一、网络安全的基本概念 &#xff08;一&#xff09;网络的定义 网络是指由计算机或者其他信息终端及相关设备组成的按照一定的规则和程序对信息收集、存储、传输、交换、处理的系统。在当今数字化时代&#xff0c;网络已经成为人们生活和工作中不可或缺的一部分。它连接了世…

宏海科技募资额有所缩减,最大销售和采购都重度依赖美的集团

《港湾商业观察》施子夫 11月29日&#xff0c;北交所上市审核委员会将召开2024年第24次上市委审议会议&#xff0c;届时将审议武汉宏海科技股份有限公司&#xff08;以下简称&#xff0c;宏海科技&#xff09;的首发上会事项。 在上会之前&#xff0c;宏海科技共收到北交所下…

算法日记 36-38day 动态规划

今天把动态规划结束掉&#xff0c;包括子序列以及编辑距离 题目&#xff1a;最长公共子序列 1143. 最长公共子序列 - 力扣&#xff08;LeetCode&#xff09; 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &…

Gopeed 1.6.3 | 不限速下载工具附百度网盘不限速教程

Gopeed是一款高效且易于使用的下载软件。它具有加速下载速度的功能&#xff0c;可以帮助用户更快地下载文件。此外&#xff0c;Gopeed还支持多线程下载&#xff0c;可以同时下载多个文件&#xff0c;提高下载效率。它提供了简洁的界面和简单的操作&#xff0c;方便用户操作和管…

K8S版本和istio版本的对照关系

版本对照关系 下载地址1 下载地址2

【大数据学习 | Spark-SQL】关于RDD、DataFrame、Dataset对象

1. 概念&#xff1a; RDD&#xff1a; 弹性分布式数据集&#xff1b; DataFrame&#xff1a; DataFrame是一种以RDD为基础的分布式数据集&#xff0c;类似于传统数据库中的二维表格。带有schema元信息&#xff0c;即DataFrame所表示的二维表数据集的每一列都带有名称和类型…

如何调用百度文心一言API实现智能问答

诸神缄默不语-个人CSDN博文目录 百度需要先认证个人信息才能使用LLM API。 文章目录 1. 获得 API Key2. 撰写代码并实现提问和回答2.1 用openai包实现调用2.2 用openai包实现流式调用2.3 用openai包实现工具调用2.4 构建智能体2.5 文生图2.6 图生图 3. 用gradio建立大模型问答…

python除了熟悉的pandas,openpyxl库也很方便的支持编辑Excel表

excel表格是大家经常用到的文件格式&#xff0c;各行各业都会跟它打交道。之前文章我们介绍了使用openpyxl和xlrd库读取excel表数据&#xff0c;使用xlwt库创建和编辑excel表&#xff0c;在办公自动化方面可以方便我们快速处理数据&#xff0c;帮助我们提升效率。 python之open…

JMeter 并发策略-针对准点秒杀场景的压测实现

一、场景的压测实现 1&#xff0c;创建线程组&#xff0c;10并发用户执行5次&#xff1b; 2&#xff0c;创建 Synchronizing Timer 元件,用于同步线程&#xff0c;设置同步元件 Synchronizing Timer 3&#xff0c;创建 http 请求4&#xff0c;创建 view results in table 元件…

损失函数分类

1. NLLLoss&#xff08;负对数似然损失&#xff09; 定义&#xff1a; 直接对预测的概率 p(yi) 的负对数求平均。通常配合 Softmax 使用&#xff0c;输入为对数概率。 优点&#xff1a; 对离散分类问题效果良好。更灵活&#xff0c;用户可以自行计算 Softmax。 缺点&#x…

【热门主题】000072 分布式数据库:开启数据管理新纪元

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【热…

算法笔记:力扣148. 排序链表

思路&#xff1a; 将链表中的节点一一取出放到list集合中&#xff0c;然后通过Comparator实现排序&#xff0c;对排序好的list节点一一取出&#xff0c;组成排序好的新链表。 关键思路&#xff1a; Comparator实现对ListNode的排序&#xff1b; &#x1f4a1;注意&#xff…

Kafka知识体系

一、认识Kafka 1. kafka适用场景 消息系统&#xff1a;kafka不仅具备传统的系统解耦、流量削峰、缓冲、异步通信、可扩展性、可恢复性等功能&#xff0c;还有其他消息系统难以实现的消息顺序消费及消息回溯功能。 存储系统&#xff1a;kafka把消息持久化到磁盘上&#xff0c…

MacOS安装MySQL数据库和Java环境以及Navicat

安装MySQL 去官网下载&#xff1a;MySQL 下载好后安装&#xff0c;在设置里往下滑&#xff0c;出现了这样&#xff0c;就代表安装成功了 接下来配置环境&#xff1a; 首先在我们的设备上找到终端并打开,输入 vim ~/.bash_profile(注意vim后面的空格)&#xff0c;输入完成后点击…

Css、less和Sass(SCSS)的区别详解

文章目录 Css、less和Sass&#xff08;SCSS&#xff09;的区别详解一、引言二、CSS 简介1.1、CSS 示例 三、Less 简介2.1、Less 特性2.2、Less 示例 四、Sass&#xff08;SCSS&#xff09;简介3.1、Sass 特性3.2、SCSS 示例 五、总结 Css、less和Sass&#xff08;SCSS&#xff…

misc-好久不见17

wireshark_secret Ctrlshirtv 8、热心助人的小明同学 使用工具volatility查看镜像的信息 volatility_2.6_win64_standalone -f image.raw imageinfo 列出所有用户和密码&#xff1a; volatility_2.6_win64_standalone -f image.raw --profileWin7SP1x86_23418 hashdump Xi…

利用PyTorch Profiler实现大模型的性能分析和故障排查

本文介绍PyTorch Profiler结合TensorBoard分析模型性能&#xff0c;分别从数据加载、数据传输、GPU计算、模型编译等优化思路去提升模型训练的性能。最后总结了一些会导致CPU和GPU同步的常见的PyTorch API&#xff0c;在使用这些API时需要考虑是否会带来性能影响。 PyTorch Pr…