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

基于SpringBoot實(shí)現(xiàn)一個(gè)有趣好玩的年會(huì)抽獎(jiǎng)系統(tǒng)

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

一、項(xiàng)目背景與設(shè)計(jì)思路

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

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

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

二、技術(shù)棧選型

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

三、項(xiàng)目結(jié)構(gòu)設(shè)計(jì)

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

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

核心表結(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
);

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

-- 中獎(jiǎng)記錄表
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 '抽獎(jiǎng)方式',
    FOREIGN KEY (employee_id) REFERENCES employee(employee_id),
    FOREIGN KEY (prize_id) REFERENCES prize(id)
);

-- 抽獎(jiǎng)活動(dòng)表
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 '活動(dòng)配置'
);

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

5.1 項(xiàng)目依賴配置

<!-- 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ù)庫(kù) -->
    <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 員工實(shí)體類

@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 抽獎(jiǎng)服務(wù)核心實(shí)現(xiàn)

@Service
@Slf4j
public class LotteryService {
    
    @Autowired
    private EmployeeRepository employeeRepository;
    
    @Autowired
    private PrizeRepository prizeRepository;
    
    @Autowired
    private WinningRecordRepository winningRecordRepository;
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 核心抽獎(jiǎng)算法 - 使用加密安全的隨機(jī)數(shù)
     */
    public LotteryResult drawPrize(Long prizeId, String drawMethod) {
        Prize prize = prizeRepository.findById(prizeId)
            .orElseThrow(() -> new RuntimeException("獎(jiǎng)品不存在"));
            
        if (prize.getRemainCount() <= 0) {
            throw new RuntimeException("該獎(jiǎng)品已抽完");
        }
        
        // 獲取所有未中獎(jiǎng)的參會(huì)員工
        List<Employee> candidates = employeeRepository.findByIsAttendedTrueAndIsWinnerFalse();
        if (candidates.isEmpty()) {
            throw new RuntimeException("沒(méi)有符合條件的抽獎(jiǎng)人員");
        }
        
        // 使用SecureRandom確保隨機(jī)性
        SecureRandom secureRandom = new SecureRandom();
        byte[] randomBytes = new byte[32];
        secureRandom.nextBytes(randomBytes);
        
        // 基于哈希值選擇中獎(jiǎng)?wù)?
        int index = Math.abs(Arrays.hashCode(randomBytes)) % candidates.size();
        Employee winner = candidates.get(index);
        
        // 保存中獎(jiǎng)記錄
        WinningRecord record = new WinningRecord();
        record.setEmployeeId(winner.getEmployeeId());
        record.setPrizeId(prizeId);
        record.setDrawMethod(drawMethod);
        record.setDrawTime(LocalDateTime.now());
        winningRecordRepository.save(record);
        
        // 更新獎(jiǎng)品剩余數(shù)量
        prize.setRemainCount(prize.getRemainCount() - 1);
        prizeRepository.save(prize);
        
        // 標(biāo)記員工為中獎(jiǎng)狀態(tài)(實(shí)際中可能需要更復(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());
        }
    }
    
    /**
     * 多種抽獎(jiǎng)模式
     */
    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);
        }
    }
    
    /**
     * 輪盤(pán)抽獎(jiǎng)算法
     */
    private LotteryResult rouletteDraw(Prize prize) {
        List<Employee> candidates = getValidCandidates();
        
        // 為每個(gè)候選人分配權(quán)重(這里簡(jiǎn)單按部門(mén)分配不同權(quán)重)
        Map<Employee, Double> weights = new HashMap<>();
        for (Employee emp : candidates) {
            double weight = getDepartmentWeight(emp.getDepartment());
            weights.put(emp, weight);
        }
        
        // 輪盤(pán)選擇
        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實(shí)時(shí)通信

@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, "歡迎加入年會(huì)抽獎(jiǎng)!");
    }
    
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) {
        // 處理客戶端消息
        String payload = message.getPayload();
        // 處理業(yè)務(wù)邏輯...
    }
    
    /**
     * 廣播中獎(jiǎng)消息
     */
    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 抽獎(jiǎng)控制器

@Controller
@RequestMapping("/lottery")
public class LotteryController {
    
    @Autowired
    private LotteryService lotteryService;
    
    @Autowired
    private LotteryWebSocketHandler webSocketHandler;
    
    /**
     * 抽獎(jiǎng)頁(yè)面
     */
    @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í)行抽獎(jiǎng)
     */
    @PostMapping("/execute")
    @ResponseBody
    public ApiResponse<LotteryResult> executeDraw(
            @RequestParam Long prizeId,
            @RequestParam(defaultValue = "RANDOM") String mode) {
        
        try {
            LotteryResult result = lotteryService.drawWithMode(prizeId, mode);
            
            // 發(fā)送實(shí)時(shí)通知
            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)計(jì)
     */
    @GetMapping("/statistics")
    @ResponseBody
    public ApiResponse<Statistics> getStatistics() {
        Statistics stats = lotteryService.getLotteryStatistics();
        return ApiResponse.success(stats);
    }
}

5.6 前端抽獎(jiǎng)頁(yè)面實(shí)現(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>年會(huì)抽獎(jiǎng)系統(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">
            <!-- 標(biāo)題區(qū)域 -->
            <div class="text-center text-white mb-5">
                <h1 class="display-3 fw-bold">?? 年會(huì)幸運(yùn)大抽獎(jiǎng) ??</h1>
                <p class="lead">夢(mèng)想成真,幸運(yùn)降臨</p>
            </div>
            
            <!-- 抽獎(jiǎng)主區(qū)域 -->
            <div class="row">
                <!-- 左側(cè):獎(jiǎng)品選擇 -->
                <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> 獎(jiǎng)品列表</h5>
                        </div>
                        <div class="card-body" id="prizeList">
                            <!-- 獎(jiǎng)品列表動(dòng)態(tài)加載 -->
                        </div>
                    </div>
                    
                    <!-- 抽獎(jiǎng)控制 -->
                    <div class="card mt-3">
                        <div class="card-body">
                            <div class="mb-3">
                                <label class="form-label">抽獎(jiǎng)模式</label>
                                <select class="form-select" id="drawMode">
                                    <option value="RANDOM">隨機(jī)抽獎(jiǎng)</option>
                                    <option value="ROULETTE">幸運(yùn)輪盤(pán)</option>
                                    <option value="GRID">九宮格抽獎(jiǎng)</option>
                                    <option value="LUCKY_NUMBER">幸運(yùn)數(shù)字</option>
                                </select>
                            </div>
                            <button class="btn btn-danger btn-lg w-100" id="drawButton">
                                <i class="fas fa-play"></i> 開(kāi)始抽獎(jiǎng)
                            </button>
                        </div>
                    </div>
                </div>
                
                <!-- 中間:抽獎(jiǎng)動(dòng)畫(huà) -->
                <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">?? 恭喜中獎(jiǎng)! ??</h4>
                                <h3 id="winnerName" class="fw-bold"></h3>
                                <p>獲得:<span id="prizeName" class="text-primary"></span></p>
                            </div>
                        </div>
                    </div>
                </div>
                
                <!-- 右側(cè):中獎(jiǎng)名單 -->
                <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> 中獎(jiǎng)名單</h5>
                        </div>
                        <div class="card-body">
                            <div id="winnerList" class="list-group">
                                <!-- 中獎(jiǎng)名單動(dòng)態(tài)加載 -->
                            </div>
                        </div>
                    </div>
                    
                    <!-- 統(tǒng)計(jì)數(shù)據(jù) -->
                    <div class="card mt-3">
                        <div class="card-body">
                            <h6>抽獎(jiǎng)統(tǒng)計(jì)</h6>
                            <div class="row text-center">
                                <div class="col-6">
                                    <div class="display-6" id="totalAttendees">0</div>
                                    <small>參會(huì)人數(shù)</small>
                                </div>
                                <div class="col-6">
                                    <div class="display-6" id="totalWinners">0</div>
                                    <small>已中獎(jiǎng)人數(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('請(qǐng)先選擇獎(jiǎng)品!');
                        return;
                    }
                    
                    if (this.isDrawing) {
                        alert('正在抽獎(jiǎng)中,請(qǐng)稍候...');
                        return;
                    }
                    
                    this.isDrawing = true;
                    $('#drawButton').prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> 抽獎(jiǎng)中...');
                    
                    const drawMode = $('#drawMode').val();
                    
                    // 開(kāi)始抽獎(jiǎng)動(dòng)畫(huà)
                    this.startWheelAnimation();
                    
                    // 發(fā)送抽獎(jiǎng)?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> 開(kāi)始抽獎(jiǎng)');
                                }, 3000);
                            } else {
                                alert(response.message);
                                this.isDrawing = false;
                                $('#drawButton').prop('disabled', false).html('<i class="fas fa-play"></i> 開(kāi)始抽獎(jiǎng)');
                            }
                        },
                        error: () => {
                            alert('抽獎(jiǎng)失敗,請(qǐng)重試!');
                            this.isDrawing = false;
                            $('#drawButton').prop('disabled', false).html('<i class="fas fa-play"></i> 開(kāi)始抽獎(jiǎng)');
                        }
                    });
                });
            }
            
            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)盤(pá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('幸運(yùn)', 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) {
                // 顯示實(shí)時(shí)通知
                const notificationHtml = `
                    <div class="toast show" role="alert">
                        <div class="toast-header bg-success text-white">
                            <strong class="me-auto">?? 恭喜中獎(jiǎng)!</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秒后自動(dòng)移除
                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() {
                // 初始化抽獎(jiǎng)轉(zhuǎn)盤(pán)
                const canvas = document.getElementById('wheelCanvas');
                if (canvas.getContext) {
                    // 已經(jīng)在上面的startWheelAnimation中初始化了
                }
            }
        }
        
        // 頁(yè)面加載完成后初始化
        $(document).ready(() => {
            window.lotterySystem = new LotterySystem();
        });
    </script>
</body>
</html>

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

6.1 多種抽獎(jiǎng)模式

  • 隨機(jī)抽獎(jiǎng):完全隨機(jī),確保公平
  • 幸運(yùn)輪盤(pán):視覺(jué)沖擊力強(qiáng),增加趣味性
  • 九宮格抽獎(jiǎng):互動(dòng)性強(qiáng),適合小團(tuán)隊(duì)
  • 幸運(yùn)數(shù)字:與員工工號(hào)關(guān)聯(lián),增加參與感

6.2 實(shí)時(shí)互動(dòng)功能

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

6.3 防作弊機(jī)制

@Component
public class LotterySecurityService {
    
    /**
     * 驗(yàn)證抽獎(jiǎng)?wù)埱蟮暮戏ㄐ?
     */
    public boolean validateDrawRequest(String sessionId, Long prizeId) {
        // 防止頻繁請(qǐng)求
        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)最多請(qǐng)求3次
    }
    
    /**
     * 驗(yàn)證員工資格
     */
    public boolean validateEmployeeEligibility(String employeeId) {
        // 檢查是否已簽到
        // 檢查是否已中獎(jiǎng)(限制每人最多中獎(jiǎng)次數(shù))
        // 檢查是否符合特定獎(jiǎng)品要求
        return true;
    }
    
    /**
     * 生成抽獎(jiǎng)過(guò)程審計(jì)日志
     */
    public void auditDrawProcess(LotteryResult result, String operator) {
        String auditLog = String.format(
            "抽獎(jiǎng)審計(jì) - 操作人:%s, 獎(jiǎng)品:%s, 中獎(jiǎng)人:%s, 時(shí)間:%s, 隨機(jī)種子:%s",
            operator,
            result.getPrize().getName(),
            result.getWinner().getName(),
            LocalDateTime.now(),
            generateRandomSeed()
        );
        
        log.info(auditLog);
        saveToBlockchain(auditLog); // 可選:保存到區(qū)塊鏈確保不可篡改
    }
}

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

@Service
public class StatisticsService {
    
    public LotteryStatistics getStatistics() {
        LotteryStatistics stats = new LotteryStatistics();
        
        // 基礎(chǔ)統(tǒng)計(jì)
        stats.setTotalAttendees(employeeRepository.countByIsAttendedTrue());
        stats.setTotalWinners(winningRecordRepository.count());
        stats.setTotalPrizes(prizeRepository.count());
        
        // 部門(mén)中獎(jiǎng)分布
        Map<String, Long> deptDistribution = winningRecordRepository
            .getDepartmentDistribution();
        stats.setDepartmentDistribution(deptDistribution);
        
        // 時(shí)間段分析
        List<HourlyDraw> hourlyDraws = winningRecordRepository
            .getHourlyDrawCount();
        stats.setHourlyDraws(hourlyDraws);
        
        // 獎(jiǎng)品熱度
        List<PrizePopularity> prizePopularity = winningRecordRepository
            .getPrizePopularity();
        stats.setPrizePopularity(prizePopularity);
        
        return stats;
    }
    
    /**
     * 生成抽獎(jiǎng)報(bào)告
     */
    public Report generateReport() {
        Report report = new Report();
        
        // 總體情況
        report.setSummary(getSummary());
        
        // 詳細(xì)數(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: 年會(huì)幸運(yùn)大抽獎(jiǎng)
    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)化與擴(kuò)展

8.1 性能優(yōu)化

@Service
@Slf4j
public class LotteryOptimizationService {
    
    /**
     * 使用Redis緩存熱門(mén)數(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 擴(kuò)展功能

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

九、測(cè)試與驗(yàn)證

單元測(cè)試

@SpringBootTest
@AutoConfigureMockMvc
class LotteryServiceTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Test
    void testRandomDraw() {
        // 測(cè)試隨機(jī)抽獎(jiǎng)的公平性
        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);
        }
        
        // 驗(yàn)證分布是否均勻
        double expectedFrequency = totalTests / 100.0; // 假設(shè)有100個(gè)候選人
        double chiSquare = calculateChiSquare(winCount, expectedFrequency);
        
        assertTrue(chiSquare < 15.0, "抽獎(jiǎng)分布不均勻");
    }
    
    @Test
    void testConcurrentDraw() throws InterruptedException {
        // 測(cè)試并發(fā)抽獎(jiǎng)
        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);
        
        // 驗(yàn)證沒(méi)有重復(fù)中獎(jiǎng)
        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ǎng)情況");
    }
}

十、總結(jié)

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

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

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

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

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

相關(guān)文章

最新評(píng)論

石台县| 成安县| 开鲁县| 沂水县| 崇义县| 广安市| 多伦县| 太原市| 临猗县| 兴业县| 孝感市| 齐河县| 茌平县| 晋江市| 建昌县| 涿州市| 灵丘县| 芒康县| 北流市| 南安市| 永济市| 嘉荫县| 保亭| 额敏县| 田东县| 呼玛县| 黔东| 资阳市| 德州市| 山西省| 开远市| 若尔盖县| 嘉鱼县| 阳曲县| 綦江县| 句容市| 越西县| 林西县| 衡阳市| 同心县| 布拖县|