【项目】【抽奖系统】查询中奖记录

目录

  • 一、简介
  • 二、参数列表
  • 三、接口规范
  • 四、controller层
    • [4.1 convertWinningRecordsResult:service层与controller层返回结果转换方法](#4.1 convertWinningRecordsResult:service层与controller层返回结果转换方法)
    • [4.2 WinningRecordsParam :传入参数](#4.2 WinningRecordsParam :传入参数)
    • [4.3 WinningRecordsResult :controller层返回结果](#4.3 WinningRecordsResult :controller层返回结果)
  • 五、service层
    • [5.1 定义接口](#5.1 定义接口)
    • [5.2 实现接口](#5.2 实现接口)
    • [5.3 WinningRecordsDTO :service返回结果](#5.3 WinningRecordsDTO :service返回结果)
  • 六、dao层
  • 七、前端

一、简介

时序图:

二、参数列表

参数名 描述 类型 默认值 条件
activityId 活动id Long 必须
prizeId 奖品id Long 非必须

三、接口规范

java 复制代码
[请求] /winning-records/show POST
{
 "activityId":23
}
[响应] 
{
 "code": 200,
 "data": [
	 {
		 "winnerId": 15,
		 "winnerName": "张三",
		 "prizeName": "华为⼿机",
		 "prizeTier": "⼀等奖",
		 "winningTime": "2024-05-21T11:55:10.000+00:00"
	 },
	 {
		 "winnerId": 21,
		 "winnerName": "李四",
		 "prizeName": "华为⼿机",
		 "prizeTier": "⼀等奖",
		 "winningTime": "2024-05-21T11:55:10.000+00:00"
	 }
 ],
 "msg": ""
}

四、controller层

com/yj/lottery_system/controller 包下 DrawPrizeController.java 类:

java 复制代码
    @RequestMapping("/winning-records/show")
    public CommonResult<List<WinningRecordsResult>> showWinningRecords(@RequestBody@Valid WinningRecordsParam param){
        //打印日志
        log.info("showWinningRecords WinningRecordsParam : {}", JacksonUtil.writeValueAsString(param));
        //调用service
        List<WinningRecordsDTO> winningRecordsDTOList = drawPrizeService.getRecords(param);
        return CommonResult.success(convertWinningRecordsResult(winningRecordsDTOList));
    }

4.1 convertWinningRecordsResult:service层与controller层返回结果转换方法

com/yj/lottery_system/controller 包下 DrawPrizeController.java 类:

java 复制代码
    /**
     * showWinningRecords 返回结果转换方法
     * @param winningRecordsDTOList
     * @return
     */
    private List<WinningRecordsResult> convertWinningRecordsResult(List<WinningRecordsDTO> winningRecordsDTOList) {
            if(CollectionUtils.isEmpty(winningRecordsDTOList)) {
            return Arrays.asList();
        }
        List<WinningRecordsResult> winningRecordsResultList =  winningRecordsDTOList.stream()
                .map(winningRecordsDTO -> {
                    WinningRecordsResult winningRecordsResult = new WinningRecordsResult();
                    winningRecordsResult.setWinnerId(winningRecordsDTO.getWinnerId());
                    winningRecordsResult.setWinnerName(winningRecordsDTO.getWinnerName());
                    winningRecordsResult.setPrizeName(winningRecordsDTO.getPrizeName());
                    winningRecordsResult.setPrizeTier(winningRecordsDTO.getPrizeTier().getMessage());
                    winningRecordsResult.setWinningTime(winningRecordsDTO.getWinningTime());
                    return winningRecordsResult;
                }).collect(Collectors.toList());

        return winningRecordsResultList;
    }

4.2 WinningRecordsParam :传入参数

com.yj.lottery_system.controller.param 包下

java 复制代码
package com.yj.lottery_system.controller.param;

import jakarta.validation.constraints.NotNull;
import lombok.Data;

import java.io.Serializable;

@Data
public class WinningRecordsParam implements Serializable {
    //活动id
    @NotNull(message = "活动id不能为空")
    private Long activityId;
    //奖品id
    private Long prizeId;
}

4.3 WinningRecordsResult :controller层返回结果

com.yj.lottery_system.controller.result 包下:

java 复制代码
package com.yj.lottery_system.controller.result;

import lombok.Data;

import java.io.Serializable;
import java.util.Date;

@Data
public class WinningRecordsResult implements Serializable {
    //中奖者id
    private Long winnerId;
    //中奖者姓名
    private String winnerName;
    //奖品名
    private String prizeName;
    //奖品等级
    private String prizeTier;
    //获奖时间
    private Date  winningTime;
}

五、service层

5.1 定义接口

com/yj/lottery_system/service 包下 IDrawPrizeService.java 类:

java 复制代码
    /**
     * 获取中奖记录
     * @param param
     * @return
     */
    List<WinningRecordsDTO> getRecords(WinningRecordsParam param);

5.2 实现接口

com/yj/lottery_system/service/impl 包下:DrawPrizeServiceImpl.java 类

java 复制代码
/**
     * 获取中奖记录
     * @param param
     * @return
     */
    @Override
    public List<WinningRecordsDTO> getRecords(WinningRecordsParam param) {
        //查询Redis
        String key = null == param.getPrizeId()
                ? String.valueOf(param.getActivityId())
                : param.getActivityId()+"_"+param.getPrizeId();
        List<WinningRecodesDO> winningRecords = getWinningRecords(key);

        if(!CollectionUtils.isEmpty(winningRecords)) {
            return convertToWinningRecordsDTOList(winningRecords);
        }
        //没查到查库
        winningRecords = winningRecordMapper.selectByActivityIdOrPrizeId(param.getActivityId(), param.getPrizeId());

        if(CollectionUtils.isEmpty(winningRecords)) {
            log.info("查询中奖记录为空 param: {}",
                    JacksonUtil.writeValueAsString(param));
            return Arrays.asList();
        }
        //存放记录进Redis
        cacheWinningRecords(key,winningRecords,WINNING_RECORDS_TIMEOUT);
        return convertToWinningRecordsDTOList(winningRecords);
    }

    private List<WinningRecordsDTO> convertToWinningRecordsDTOList(List<WinningRecodesDO> winningRecords) {
        if(CollectionUtils.isEmpty(winningRecords)) {
            return Arrays.asList();
        }
        List<WinningRecordsDTO> winningRecordsDTOList = winningRecords.stream()
                .map(winningRecodesDO -> {
                    WinningRecordsDTO winningRecordsDTO = new WinningRecordsDTO();
                    winningRecordsDTO.setWinnerId(winningRecodesDO.getWinnerId());
                    winningRecordsDTO.setWinnerName(winningRecodesDO.getWinnerName());
                    winningRecordsDTO.setPrizeName(winningRecodesDO.getPrizeName());
                    winningRecordsDTO.setPrizeTier(ActivityPrizeTiresEnum.forName(winningRecodesDO.getPrizeTier()));
                    winningRecordsDTO.setWinningTime(winningRecodesDO.getWinningTime());
                    return winningRecordsDTO;
                }).collect(Collectors.toList());
        return winningRecordsDTOList;

    }

5.3 WinningRecordsDTO :service返回结果

com.yj.lottery_system.service.dto 包下:

java 复制代码
package com.yj.lottery_system.service.dto;

import com.yj.lottery_system.service.enums.ActivityPrizeTiresEnum;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;
@Data
public class WinningRecordsDTO implements Serializable {
    //中奖者id
    private Long winnerId;
    //中奖者姓名
    private String winnerName;
    //奖品名
    private String prizeName;
    //奖品等级
    private ActivityPrizeTiresEnum prizeTier;
    //获奖时间
    private Date winningTime;
}

六、dao层

com/yj/lottery_system/dao/mapper 包下:WinningRecordMapper.java类

java 复制代码
    /**
     *  根据活动id 或 奖品id 查询中奖记录
     * @param activityId
     * @param prizeId
     */
    @Select("<script>" +
            "select * from winning_record " +
            "where activity_id = #{activityId} " +
            "<if test=\"prizeId!=null\">" +
            "and prize_id = #{prizeId}" +
            "</if>" +
            "</script>")
    List<WinningRecodesDO> selectByActivityIdOrPrizeId(@Param("activityId")Long activityId, @Param("prizeId")Long prizeId);

七、前端

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <title>抽奖页面</title>
    <style type="text/css">
        * {
            margin: 0;
            padding: 0;
        }
        html, body {
            width: 100%;
            height: 100%;
        }
        body {
            text-align: center;
            background: url("./pic/bg.png") no-repeat;
            overflow: hidden;
            background-size: 100% 100%;
            font-weight: bold;
            color: #D40000;
        }
        #container {
            min-width: 1000px;
            min-height: 700px;
        }
        #title {
            font-size: 100px;
            margin-top: 80px;
        }
        #disc {
            font-size: 40px;
            margin: 10px 0;
        }
        #image {
            margin-top: 20px;
            max-height: 280px;
            border: 1px solid #E23540FF;
            border-radius: 20px;
        }
        #list {
            margin: 0 auto;
            max-width: 800px;
        }
        #list span {
            display: inline-block;
            width: 160px;
            font-size: 36px;
            margin-top: 8px;
        }
        .records-item {
            text-align: center;
            cursor: pointer;
            padding: 20px;
            transition: background-color 0.3s;
            font-size: 20px;
        }
        .opt-box{
            display: flex;
            align-items: center;
            justify-content: center;
            margin-top: 30px;
        }
        .opt-box .btn{
            width: 120px;
            height: 32px;
            display: flex;
            align-items: center;
            justify-content: center;
            border: 2px solid #D40000;
            margin-inline: 18px;
            cursor: pointer;
            border-radius: 4px;
            font-weight: normal;
            font-size: 15px;
        }
        .opt-box .btn:hover{
            background-color: #D40000;
            color: #fff;
        }
    </style>
</head>

<body>
<div id="container">
    <div id="title"></div>
    <div id="disc"></div>
    <img id="image" />
    <div id="list"></div>
    <div class="opt-box">
        <span class="btn pre-btn" onclick="previousStep()">查看上一奖项</span>
        <span class="btn next-btn" onclick="nextStep()">开始抽奖</span>
    </div>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>

    var params = new URLSearchParams(window.location.search);
    var activityId = params.get('activityId');
    var valid = params.get('valid');
    var userToken = localStorage.getItem("user_token");


    // 获取网页上通过id为disc、image和list的DOM元素的引用。
    var disc = document.getElementById('disc')
    var image = document.getElementById('image')
    var list = document.getElementById('list')

    // 奖品列表
    var steps = null
    // 抽到第几个了
    var step = 0
    // 人员列表
    var names = null
    // 正在做什么
    var state = ''

    // 等待DOM加载完成
    document.addEventListener('DOMContentLoaded', function() {
        var titleDiv = document.getElementById('title');
        var activityName = params.get('activityName');
        titleDiv.textContent = activityName;
    });

    function formatDate(dateString) {
        var date = new Date(dateString);
        var hour = ('0' + date.getHours()).slice(-2);
        var minute = ('0' + date.getMinutes()).slice(-2);
        var second = ('0' + date.getSeconds()).slice(-2);
        // 构建格式化的日期字符串
        return hour + ':' + minute + ':' + second;
    }

    // showPic函数用于显示图片和奖品信息。
    // 它将data.disc设置为disc元素的innerHTML,将data.image设置为image元素的src属性,然后显示图片并隐藏列表。
    function showPic(data) {
        disc.innerHTML = data.prizeTierName + ' ' + data.name + ' ' + data.prizeAmount + '份'
        image.src = data.imageUrl ? data.imageUrl : '/pic/defaultPrizeImg.png'
        image.style.display = 'inline'
        list.style.display = 'none'
        while (list.hasChildNodes()) {
            list.removeChild(list.firstChild)
        }
    }

    // 查询中奖记录
    function showRecords() {
        disc.innerHTML = "中奖名单"
        image.style.display = 'none'
        list.style.display = 'block'
        while (list.hasChildNodes()) {
            list.removeChild(list.firstChild)
        }
        $.ajax({
            url: '/winning-records/show',
            type: 'POST',
            dataType: 'json',
            contentType: 'application/json',
            data: JSON.stringify({activityId: activityId}),
            headers: {
                'user_token': userToken
            },
            success: function(result) {
                if (result.code != 200) {
                    alert("查询中奖记录失败!" + result.msg);
                } else {
                    var records = result.data
                    list.className = "records-item"
                    for (var i = 0; i < records.length; ++i) {
                        var item = document.createElement('div')
                        item.textContent = formatDate(records[i].winningTime) + ' '
                        + records[i].winnerName + ' '
                        + records[i].prizeName + ' '
                        + records[i].prizeTier + ' '
                        list.appendChild(item)
                    }

                    // 分享链接
                    // 检查是否已经有分享按钮,如果没有则创建
                    var optBox = document.querySelector('.opt-box');
                    var existingBtn = optBox.querySelector('.btn.copy-btn');
                    if (!existingBtn) {
                        var newBtn = document.createElement('span');
                        newBtn.className = 'btn copy-btn';
                        // 设置点击事件处理函数
                        newBtn.onclick = function() {
                            // 创建URL对象
                            var currentUrl = new URL(window.location);
                            // 创建URLSearchParams对象
                            var searchParams = currentUrl.searchParams;
                            // 更新参数
                            searchParams.delete('valid');
                            searchParams.append('valid', false);
                            // 防止分享再分享,拼接多个参数
                            searchParams.delete('hideButton');
                            searchParams.append('hideButton', true);
                            // 更新URL对象的搜索字符串
                            currentUrl.search = searchParams.toString();
                            // 获取当前页面的URL
                            var url = currentUrl;
                            // 执行复制操作
                            copyToClipboard(url);
                        };
                        // 设置span元素的文本内容
                        newBtn.textContent = '分享结果';
                        // 将新的span元素添加到.opt-box容器中
                        optBox.appendChild(newBtn);
                    }

                    // 是否隐藏按钮
                    var hideButton = params.get('hideButton');
                    if ((hideButton == null && valid === 'false')
                        || hideButton === 'true') {
                        // 隐藏所有元素
                        var buttons = document.querySelectorAll('.btn.pre-btn, .btn.next-btn');
                        buttons.forEach(function(button) {
                            button.style.display = 'none';
                        });
                    }
                }
            }
        });
    }

    // 复制当前地址
    function copyToClipboard(textToCopy) {
        // navigator clipboard 需要https等安全上下文
        if (navigator.clipboard && window.isSecureContext) {
            // navigator clipboard 向剪贴板写文本
            return navigator.clipboard.writeText(textToCopy)
                .then(() => {alert("链接已复制到剪贴板");});
        } else {
            // 创建text area
            let textArea = document.createElement("textarea");
            textArea.value = textToCopy;
            // 使text area不在viewport,同时设置不可见
            textArea.style.position = "absolute";
            textArea.style.opacity = 0;
            textArea.style.left = "-999999px";
            textArea.style.top = "-999999px";
            document.body.appendChild(textArea);
            textArea.focus();
            textArea.select();
            return new Promise((res, rej) => {
                // 执行复制命令并移除文本框
                document.execCommand('copy') ? res() : rej();
                textArea.remove();
                alert("链接已复制到剪贴板");
            });
        }
    }

    // showBlink函数用于显示闪烁的文字。
    // 它首先设置奖品信息,隐藏图片,显示列表,然后创建与data.count相等数量的span元素。
    function showBlink(data) {
        disc.innerHTML = data.prizeTierName + ' ' + data.name + ' ' + data.prizeAmount + '份'
        image.style.display = 'none'
        list.style.display = 'block'
        var spans = []
        for (var i = 0; i < data.prizeAmount; ++i) {
            var span = document.createElement('span')
            list.appendChild(span)
            spans.push(span)
        }

        // doBlink函数用于实现文字的闪烁效果,通过window.requestAnimationFrame来循环调用。
        function doBlink() {
            if (state == 'showBlink') {
                // Math.random() 生成一个 [0, 1) 范围内的伪随机数,
                // 所以表达式的结果将在 (-0.5, 0.5] 范围内,这会导致 names 数组的元素随机排序。
                names.sort(function(a, b) {
                    return 0.5 - Math.random()
                })
                // 只展示当前奖项个数的随机人名
                for (var i = 0; i < data.prizeAmount; ++i) {
                    spans[i].innerHTML = names[i].userName
                }
                window.requestAnimationFrame(doBlink)
            }
        }

      // 告诉浏览器------你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。
      window.requestAnimationFrame(doBlink)
    }

    // 通过后端展示奖品维度中奖者,主要是防止页面刷新
    function showWinnerListWithPrize(data) {
        $.ajax({
            url: '/winning-records/show',
            type: 'POST',
            dataType: 'json',
            contentType: 'application/json',
            headers: {
                'user_token': userToken
            },
            data: JSON.stringify({activityId: activityId, prizeId: data.prizeId}),
            success: function(result) {
                if (result.code != 200) {
                    alert("查询中奖记录失败!" + result.msg);
                } else {
                    var luckRecords = result.data
                    // 从names中移除幸运儿
                    var luck = []
                    // 获得需要删除的用户id
                    var needRemoves = luckRecords.map(function(luckRecord) {
                        return luckRecord.winnerId;
                    });
                    console.log("needRemoves" + JSON.stringify(needRemoves))
                    console.log("names1" + JSON.stringify(names))
                    // 从names中移除包含在luckRecords中的name对应的元素
                    for (var i = 0; i < names.length; i++) {
                        if (needRemoves.includes(names[i].userId)) {
                            var tmp = names.splice(i, 1)  // 移除当前项,并更新索引i,因为后面的项会向前移动
                            luck = luck.concat(tmp)
                            console.log("tmp" + JSON.stringify(tmp))
                            i--  // 减1以确保索引i仍然指向当前的项
                        }
                    }
                    console.log("names2" + JSON.stringify(names))
                    console.log("luck" + JSON.stringify(luck))
                    // 只存放当前奖项的人员信息,下次抽奖会被覆盖
                    data.list = luck.map(x => x)
                    for (var i = 0; i < data.list.length; ++i) {
                        var span = document.createElement('span')
                        span.innerHTML = data.list[i].userName
                        list.appendChild(span)
                    }
                }
            }
        });
    }

    // showList函数用于显示静态的文字列表。它设置奖品信息,隐藏图片,显示列表,并将data.list中的数据添加到列表中。
    function showList(data) {
        disc.innerHTML = data.prizeTierName + ' ' + data.name + ' ' + data.prizeAmount + '份'
        image.style.display = 'none'
        list.style.display = 'block'
        while (list.hasChildNodes()) {
            list.removeChild(list.firstChild)
        }

        // data.list是前端保存的临时数据,若当前抽奖页刷新,则数据丢失
        // 因此判断一下是否为空,为空需要查询后端
        data.list = data.list || []
        if (data.list.length > 0) {
            for (var i = 0; i < data.list.length; ++i) {
                var span = document.createElement('span')
                span.innerHTML = data.list[i].userName
                list.appendChild(span)
            }
        } else {
            showWinnerListWithPrize(data)
        }
    }

    // 这是改变next-btn按钮文本的函数
    function changeNextButtonText(text) {
        var nextButton = document.querySelector('.btn.next-btn');
        nextButton.textContent = text;
    }

    function saveLuck() {
        var data = steps[step]

        var luckUserList = []
        for (var i = 0; i < data.list.length; ++i) {
            luckUserList.push({
                userId: data.list[i].userId,
                userName: data.list[i].userName
            })
        }

        var drawPrizesData = {
            activityId: activityId,
            prizeId: data.prizeId,
            winningTime: new Date(),
            winnerList: luckUserList
        };

        // 发送AJAX请求
        $.ajax({
            url: '/draw-prize',
            type: 'POST',
            dataType: 'json',
            contentType: 'application/json',
            headers: {
                'user_token': userToken
            },
            data: JSON.stringify(drawPrizesData),
            success: function(result) {
                if (result.code != 200) {
                    alert("抽奖失败!" + result.msg);
                }
            },
            error:function(err){
                console.log(err);
                if(err!=null && err.status==401){
                    alert("用户未登录, 即将跳转到登录页!");
                    // 已经被拦截器拦截了, 未登录
                    location.href ="/blogin.html";
                }
            }
        });
    }

    // nextStep函数用于根据当前状态显示下一个步骤的内容。
    // 它根据state的值和data.list的长度来决定是显示图片、闪烁文字还是静态文字列表,并更新step和state。
    function nextStep() {
        var data = steps[step]
        console.log("steps[" + step + "]:" + JSON.stringify(data))
        if (state == 'showPic') {
            if (data.valid) {
                // 还没抽:抽奖
                state = 'showBlink'
                showBlink(data)
                changeNextButtonText('点我确定')
            } else {
                // 抽过了:展示
                state = 'showList'
                showList(data)
                changeNextButtonText('已抽完,下一步')
            }
        } else if (state == 'showBlink') {
            // 从names中移除幸运儿,并将幸运儿存放起来
            var luck = names.splice(0, data.prizeAmount)
            // 只存放当前奖项的人员信息,下次抽奖会被覆盖
            data.list = luck.map(x => x)
            console.log("data.list:" + JSON.stringify(data.list))
            // 调用后端抽奖接口,保存中奖信息
            saveLuck()
            data.valid = false
            state = 'showList'
            showList(data)
            changeNextButtonText('已抽完,下一步')
        } else if (state == 'showList') {
            if (step < (steps.length - 1)) {
                ++step
                state = ''
                nextStep()
                changeNextButtonText('开始抽奖')
            } else {
                // 抽奖结束,展示全量中奖列表
                if (step == (steps.length - 1)) {
                    ++step
                }
                showRecords()
                changeNextButtonText('已全部抽完')
            }
        } else {
            state = 'showPic'
            showPic(data)
            changeNextButtonText('开始抽奖')
        }
    }

    // previousStep函数用于返回到上一个步骤。如果当前步骤不是第一个步骤,则减少step的值,然后调用nextStep函数。
    function previousStep() {
        if (step > 0) {
            --step
        }
        state = ''
        nextStep()
    }

    // 抽奖页面加载逻辑
    function drawPage() {
        // 判断活动是否有效:进行中
        if (valid == "true") {
            // 判断是用户还是管理员,只有管理员有权限抽奖
            var identity = localStorage.getItem("user_identity");
            if (identity == "ADMIN") {
                // 在页面加载完成后,调用reloadConf函数并传入nextStep作为回调函数,以初始化页面显示。
                reloadConf(nextStep)
            } else {
                alert("抽奖未结束,由于您不是管理员,无权限限参与抽奖。请耐心等待抽奖结束后查看中奖名单");
            }
        } else {
            showRecords()
        }
    }

    // reloadConf函数用于重新加载配置文件。
    // 如果提供了回调函数func,则在文件读取完成后调用它。
    function reloadConf(func) {
        // 发送AJAX请求
        $.ajax({
            url: '/activity-detail/find',
            type: 'GET',
            data: { activityId: activityId },
            dataType: 'json',
            headers: {
                'user_token': userToken
            },
            success: function(result) {
                if (result.code != 200) {
                    alert("获取活动详情失败!" + result.msg);
                } else {
                    names = result.data.users;
                    steps = result.data.prizes;
                    if (func) func();
                }
            },
            error:function(err){
                console.log(err);
                if(err!=null && err.status==401){
                    alert("用户未登录, 即将跳转到登录页!");
                    // 已经被拦截器拦截了, 未登录
                    location.href ="/blogin.html";
                }
            }
        });
    }

    // 抽奖页起始逻辑
    drawPage()
</script>
</body>
</html>
相关推荐
计算机徐师兄6 小时前
Java基于微信小程序的物流管理系统【附源码、文档说明】
java·微信小程序·物流管理系统·java物流管理系统小程序·物流管理系统小程序·物流管理系统微信小程序·java物流管理系统微信小程序
青云交6 小时前
Java 大视界 -- Java 大数据机器学习模型在金融风险管理体系构建与风险防范能力提升中的应用(435)
java·大数据·机器学习·spark·模型可解释性·金融风控·实时风控
跟‘码’死磕7 小时前
springboot集成钉钉群内发送消息
java·spring boot·钉钉
0和1的舞者7 小时前
SpringBoot配置文件
java·spring boot·后端·web·配置·spirng
cike_y7 小时前
JavaWeb之过滤器Filter&监听器
java·servlet·javaweb
多则惑少则明7 小时前
SpringAI框架接入-jdk升级21后报错“run failed: Unsupported class file major version 65”
java·后端·spring·springai
uup7 小时前
线程池中任务堆积与饥饿死锁问题
java
deng-c-f7 小时前
C/C++内置库函数(2):智能指针
java·c语言·c++
毕设源码-朱学姐7 小时前
【开题答辩全过程】以 基于SSM框架的餐厅点餐系统的设计与实现为例,包含答辩的问题和答案
java·eclipse