最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于SpringBoot實現(xiàn)一個有趣好玩的年會抽獎系統(tǒng)

 更新時間:2026年02月07日 08:37:30   作者:碼農(nóng)阿豪@新空間  
年會抽獎是每個企業(yè)年末的重要活動,本文將基于SpringBoot框架,設(shè)計并實現(xiàn)一個公正、有趣、好玩的年會抽獎系統(tǒng),確保抽獎過程的透明性和隨機性,同時增加多種互動功能,有需要的可以了解下

一、項目背景與設(shè)計思路

年會抽獎是每個企業(yè)年末的重要活動,但傳統(tǒng)的抽獎方式往往存在公平性不足、互動性差的問題。本文將基于SpringBoot框架,設(shè)計并實現(xiàn)一個公正、有趣、好玩的年會抽獎系統(tǒng),確保抽獎過程的透明性和隨機性,同時增加多種互動功能。

系統(tǒng)核心設(shè)計原則:

  • 公正性:使用加密隨機算法,確保結(jié)果不可預(yù)測
  • 趣味性:多種抽獎模式,動畫效果,實時互動
  • 可靠性:高并發(fā)支持,數(shù)據(jù)持久化,操作日志
  • 易用性:簡潔的Web界面,易于部署和管理

二、技術(shù)棧選型

  • 后端框架:SpringBoot 2.7.x
  • 前端技術(shù):Thymeleaf + Bootstrap 5 + jQuery
  • 數(shù)據(jù)庫:MySQL 8.0 + Redis 7.0
  • 實時通信:WebSocket
  • 安全框架:Spring Security
  • 構(gòu)建工具:Maven 3.8+

三、項目結(jié)構(gòu)設(shè)計

lottery-system/
├── src/main/java/com/lottery/
│   ├── config/          # 配置類
│   ├── controller/      # 控制器層
│   ├── service/         # 業(yè)務(wù)邏輯層
│   ├── repository/      # 數(shù)據(jù)訪問層
│   ├── model/          # 實體類
│   ├── utils/          # 工具類
│   ├── aspect/         # AOP切面
│   └── security/       # 安全配置
├── src/main/resources/
│   ├── static/         # 靜態(tài)資源
│   └── templates/      # 模板文件
└── pom.xml

四、數(shù)據(jù)庫設(shè)計

核心表結(jié)構(gòu)

-- 員工表
CREATE TABLE employee (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    employee_id VARCHAR(50) UNIQUE NOT NULL,
    name VARCHAR(100) NOT NULL,
    department VARCHAR(100),
    avatar_url VARCHAR(500),
    is_attended BOOLEAN DEFAULT false,
    created_time DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- 獎品表
CREATE TABLE prize (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(200) NOT NULL,
    level INT NOT NULL COMMENT '獎品等級:1-特等獎,2-一等獎,3-二等獎...',
    total_count INT NOT NULL,
    remain_count INT NOT NULL,
    image_url VARCHAR(500),
    description TEXT,
    created_time DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- 中獎記錄表
CREATE TABLE winning_record (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    employee_id VARCHAR(50) NOT NULL,
    prize_id BIGINT NOT NULL,
    draw_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    draw_method VARCHAR(50) COMMENT '抽獎方式',
    FOREIGN KEY (employee_id) REFERENCES employee(employee_id),
    FOREIGN KEY (prize_id) REFERENCES prize(id)
);

-- 抽獎活動表
CREATE TABLE lottery_event (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(200) NOT NULL,
    start_time DATETIME,
    end_time DATETIME,
    status VARCHAR(20) DEFAULT 'NOT_STARTED',
    settings JSON COMMENT '活動配置'
);

五、核心功能實現(xiàn)

5.1 項目依賴配置

<!-- pom.xml -->
<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
    <!-- 數(shù)據(jù)庫 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <!-- 工具類 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.12.0</version>
    </dependency>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>31.1-jre</version>
    </dependency>
    
    <!-- JSON處理 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>

5.2 員工實體類

@Entity
@Table(name = "employee")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "employee_id", unique = true, nullable = false)
    private String employeeId;
    
    @Column(nullable = false)
    private String name;
    
    private String department;
    private String avatarUrl;
    
    @Column(name = "is_attended")
    private Boolean isAttended = false;
    
    @CreationTimestamp
    @Column(name = "created_time", updatable = false)
    private LocalDateTime createdTime;
    
    @Transient
    private Boolean isWinner = false;
}

5.3 抽獎服務(wù)核心實現(xiàn)

@Service
@Slf4j
public class LotteryService {
    
    @Autowired
    private EmployeeRepository employeeRepository;
    
    @Autowired
    private PrizeRepository prizeRepository;
    
    @Autowired
    private WinningRecordRepository winningRecordRepository;
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 核心抽獎算法 - 使用加密安全的隨機數(shù)
     */
    public LotteryResult drawPrize(Long prizeId, String drawMethod) {
        Prize prize = prizeRepository.findById(prizeId)
            .orElseThrow(() -> new RuntimeException("獎品不存在"));
            
        if (prize.getRemainCount() <= 0) {
            throw new RuntimeException("該獎品已抽完");
        }
        
        // 獲取所有未中獎的參會員工
        List<Employee> candidates = employeeRepository.findByIsAttendedTrueAndIsWinnerFalse();
        if (candidates.isEmpty()) {
            throw new RuntimeException("沒有符合條件的抽獎人員");
        }
        
        // 使用SecureRandom確保隨機性
        SecureRandom secureRandom = new SecureRandom();
        byte[] randomBytes = new byte[32];
        secureRandom.nextBytes(randomBytes);
        
        // 基于哈希值選擇中獎?wù)?
        int index = Math.abs(Arrays.hashCode(randomBytes)) % candidates.size();
        Employee winner = candidates.get(index);
        
        // 保存中獎記錄
        WinningRecord record = new WinningRecord();
        record.setEmployeeId(winner.getEmployeeId());
        record.setPrizeId(prizeId);
        record.setDrawMethod(drawMethod);
        record.setDrawTime(LocalDateTime.now());
        winningRecordRepository.save(record);
        
        // 更新獎品剩余數(shù)量
        prize.setRemainCount(prize.getRemainCount() - 1);
        prizeRepository.save(prize);
        
        // 標記員工為中獎狀態(tài)(實際中可能需要更復(fù)雜的邏輯)
        markEmployeeAsWinner(winner.getId());
        
        // 發(fā)送WebSocket通知
        sendWinningNotification(winner, prize);
        
        return LotteryResult.builder()
                .winner(winner)
                .prize(prize)
                .drawTime(LocalDateTime.now())
                .drawId(record.getId())
                .build();
    }
    
    /**
     * 批量導(dǎo)入員工
     */
    @Transactional
    public void importEmployees(MultipartFile file) {
        try {
            List<Employee> employees = parseEmployeeFile(file);
            employeeRepository.saveAll(employees);
            log.info("成功導(dǎo)入{}名員工", employees.size());
        } catch (Exception e) {
            log.error("導(dǎo)入員工失敗", e);
            throw new RuntimeException("導(dǎo)入失敗: " + e.getMessage());
        }
    }
    
    /**
     * 多種抽獎模式
     */
    public LotteryResult drawWithMode(Prize prize, DrawMode mode) {
        switch (mode) {
            case RANDOM:
                return randomDraw(prize);
            case LUCKY_NUMBER:
                return luckyNumberDraw(prize);
            case ROULETTE:
                return rouletteDraw(prize);
            case GRID:
                return gridDraw(prize);
            default:
                return randomDraw(prize);
        }
    }
    
    /**
     * 輪盤抽獎算法
     */
    private LotteryResult rouletteDraw(Prize prize) {
        List<Employee> candidates = getValidCandidates();
        
        // 為每個候選人分配權(quán)重(這里簡單按部門分配不同權(quán)重)
        Map<Employee, Double> weights = new HashMap<>();
        for (Employee emp : candidates) {
            double weight = getDepartmentWeight(emp.getDepartment());
            weights.put(emp, weight);
        }
        
        // 輪盤選擇
        Employee winner = rouletteSelect(weights);
        return createLotteryResult(winner, prize, "ROULETTE");
    }
    
    private Employee rouletteSelect(Map<Employee, Double> weights) {
        double totalWeight = weights.values().stream().mapToDouble(Double::doubleValue).sum();
        double random = Math.random() * totalWeight;
        
        double cumulativeWeight = 0.0;
        for (Map.Entry<Employee, Double> entry : weights.entrySet()) {
            cumulativeWeight += entry.getValue();
            if (random <= cumulativeWeight) {
                return entry.getKey();
            }
        }
        
        return weights.keySet().iterator().next();
    }
}

5.4 WebSocket實時通信

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(lotteryWebSocketHandler(), "/ws/lottery")
                .setAllowedOrigins("*")
                .withSockJS();
    }
    
    @Bean
    public WebSocketHandler lotteryWebSocketHandler() {
        return new LotteryWebSocketHandler();
    }
}

@Component
public class LotteryWebSocketHandler extends TextWebSocketHandler {
    
    private static final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
    
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        String sessionId = session.getId();
        sessions.put(sessionId, session);
        
        // 發(fā)送歡迎消息
        sendMessage(session, "歡迎加入年會抽獎!");
    }
    
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) {
        // 處理客戶端消息
        String payload = message.getPayload();
        // 處理業(yè)務(wù)邏輯...
    }
    
    /**
     * 廣播中獎消息
     */
    public void broadcastWinningMessage(Employee winner, Prize prize) {
        Map<String, Object> message = new HashMap<>();
        message.put("type", "WINNING_NOTIFICATION");
        message.put("winner", winner.getName());
        message.put("prize", prize.getName());
        message.put("department", winner.getDepartment());
        message.put("time", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_TIME));
        
        String jsonMessage = JSON.toJSONString(message);
        
        sessions.values().forEach(session -> {
            try {
                if (session.isOpen()) {
                    session.sendMessage(new TextMessage(jsonMessage));
                }
            } catch (IOException e) {
                log.error("發(fā)送WebSocket消息失敗", e);
            }
        });
    }
}

5.5 抽獎控制器

@Controller
@RequestMapping("/lottery")
public class LotteryController {
    
    @Autowired
    private LotteryService lotteryService;
    
    @Autowired
    private LotteryWebSocketHandler webSocketHandler;
    
    /**
     * 抽獎頁面
     */
    @GetMapping("/draw")
    public String drawPage(Model model) {
        List<Prize> availablePrizes = lotteryService.getAvailablePrizes();
        List<Employee> attendees = lotteryService.getAttendees();
        
        model.addAttribute("prizes", availablePrizes);
        model.addAttribute("attendees", attendees);
        model.addAttribute("totalAttendees", attendees.size());
        
        return "lottery/draw";
    }
    
    /**
     * 執(zhí)行抽獎
     */
    @PostMapping("/execute")
    @ResponseBody
    public ApiResponse<LotteryResult> executeDraw(
            @RequestParam Long prizeId,
            @RequestParam(defaultValue = "RANDOM") String mode) {
        
        try {
            LotteryResult result = lotteryService.drawWithMode(prizeId, mode);
            
            // 發(fā)送實時通知
            webSocketHandler.broadcastWinningMessage(
                result.getWinner(), 
                result.getPrize()
            );
            
            return ApiResponse.success(result);
        } catch (Exception e) {
            return ApiResponse.error(e.getMessage());
        }
    }
    
    /**
     * 大屏幕顯示
     */
    @GetMapping("/screen")
    public String bigScreen(Model model) {
        List<WinningRecord> recentWinners = lotteryService.getRecentWinners(10);
        model.addAttribute("winners", recentWinners);
        return "lottery/screen";
    }
    
    /**
     * 數(shù)據(jù)統(tǒng)計
     */
    @GetMapping("/statistics")
    @ResponseBody
    public ApiResponse<Statistics> getStatistics() {
        Statistics stats = lotteryService.getLotteryStatistics();
        return ApiResponse.success(stats);
    }
}

5.6 前端抽獎頁面實現(xiàn)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>年會抽獎系統(tǒng)</title>
    
    <!-- Bootstrap CSS -->
    <link  rel="external nofollow"  rel="stylesheet">
    <link  rel="external nofollow"  rel="stylesheet">
    
    <style>
        .lottery-container {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }
        
        .prize-card {
            transition: all 0.3s ease;
            cursor: pointer;
        }
        
        .prize-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 10px 20px rgba(0,0,0,0.2);
        }
        
        .lottery-wheel {
            width: 500px;
            height: 500px;
            position: relative;
            margin: 0 auto;
        }
        
        .wheel-canvas {
            width: 100%;
            height: 100%;
        }
        
        .winner-display {
            background: rgba(255,255,255,0.9);
            border-radius: 15px;
            padding: 20px;
            animation: slideIn 0.5s ease;
        }
        
        @keyframes slideIn {
            from { transform: translateY(50px); opacity: 0; }
            to { transform: translateY(0); opacity: 1; }
        }
        
        .confetti {
            position: fixed;
            width: 15px;
            height: 15px;
            background-color: #f00;
            top: -10px;
            opacity: 0;
        }
        
        @keyframes confetti-fall {
            0% { top: -10px; opacity: 1; }
            100% { top: 100vh; opacity: 0; }
        }
    </style>
</head>
<body>
    <div class="lottery-container">
        <div class="container">
            <!-- 標題區(qū)域 -->
            <div class="text-center text-white mb-5">
                <h1 class="display-3 fw-bold">?? 年會幸運大抽獎 ??</h1>
                <p class="lead">夢想成真,幸運降臨</p>
            </div>
            
            <!-- 抽獎主區(qū)域 -->
            <div class="row">
                <!-- 左側(cè):獎品選擇 -->
                <div class="col-md-4">
                    <div class="card">
                        <div class="card-header bg-primary text-white">
                            <h5 class="mb-0"><i class="fas fa-gift"></i> 獎品列表</h5>
                        </div>
                        <div class="card-body" id="prizeList">
                            <!-- 獎品列表動態(tài)加載 -->
                        </div>
                    </div>
                    
                    <!-- 抽獎控制 -->
                    <div class="card mt-3">
                        <div class="card-body">
                            <div class="mb-3">
                                <label class="form-label">抽獎模式</label>
                                <select class="form-select" id="drawMode">
                                    <option value="RANDOM">隨機抽獎</option>
                                    <option value="ROULETTE">幸運輪盤</option>
                                    <option value="GRID">九宮格抽獎</option>
                                    <option value="LUCKY_NUMBER">幸運數(shù)字</option>
                                </select>
                            </div>
                            <button class="btn btn-danger btn-lg w-100" id="drawButton">
                                <i class="fas fa-play"></i> 開始抽獎
                            </button>
                        </div>
                    </div>
                </div>
                
                <!-- 中間:抽獎動畫 -->
                <div class="col-md-4">
                    <div class="lottery-wheel">
                        <canvas id="wheelCanvas" class="wheel-canvas"></canvas>
                        <div class="text-center mt-3">
                            <div class="winner-display" id="winnerDisplay" style="display:none;">
                                <h4 class="text-success">?? 恭喜中獎! ??</h4>
                                <h3 id="winnerName" class="fw-bold"></h3>
                                <p>獲得:<span id="prizeName" class="text-primary"></span></p>
                            </div>
                        </div>
                    </div>
                </div>
                
                <!-- 右側(cè):中獎名單 -->
                <div class="col-md-4">
                    <div class="card">
                        <div class="card-header bg-success text-white">
                            <h5 class="mb-0"><i class="fas fa-trophy"></i> 中獎名單</h5>
                        </div>
                        <div class="card-body">
                            <div id="winnerList" class="list-group">
                                <!-- 中獎名單動態(tài)加載 -->
                            </div>
                        </div>
                    </div>
                    
                    <!-- 統(tǒng)計數(shù)據(jù) -->
                    <div class="card mt-3">
                        <div class="card-body">
                            <h6>抽獎統(tǒng)計</h6>
                            <div class="row text-center">
                                <div class="col-6">
                                    <div class="display-6" id="totalAttendees">0</div>
                                    <small>參會人數(shù)</small>
                                </div>
                                <div class="col-6">
                                    <div class="display-6" id="totalWinners">0</div>
                                    <small>已中獎人數(shù)</small>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    
    <!-- WebSocket連接 -->
    <script src="https://cdn.bootcdn.net/ajax/libs/sockjs-client/1.6.1/sockjs.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script>
    
    <script>
        class LotterySystem {
            constructor() {
                this.stompClient = null;
                this.currentPrizeId = null;
                this.isDrawing = false;
                this.init();
            }
            
            init() {
                this.loadPrizes();
                this.loadWinners();
                this.loadStatistics();
                this.connectWebSocket();
                this.bindEvents();
                this.initWheel();
            }
            
            connectWebSocket() {
                const socket = new SockJS('/ws/lottery');
                this.stompClient = Stomp.over(socket);
                
                this.stompClient.connect({}, (frame) => {
                    console.log('Connected: ' + frame);
                    
                    this.stompClient.subscribe('/topic/winners', (message) => {
                        const notification = JSON.parse(message.body);
                        this.showWinningNotification(notification);
                        this.loadWinners();
                        this.loadStatistics();
                    });
                });
            }
            
            loadPrizes() {
                $.get('/lottery/prizes', (prizes) => {
                    const container = $('#prizeList');
                    container.empty();
                    
                    prizes.forEach(prize => {
                        const card = `
                            <div class="card prize-card mb-2" data-prize-id="${prize.id}">
                                <div class="card-body">
                                    <h6 class="card-title">${prize.name}</h6>
                                    <p class="card-text text-muted small">剩余: ${prize.remainCount}/${prize.totalCount}</p>
                                </div>
                            </div>
                        `;
                        container.append(card);
                    });
                    
                    // 綁定選擇事件
                    $('.prize-card').click(function() {
                        $('.prize-card').removeClass('border-primary');
                        $(this).addClass('border-primary border-3');
                        this.currentPrizeId = $(this).data('prize-id');
                    });
                });
            }
            
            loadWinners() {
                $.get('/lottery/recent-winners', (winners) => {
                    const container = $('#winnerList');
                    container.empty();
                    
                    winners.forEach(record => {
                        const item = `
                            <div class="list-group-item">
                                <div class="d-flex w-100 justify-content-between">
                                    <h6 class="mb-1">${record.employeeName}</h6>
                                    <small>${record.department}</small>
                                </div>
                                <p class="mb-1">${record.prizeName}</p>
                                <small class="text-muted">${record.drawTime}</small>
                            </div>
                        `;
                        container.prepend(item);
                    });
                });
            }
            
            loadStatistics() {
                $.get('/lottery/statistics', (stats) => {
                    $('#totalAttendees').text(stats.totalAttendees);
                    $('#totalWinners').text(stats.totalWinners);
                });
            }
            
            bindEvents() {
                $('#drawButton').click(() => {
                    if (!this.currentPrizeId) {
                        alert('請先選擇獎品!');
                        return;
                    }
                    
                    if (this.isDrawing) {
                        alert('正在抽獎中,請稍候...');
                        return;
                    }
                    
                    this.isDrawing = true;
                    $('#drawButton').prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> 抽獎中...');
                    
                    const drawMode = $('#drawMode').val();
                    
                    // 開始抽獎動畫
                    this.startWheelAnimation();
                    
                    // 發(fā)送抽獎?wù)埱?
                    $.ajax({
                        url: '/lottery/execute',
                        method: 'POST',
                        data: {
                            prizeId: this.currentPrizeId,
                            mode: drawMode
                        },
                        success: (response) => {
                            if (response.success) {
                                setTimeout(() => {
                                    this.showWinner(response.data);
                                    this.createConfetti();
                                    this.isDrawing = false;
                                    $('#drawButton').prop('disabled', false).html('<i class="fas fa-play"></i> 開始抽獎');
                                }, 3000);
                            } else {
                                alert(response.message);
                                this.isDrawing = false;
                                $('#drawButton').prop('disabled', false).html('<i class="fas fa-play"></i> 開始抽獎');
                            }
                        },
                        error: () => {
                            alert('抽獎失敗,請重試!');
                            this.isDrawing = false;
                            $('#drawButton').prop('disabled', false).html('<i class="fas fa-play"></i> 開始抽獎');
                        }
                    });
                });
            }
            
            startWheelAnimation() {
                const canvas = document.getElementById('wheelCanvas');
                const ctx = canvas.getContext('2d');
                const centerX = canvas.width / 2;
                const centerY = canvas.height / 2;
                const radius = Math.min(centerX, centerY) - 10;
                
                let rotation = 0;
                
                const animate = () => {
                    ctx.clearRect(0, 0, canvas.width, canvas.height);
                    
                    // 繪制轉(zhuǎn)盤
                    rotation += 0.1;
                    
                    // 繪制扇形
                    const segments = 12;
                    const segmentAngle = (2 * Math.PI) / segments;
                    
                    for (let i = 0; i < segments; i++) {
                        const startAngle = segmentAngle * i + rotation;
                        const endAngle = segmentAngle * (i + 1) + rotation;
                        
                        ctx.beginPath();
                        ctx.moveTo(centerX, centerY);
                        ctx.arc(centerX, centerY, radius, startAngle, endAngle);
                        ctx.closePath();
                        
                        // 交替顏色
                        ctx.fillStyle = i % 2 === 0 ? '#FF6B6B' : '#4ECDC4';
                        ctx.fill();
                        
                        // 繪制文字
                        ctx.save();
                        ctx.translate(centerX, centerY);
                        ctx.rotate(startAngle + segmentAngle / 2);
                        ctx.textAlign = 'right';
                        ctx.fillStyle = 'white';
                        ctx.font = 'bold 14px Arial';
                        ctx.fillText('幸運', radius - 20, 10);
                        ctx.restore();
                    }
                    
                    // 繪制指針
                    ctx.beginPath();
                    ctx.moveTo(centerX, centerY - 20);
                    ctx.lineTo(centerX, centerY - radius + 20);
                    ctx.lineTo(centerX + 10, centerY - radius);
                    ctx.lineTo(centerX - 10, centerY - radius);
                    ctx.closePath();
                    ctx.fillStyle = '#FFD93D';
                    ctx.fill();
                    
                    if (this.isDrawing) {
                        requestAnimationFrame(animate);
                    }
                };
                
                canvas.width = 500;
                canvas.height = 500;
                animate();
            }
            
            showWinner(result) {
                $('#winnerName').text(result.winner.name);
                $('#prizeName').text(result.prize.name);
                $('#winnerDisplay').fadeIn();
                
                // 播放音效
                this.playSound('winning');
            }
            
            showWinningNotification(notification) {
                // 顯示實時通知
                const notificationHtml = `
                    <div class="toast show" role="alert">
                        <div class="toast-header bg-success text-white">
                            <strong class="me-auto">?? 恭喜中獎!</strong>
                            <small>剛剛</small>
                        </div>
                        <div class="toast-body">
                            <strong>${notification.winner}</strong> 獲得 <strong>${notification.prize}</strong>
                        </div>
                    </div>
                `;
                
                $('.toast-container').remove();
                $('body').append(`<div class="toast-container position-fixed top-0 end-0 p-3">${notificationHtml}</div>`);
                
                // 5秒后自動移除
                setTimeout(() => {
                    $('.toast-container').remove();
                }, 5000);
            }
            
            createConfetti() {
                const colors = ['#FF6B6B', '#4ECDC4', '#FFD93D', '#6BCF7F'];
                
                for (let i = 0; i < 100; i++) {
                    const confetti = document.createElement('div');
                    confetti.className = 'confetti';
                    confetti.style.left = Math.random() * 100 + 'vw';
                    confetti.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
                    confetti.style.width = Math.random() * 10 + 5 + 'px';
                    confetti.style.height = Math.random() * 10 + 5 + 'px';
                    confetti.style.animation = `confetti-fall ${Math.random() * 3 + 2}s linear forwards`;
                    confetti.style.animationDelay = Math.random() * 1 + 's';
                    
                    document.body.appendChild(confetti);
                    
                    setTimeout(() => {
                        confetti.remove();
                    }, 5000);
                }
            }
            
            playSound(type) {
                const audio = new Audio();
                if (type === 'winning') {
                    audio.src = '/sound/winning.mp3';
                } else if (type === 'draw') {
                    audio.src = '/sound/draw.mp3';
                }
                audio.play().catch(e => console.log('音頻播放失敗:', e));
            }
            
            initWheel() {
                // 初始化抽獎轉(zhuǎn)盤
                const canvas = document.getElementById('wheelCanvas');
                if (canvas.getContext) {
                    // 已經(jīng)在上面的startWheelAnimation中初始化了
                }
            }
        }
        
        // 頁面加載完成后初始化
        $(document).ready(() => {
            window.lotterySystem = new LotterySystem();
        });
    </script>
</body>
</html>

六、系統(tǒng)特色功能

6.1 多種抽獎模式

  • 隨機抽獎:完全隨機,確保公平
  • 幸運輪盤:視覺沖擊力強,增加趣味性
  • 九宮格抽獎:互動性強,適合小團隊
  • 幸運數(shù)字:與員工工號關(guān)聯(lián),增加參與感

6.2 實時互動功能

  • WebSocket實時通知:中獎結(jié)果實時推送到所有客戶端
  • 大屏幕顯示:專門為中獎?wù)故驹O(shè)計的全屏界面
  • 彈幕互動:員工可以發(fā)送祝福彈幕
  • 倒計時功能:增加緊張氛圍

6.3 防作弊機制

@Component
public class LotterySecurityService {
    
    /**
     * 驗證抽獎?wù)埱蟮暮戏ㄐ?
     */
    public boolean validateDrawRequest(String sessionId, Long prizeId) {
        // 防止頻繁請求
        String key = "draw:limit:" + sessionId;
        Long count = redisTemplate.opsForValue().increment(key, 1);
        
        if (count == 1) {
            redisTemplate.expire(key, 10, TimeUnit.SECONDS);
        }
        
        return count <= 3; // 10秒內(nèi)最多請求3次
    }
    
    /**
     * 驗證員工資格
     */
    public boolean validateEmployeeEligibility(String employeeId) {
        // 檢查是否已簽到
        // 檢查是否已中獎(限制每人最多中獎次數(shù))
        // 檢查是否符合特定獎品要求
        return true;
    }
    
    /**
     * 生成抽獎過程審計日志
     */
    public void auditDrawProcess(LotteryResult result, String operator) {
        String auditLog = String.format(
            "抽獎審計 - 操作人:%s, 獎品:%s, 中獎人:%s, 時間:%s, 隨機種子:%s",
            operator,
            result.getPrize().getName(),
            result.getWinner().getName(),
            LocalDateTime.now(),
            generateRandomSeed()
        );
        
        log.info(auditLog);
        saveToBlockchain(auditLog); // 可選:保存到區(qū)塊鏈確保不可篡改
    }
}

6.4 數(shù)據(jù)統(tǒng)計與分析

@Service
public class StatisticsService {
    
    public LotteryStatistics getStatistics() {
        LotteryStatistics stats = new LotteryStatistics();
        
        // 基礎(chǔ)統(tǒng)計
        stats.setTotalAttendees(employeeRepository.countByIsAttendedTrue());
        stats.setTotalWinners(winningRecordRepository.count());
        stats.setTotalPrizes(prizeRepository.count());
        
        // 部門中獎分布
        Map<String, Long> deptDistribution = winningRecordRepository
            .getDepartmentDistribution();
        stats.setDepartmentDistribution(deptDistribution);
        
        // 時間段分析
        List<HourlyDraw> hourlyDraws = winningRecordRepository
            .getHourlyDrawCount();
        stats.setHourlyDraws(hourlyDraws);
        
        // 獎品熱度
        List<PrizePopularity> prizePopularity = winningRecordRepository
            .getPrizePopularity();
        stats.setPrizePopularity(prizePopularity);
        
        return stats;
    }
    
    /**
     * 生成抽獎報告
     */
    public Report generateReport() {
        Report report = new Report();
        
        // 總體情況
        report.setSummary(getSummary());
        
        // 詳細數(shù)據(jù)
        report.setDetails(getDetailedData());
        
        // 圖表數(shù)據(jù)
        report.setCharts(getChartData());
        
        // 分析結(jié)論
        report.setAnalysis(getAnalysis());
        
        return report;
    }
}

七、部署與配置

7.1 應(yīng)用配置

# application.yml
server:
  port: 8080
  
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/lottery_db?useSSL=false&serverTimezone=UTC
    username: lottery_admin
    password: lottery_password
    driver-class-name: com.mysql.cj.jdbc.Driver
    
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        
  redis:
    host: localhost
    port: 6379
    password: 
    timeout: 5000ms
    
  thymeleaf:
    cache: false
    mode: HTML
    encoding: UTF-8
    
lottery:
  security:
    admin-username: admin
    admin-password: ${ADMIN_PASSWORD:admin123}
    jwt-secret: ${JWT_SECRET:lottery-secret-key}
    
  draw:
    max-attempts-per-minute: 10
    cooldown-seconds: 5
    
  ui:
    title: 年會幸運大抽獎
    theme: gradient-blue
    animation-enabled: true

7.2 Docker部署

# Dockerfile
FROM openjdk:11-jre-slim

WORKDIR /app

COPY target/lottery-system-1.0.0.jar app.jar

RUN mkdir -p /app/logs /app/uploads

ENV TZ=Asia/Shanghai
ENV JAVA_OPTS="-Xmx512m -Xms256m"

EXPOSE 8080

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
# docker-compose.yml
version: '3.8'

services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root_password
      MYSQL_DATABASE: lottery_db
      MYSQL_USER: lottery_admin
      MYSQL_PASSWORD: lottery_password
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
      
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
      
  lottery-app:
    build: .
    ports:
      - "8080:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/lottery_db
      SPRING_REDIS_HOST: redis
    depends_on:
      - mysql
      - redis
      
volumes:
  mysql_data:
  redis_data:

八、系統(tǒng)優(yōu)化與擴展

8.1 性能優(yōu)化

@Service
@Slf4j
public class LotteryOptimizationService {
    
    /**
     * 使用Redis緩存熱門數(shù)據(jù)
     */
    @Cacheable(value = "prizes", key = "'available'")
    public List<Prize> getAvailablePrizes() {
        return prizeRepository.findByRemainCountGreaterThan(0);
    }
    
    /**
     * 批量處理優(yōu)化
     */
    @Transactional
    public void batchProcessWinners(List<Long> winnerIds, Long prizeId) {
        // 使用批量插入提高性能
        List<WinningRecord> records = winnerIds.stream()
            .map(employeeId -> {
                WinningRecord record = new WinningRecord();
                record.setEmployeeId(employeeId.toString());
                record.setPrizeId(prizeId);
                return record;
            })
            .collect(Collectors.toList());
            
        winningRecordRepository.saveAll(records);
    }
    
    /**
     * 異步處理非核心業(yè)務(wù)
     */
    @Async
    public void asyncSendNotification(WinningRecord record) {
        // 發(fā)送郵件通知
        emailService.sendWinningEmail(record);
        
        // 發(fā)送短信通知
        smsService.sendWinningSMS(record);
        
        // 記錄日志
        auditService.logWinningEvent(record);
    }
}

8.2 擴展功能

  • 微信小程序接入:員工掃碼參與
  • 人臉識別簽到:防止代簽
  • 虛擬禮物系統(tǒng):員工互送祝福
  • 抽獎策略引擎:可配置的抽獎規(guī)則
  • 多會場支持:分布式抽獎同步

九、測試與驗證

單元測試

@SpringBootTest
@AutoConfigureMockMvc
class LotteryServiceTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Test
    void testRandomDraw() {
        // 測試隨機抽獎的公平性
        Map<Long, Integer> winCount = new HashMap<>();
        int totalTests = 10000;
        
        for (int i = 0; i < totalTests; i++) {
            LotteryResult result = lotteryService.drawPrize(1L, "RANDOM");
            Long winnerId = result.getWinner().getId();
            winCount.put(winnerId, winCount.getOrDefault(winnerId, 0) + 1);
        }
        
        // 驗證分布是否均勻
        double expectedFrequency = totalTests / 100.0; // 假設(shè)有100個候選人
        double chiSquare = calculateChiSquare(winCount, expectedFrequency);
        
        assertTrue(chiSquare < 15.0, "抽獎分布不均勻");
    }
    
    @Test
    void testConcurrentDraw() throws InterruptedException {
        // 測試并發(fā)抽獎
        int threadCount = 100;
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        CountDownLatch latch = new CountDownLatch(threadCount);
        
        List<Future<LotteryResult>> futures = new ArrayList<>();
        
        for (int i = 0; i < threadCount; i++) {
            Future<LotteryResult> future = executor.submit(() -> {
                try {
                    return lotteryService.drawPrize(1L, "RANDOM");
                } finally {
                    latch.countDown();
                }
            });
            futures.add(future);
        }
        
        latch.await(10, TimeUnit.SECONDS);
        
        // 驗證沒有重復(fù)中獎
        Set<Long> winnerIds = futures.stream()
            .map(f -> {
                try {
                    return f.get().getWinner().getId();
                } catch (Exception e) {
                    return null;
                }
            })
            .filter(Objects::nonNull)
            .collect(Collectors.toSet());
            
        assertEquals(threadCount, winnerIds.size(), "存在重復(fù)中獎情況");
    }
}

十、總結(jié)

本文詳細介紹了如何使用SpringBoot構(gòu)建一個公正、有趣、好玩的年會抽獎系統(tǒng)。系統(tǒng)具有以下特點:

  • 公正性保障:使用加密安全的隨機數(shù)生成算法,所有操作都有審計日志
  • 趣味性體驗:多種抽獎模式,炫酷的動畫效果,實時互動功能
  • 高可靠性:支持高并發(fā),數(shù)據(jù)持久化,完善的錯誤處理機制
  • 易于擴展:模塊化設(shè)計,方便添加新功能和抽獎模式
  • 部署簡單:支持Docker容器化部署,一鍵啟動

系統(tǒng)不僅滿足了基本的抽獎需求,還通過WebSocket實時通信、多種抽獎模式、數(shù)據(jù)統(tǒng)計分析等功能,大大提升了年會的互動性和趣味性。企業(yè)可以根據(jù)自身需求,進一步擴展功能,如集成企業(yè)微信、添加更多互動游戲等,打造獨一無二的年會抽獎體驗。

通過本系統(tǒng)的實施,企業(yè)可以確保抽獎活動的公平公正,同時提升員工的參與感和滿意度,為年會增添更多歡樂和驚喜。

到此這篇關(guān)于基于SpringBoot實現(xiàn)一個有趣好玩的年會抽獎系統(tǒng)的文章就介紹到這了,更多相關(guān)SpringBoot年會抽獎系統(tǒng)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • springSecurity實現(xiàn)簡單的登錄功能

    springSecurity實現(xiàn)簡單的登錄功能

    這篇文章主要為大家詳細介紹了springSecurity實現(xiàn)簡單的登錄功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-09-09
  • java合并多個文件的實例代碼

    java合并多個文件的實例代碼

    在本篇文章里小編給大家整理的是關(guān)于java合并多個文件的實例代碼,有需要的朋友們可以參考學(xué)習下。
    2020-02-02
  • Java Web實現(xiàn)session過期后自動跳轉(zhuǎn)到登陸頁功能【基于過濾器】

    Java Web實現(xiàn)session過期后自動跳轉(zhuǎn)到登陸頁功能【基于過濾器】

    這篇文章主要介紹了Java Web實現(xiàn)session過期后自動跳轉(zhuǎn)到登陸頁功能,涉及java過濾器針對session的判斷與跳轉(zhuǎn)相關(guān)操作技巧,需要的朋友可以參考下
    2017-11-11
  • spring如何快速穩(wěn)定解決循環(huán)依賴問題

    spring如何快速穩(wěn)定解決循環(huán)依賴問題

    這篇文章主要介紹了spring如何快速穩(wěn)定解決循環(huán)依賴問題,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-03-03
  • 最新IntelliJ IDEA2017.3 激活方式

    最新IntelliJ IDEA2017.3 激活方式

    本文給大家提供最新IntelliJ IDEA2017.3 激活方式,本文給大家介紹的非常詳細,具有參考借鑒價值,需要的朋友可以參考下
    2018-01-01
  • Java容器ArrayList知識點總結(jié)

    Java容器ArrayList知識點總結(jié)

    本篇文章給大家分享了Java容器ArrayList的相關(guān)知識點,對此有需要的朋友可以跟著學(xué)習參考下。
    2018-05-05
  • Java讀取制表符文本轉(zhuǎn)換為JSON實現(xiàn)實例

    Java讀取制表符文本轉(zhuǎn)換為JSON實現(xiàn)實例

    在Java開發(fā)中,處理各種數(shù)據(jù)格式是常見的任務(wù),本文將介紹如何使用Java讀取制表符文本文件,并將其轉(zhuǎn)換為JSON格式,以便于后續(xù)的數(shù)據(jù)處理和分析,我們將使用Java中的相關(guān)庫來實現(xiàn)這個過程,并提供詳細的代碼示例
    2024-01-01
  • SpringBoot項目從搭建到發(fā)布一條龍

    SpringBoot項目從搭建到發(fā)布一條龍

    這篇文章主要介紹了SpringBoot項目從搭建到發(fā)布一條龍,本文給大家介紹的非常詳細,對大家的學(xué)習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-02-02
  • spring+maven實現(xiàn)郵件發(fā)送

    spring+maven實現(xiàn)郵件發(fā)送

    這篇文章主要為大家詳細介紹了spring+maven實現(xiàn)郵件發(fā)送,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-07-07
  • Java中的相除(/)和取余(%)的實現(xiàn)方法

    Java中的相除(/)和取余(%)的實現(xiàn)方法

    這篇文章主要介紹了Java中的相除(/)和取余(%)的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧
    2019-07-07

最新評論

卢氏县| 奉贤区| 嘉祥县| 浠水县| 德令哈市| 新巴尔虎左旗| 宜川县| 农安县| 沙田区| 建平县| 繁峙县| 久治县| 新竹市| 吴川市| 松溪县| 河北省| 子长县| 山丹县| 尼木县| 浏阳市| 内黄县| 收藏| 昆明市| 古蔺县| 松阳县| 屏东县| 安泽县| 开化县| 绵竹市| 鄂托克前旗| 扎兰屯市| 兴仁县| 张北县| 广昌县| 四川省| 石嘴山市| 贞丰县| 吉隆县| 平泉县| 宿松县| 柘荣县|