当前位置: 首页 > news >正文

抽奖系统(3——奖品模块)

1. 图片上传

application.properties 配置上传文件路径

## 文件上传 ##
# 目标路径
pic.local-path=D:/PIC
# spring boot3 升级配置名
spring.web.resources.static-locations=classpath:/static/,file:${pic.local-path}

tip:

1. 如果访问的是本地路径,需要添加配置项::spring.web.resources.static-locations(springboot 3 升级过配置名,原本为 spring.resources.static-locations)

2. 访问静态资源不做拦截:AppConfig 类中 excludes 新增 "/*.jpg" 或者 "/*.png"

图片服务 PictureService 接口定义

package com.example.lotterysystem.service;import org.springframework.web.multipart.MultipartFile;public interface PictureService {/*** 保存图片** @param multipartFile:上传文件的工具类* 后面上传完图片后,需要将图片保存到项目中,因此需要一个索引来找到图片* @return 返回值 String 就是索引:上传后的文件名(唯一)*/String savePicture(MultipartFile multipartFile);
}

接口实现 PictureServiceImpl

package com.example.lotterysystem.service.impl;import com.example.lotterysystem.common.errorcode.ServiceErrorCodeConstants;
import com.example.lotterysystem.common.exception.ServiceException;
import com.example.lotterysystem.service.PictureService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.IOException;
import java.util.UUID;@Component
public class PictureServiceImpl implements PictureService {@Value("${pic.local-path}")private String localPath;@Overridepublic String savePicture(MultipartFile multipartFile) {// 创建目录File dir = new File(localPath);if (!dir.exists()) {dir.mkdirs();}// 创建索引 aaa.jpg -> xxx.jpg// 第一步:需要拿到当前文件的全名称,如:aaa.jpgString filename = multipartFile.getOriginalFilename();assert filename != null;// 第二步:拿到当前文件的后缀名,如:.jpgString suffix = filename.substring(filename.lastIndexOf("."));// 第三步:生成索引,如:xxx// 第四步:拼接 xxx.jpg(拼接为自己管控的文件名)filename = UUID.randomUUID() + suffix;// 图片保存try {// transferTo 方法将创建好的 multipartFile 保存到自定义的目录(localPath + "/" + filename)中multipartFile.transferTo(new File(localPath + "/" + filename));} catch (IOException e) {throw new ServiceException(ServiceErrorCodeConstants.PIC_UPLOAD_ERROR);}return filename;}
}

Postman 测试

在 D:\pic 目录下就能看到这个上传的图片了

2. 创建奖品

时序图

约定前后端交互接口

[请求] /prize/create  POST

 param: {"prizeName":"吹风机","description":"吹风机","price":100}
 prizePic: Obj-C.jpg  (FILE)

[响应]

{
    "code": 200,
    "data": 17,
    "msg": ""
}

Controller 层接口设计

package com.example.lotterysystem.controller;import com.example.lotterysystem.common.pojo.CommonResult;
import com.example.lotterysystem.common.utils.JacksonUtil;
import com.example.lotterysystem.controller.param.CreatePrizeParam;
import com.example.lotterysystem.service.PictureService;
import com.example.lotterysystem.service.PrizeService;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;@RestController
public class PrizeController {private static final Logger logger = LoggerFactory.getLogger(PrizeController.class);@Autowiredprivate PictureService pictureService;@Autowiredprivate PrizeService prizeService;@RequestMapping("/pic/upload")public String uploadPic(MultipartFile file) {return pictureService.savePicture(file);}/*** 创建奖品* @RequestPart:用于接受表单数据的 multipart/form-data** @param param* @param picFile* @return*/@RequestMapping("/prize/create")public CommonResult<Long> createPrizeParam(@Valid @RequestPart("param") CreatePrizeParam param,@RequestPart("prizePic") MultipartFile picFile) {logger.info("createPrizeParam CreatePrizeParam:{}", JacksonUtil.writeValueAsString(param));return CommonResult.success(prizeService.createPrize(param, picFile));}
}

CreatePrizeParam

package com.example.lotterysystem.controller.param;import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;import java.io.Serializable;
import java.math.BigDecimal;@Data
public class CreatePrizeParam implements Serializable {// 奖品名称@NotBlank(message = "奖品名称不能为空!")private String prizeName;// 奖品描述private String description;// 奖品价格@NotNull(message = "奖品价格不能为空!")private BigDecimal price;
}

Service 层接口设计

package com.example.lotterysystem.service;import com.example.lotterysystem.controller.param.CreatePrizeParam;
import org.springframework.web.multipart.MultipartFile;public interface PrizeService {/*** 创建单个奖品** @param param 奖品属性* @param picFile 上传的奖品图片* @return 奖品id*/Long createPrize(CreatePrizeParam param, MultipartFile picFile);
}

接口实现

package com.example.lotterysystem.service.impl;import com.example.lotterysystem.common.errorcode.ServiceErrorCodeConstants;
import com.example.lotterysystem.common.exception.ServiceException;
import com.example.lotterysystem.service.PictureService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.IOException;
import java.util.UUID;@Component
public class PictureServiceImpl implements PictureService {@Value("${pic.local-path}")private String localPath;@Overridepublic String savePicture(MultipartFile multipartFile) {// 创建目录File dir = new File(localPath);if (!dir.exists()) {dir.mkdirs();}// 创建索引 aaa.jpg -> xxx.jpg// 第一步:需要拿到当前文件的全名称,如:aaa.jpgString filename = multipartFile.getOriginalFilename();assert filename != null;// 第二步:拿到当前文件的后缀名,如:.jpgString suffix = filename.substring(filename.lastIndexOf("."));// 第三步:生成索引,如:xxx// 第四步:拼接 xxx.jpg(拼接为自己管控的文件名)filename = UUID.randomUUID() + suffix;// 图片保存try {// transferTo 方法将创建好的 multipartFile 保存到自定义的目录(localPath + "/" + filename)中multipartFile.transferTo(new File(localPath + "/" + filename));} catch (IOException e) {throw new ServiceException(ServiceErrorCodeConstants.PIC_UPLOAD_ERROR);}return filename;}
}

Dao 层接口设计

package com.example.lotterysystem.dao.mapper;import com.example.lotterysystem.dao.dataobject.PrizeDO;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;@Mapper
public interface PrizeMapper {@Insert("insert into prize (name, image_url, price, description)" +" values (#{name}, #{imageUrl}, #{price}, #{description})")@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")int insert(PrizeDO prizeDO);
}

PrizeDO

package com.example.lotterysystem.dao.dataobject;import lombok.Data;
import lombok.EqualsAndHashCode;import java.math.BigDecimal;@Data
@EqualsAndHashCode(callSuper = true)
public class PrizeDO extends BaseDO{// 奖品名private String name;// 图片索引private String imageUrl;// 价格private BigDecimal price;// 描述private String description;
}

奖品上传测试

可能会出现下面的问题:

1. 被拦截器拦截

在拦截器中设置忽略掉 "/prize/create" 即可,如下:

2. param 转 json 错误 

可能的原因:根据报错提示,可能是 param 转为 json 的方式不是很好(序列化问题)

解决方法:通过一个转换器来进行转换

转换器代码:

package com.example.lotterysystem.common.converter;import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.stereotype.Component;import java.lang.reflect.Type;@Component
public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {protected MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) {// MediaType.APPLICATION_OCTET_STREAM 表示这个转换器用于处理二进制流数据,通常用于文件上传。super(objectMapper, MediaType.APPLICATION_OCTET_STREAM);}@Overridepublic boolean canWrite(Class<?> clazz, MediaType mediaType) {// 转换器不用于写入(即不用于响应的序列化)return false;}@Overridepublic boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {return false;}@Overrideprotected boolean canWrite(MediaType mediaType) {return false;}
}

再次运行:

奖品创建页面前端实现

// create-prizes.html<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>创建奖品</title><link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/bootstrap/4.5.2/css/bootstrap.min.css"><link rel="stylesheet" href="./css/base.css"></link><style>body {font-family: Arial, sans-serif;background-color: white;margin: 0;padding: 0;}.container {max-width: 800px;margin: 10px auto;padding: 20px;background-color: #fff;/* box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); */}.form-group {margin-bottom: 15px;}.form-group label {display: block;margin-bottom: 5px;}.form-group input[type=text],.form-group input[type=url] {width: 100%;padding: 10px;border: 1px solid #ccc;border-radius: 4px;}.form-group textarea {width: 100%;height: 100px;padding: 10px;border: 1px solid #ccc;border-radius: 4px;}.btn {border: none;border-radius: 6px;cursor: pointer;width: 140px;height: 48px;display: flex;align-items: center;justify-content: center;margin: 0 auto;}.prize-row {margin-bottom: 10px;}.container h2{font-weight: 600;font-size: 30px;letter-spacing: 1px;color: #000000;line-height: 50px;text-align: center;margin-bottom: 40px;}.upload-box{display: flex;width: 150px;height: 150px;flex-direction: column;justify-content: center;align-items: center;border: 1px dashed  #4c8dfb;background-color: #EDF3FE;cursor: pointer;margin-bottom: 10px;position: relative;border-radius: 8px;overflow: hidden;}.plus{color: #4C8DFB;font-size: 30px;}.up-txt{font-weight: 400;font-size: 14px;color: #4C8DFB;}.upload-box .form-control{position: absolute;top: 0;left: 0;height: 100%;width: 100%;opacity: 0;z-index: 2;}.preview-img{width: 150px;height: 150px;position: absolute;top: 0;left: 0;z-index: 1;display: none;background-color: #fff;object-fit: scale-down;}</style>
</head>
<body>
<div class="container"><h2>开始创建奖品</h2><div id="prizeRows"><div class="prize-row"><div class="form-group"><label for="prizeName">奖品名称</label><input class="form-control" type="text" placeholder="请输入奖品名称" id="prizeName" name="prizeName" class="prize-name" required></div><div class="form-group"><label for="prizeName">奖品图片</label><div class="img-upload"><!-- 上传图片的框 --><div class="upload-box"><span class="plus">+</span><span class="up-txt">上传图片</span><!-- 预览图 --><img src="./pic/bg.png" class="preview-img" id="previewImg" alt="" srcset=""><input class="form-control" type="file" onchange="showImg()" id="prizeImageUrl" ></div></div></div><div class="form-group"><label for="price">奖品价格</label><input class="form-control" placeholder="请输入奖品价格" type="number" id="price" name="price" class="prize-price" required></div><div class="form-group"><label for="description">奖品描述</label><textarea class="form-control" id="description" placeholder="请输入奖品描述"  name="description" class="prize-description" required></textarea></div></div></div><button type="submit" class="btn btn-primary" onclick="submitPrizes()">创建奖品</button>
</div><script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>var userToken = localStorage.getItem("user_token");var formData = new FormData();// 提交奖品数据的函数function submitPrizes() {var prizeName = $('#prizeName').val();var description = $('#description').val();var price = $('#price').val();formData.append("param", JSON.stringify({prizeName:prizeName,description:description,price:price,}));// 发送POST请求到后端接口$.ajax({url: '/prize/create',type: 'POST',// 不设置Content-Type请求头,因为数据将通过其他方式传递,例如通过FormData。contentType : false,// 不要处理发送的数据processData : false,headers: {// jwt"user_token": userToken},data: formData,success: function(result) {if(result.code == 200) {alert('奖品创建成功!');} else {alert('创建奖品失败:' + result.msg);}},error:function(err){console.log(err);if(err!=null && err.status==401){alert("用户未登录, 即将跳转到登录页!");// 跳转登录页window.location.href = "/blogin.html";window.parent.location.href = "/blogin.html";//让父页面一起跳转}}});}// 上传图片:读取用户通过文件输入选择的图片文件,并将其显示在一个网页元素中function showImg(){var file =  document.getElementById("prizeImageUrl").files[0];formData.append("prizePic",file);var r= new FileReader();result = r.readAsDataURL(file);r.onload=function (e) {console.log(e)$('#previewImg').attr("src", e.target.result).show();}}</script>
</body>
</html>

测试:

创建奖品,上传图片成功,创建成功

3. 奖品列表展示(翻页)

时序图

约定前后端交互接口

[请求]  /prize/find-list?currentPage=1&pageSize=10  GET

[响应]

{

    "code": 200,

    "data": {

        "total": 2,

        "records": [

            {

                "prizeId": 2,

                "prizeName": "吹风机",

                "description": "吹风机",

                "price": 500.00,

                "imageUrl": "0a767457-513d-4b02-bdb4-fbf7f57b5b8c.png"

            },

            {

                "prizeId": 1,

                "prizeName": "手机",

                "description": "手机",

                "price": 5000.00,

                "imageUrl": "9f1739c0-3b5f-4517-999b-8626cfe4b5f6.png"

            }

        ]

    },

    "msg": ""

}

Controller 层接口设计

// package com.example.lotterysystem.controller;// 查询奖品列表@RequestMapping("/prize/find-list")public CommonResult<FindPrizeListResult> findPrizeList(PageParam param) {logger.info("findPrizeList PageParam:{}", JacksonUtil.writeValueAsString(param));PageListDTO<PrizeDTO> pageListDTO = prizeService.findPrizeList(param);return CommonResult.success(converToFindPrizeListResult(pageListDTO));}private FindPrizeListResult converToFindPrizeListResult(PageListDTO<PrizeDTO> pageListDTO) {if (null == pageListDTO) {throw  new ControllerException(ControllerErrorCodeConstants.FIND_PRIZE_LIST_ERROR);}FindPrizeListResult result = new FindPrizeListResult();result.setTotal(pageListDTO.getTotal());result.setRecords(pageListDTO.getRecords().stream().map(prizeDTO -> {FindPrizeListResult.PrizeInfo prizeInfo = new FindPrizeListResult.PrizeInfo();prizeInfo.setPrizeId(prizeDTO.getPrizeId());prizeInfo.setPrizeName(prizeDTO.getName());prizeInfo.setDescription(prizeDTO.getDescription());prizeInfo.setImageUrl(prizeDTO.getImageUrl());prizeInfo.setPrice(prizeDTO.getPrice());return prizeInfo;}).collect(Collectors.toList()));return result;}

PageParam

package com.example.lotterysystem.controller.param;import lombok.Data;@Data
public class PageParam {// 当前页(默认从第一页开始)private Integer currentPage = 1;// 当前页数量(默认显示10条数据)private Integer pageSize = 10;// 获取偏移量public Integer offset() {// 如果当前是第一页,1-1=0,0*10=0,就是从数据库中第0个数据开始// 如果当前是第二页,2-1=1,1*10=10,就是从数据库中第10个数据开始return (currentPage - 1) * pageSize;}
}

FindPrizeListResult

package com.example.lotterysystem.controller.result;import lombok.Data;import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;@Data
public class FindPrizeListResult {// 奖品总量private Integer total;// 当前列表private List<PrizeInfo> records;@Datapublic static class PrizeInfo implements Serializable {// 奖品idprivate Long prizeId;// 奖品名称private String prizeName;// 奖品描述private String description;// 奖品价值private BigDecimal price;// 奖品图片private String imageUrl;}
}

Service 层接口设计

// package com.example.lotterysystem.service;/*** 翻页查询列表* 带有翻页功能的通用接口* 所有带有翻页的列表,如活动列表、奖品列表等,都可以调用该接口** @param param* @return*/PageListDTO<PrizeDTO> findPrizeList(PageParam param);

接口实现

// package com.example.lotterysystem.service.impl;@Overridepublic PageListDTO<PrizeDTO> findPrizeList(PageParam param) {// 总量int total = prizeMapper.count();// 查询当前页列表List<PrizeDTO> prizeDTOList = new ArrayList<>();List<PrizeDO> prizeDOList = prizeMapper.selectPrizeList(param.offset(), param.getPageSize());for (PrizeDO prizeDO : prizeDOList) {PrizeDTO prizeDTO = new PrizeDTO();prizeDTO.setPrizeId(prizeDO.getId());prizeDTO.setName(prizeDO.getName());prizeDTO.setDescription(prizeDO.getDescription());prizeDTO.setImageUrl(prizeDO.getImageUrl());prizeDTO.setPrice(prizeDO.getPrice());prizeDTOList.add(prizeDTO);}return new PageListDTO<>(total, prizeDTOList);}

PageListDTO<T>

package com.example.lotterysystem.service.dto;import lombok.Data;import java.util.List;@Data
public class PageListDTO<T> {// 奖品总量private Integer total;// 当前页列表private List<T> records;public PageListDTO(Integer total, List<T> records) {this.total = total;this.records = records;}
}

PrizeDTO

package com.example.lotterysystem.service.dto;import lombok.Data;import java.math.BigDecimal;@Data
public class PrizeDTO {// 奖品idprivate Long prizeId;// 奖品名private String name;// 图片索引private String imageUrl;// 价格private BigDecimal price;// 描述private String description;
}

Dao 层接口设计

// package com.example.lotterysystem.dao.mapper;// 查询总量@Select("select count(1) from prize")int count();/*** 先根据 id 进行降序排序,然后根据偏移量获取数据库中对应大小的数据* 通过 list 返回** @param offset* @param pageSize* @return*/@Select("select * from prize order by id desc limit #{offset}, #{pageSize}")List<PrizeDO> selectPrizeList(Integer offset, Integer pageSize);

ControllerErrorCodeConstants

// package com.example.lotterysystem.common.errorcode;// ------ 奖品模块错误码 ------ErrorCode FIND_PRIZE_LIST_ERROR = new ErrorCode(200, "查询奖品列表失败!");

Postman 测试

拦截器忽略该路径:

奖品列表页面前端实现

// prizes-list.html<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>奖品列表</title><link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/bootstrap/4.5.2/css/bootstrap.min.css"><link rel="stylesheet" href="./css/base.css"></link><style>body {font-family: Arial, sans-serif;padding: 0 30px;}table {/* height: calc(100vh - 143px); */height: 300px;}.table td, .table th{border: none;}.table thead th{background-color: #f7f7f7;border-bottom: none;font-weight: 600;font-size: 16px;color: #999999;height: 80px;line-height: 80px;padding: 0;position: sticky;top: 0px;}.table tbody+tbody{border-top: none;}.prize-table {width: 100%; /* 表格宽度为容器宽度的100% */height: 100vh;overflow-y: auto;}.prize-table h2{background: #fff;width: 100%;font-weight: 600;font-size: 18px;color: #000000;height: 70px;display: flex;align-items: center;margin-bottom: 0;}.table-box{height: calc(100vh - 140px);overflow-y: auto;}.prize-table th, .prize-table td {text-align: center;vertical-align: middle;padding: 10px;height: 50px; /* 统一设置表头和单元格的高度 */}/* 第一列和第三列的样式 */.prize-table th:first-child, .prize-table td:first-child,.prize-table th:nth-child(3), .prize-table td:nth-child(3),.prize-table th:nth-child(5), .prize-table td:nth-child(5) {width: 15%; /* 设置第一列和第三列的宽度为15% */}/* 第四列的样式 */.prize-table th:nth-child(4), .prize-table td:nth-child(4) {width: 30%; /* 设置第四列的宽度为30% */}/* 其他列的样式(例如第二列和第五列) */.prize-table th:nth-child(2), .prize-table td:nth-child(2) {width: 25%; /* 保持第二列和第五列的宽度为20% */}.prize-table th {/* 如果有特定的样式只适用于表头,可以在这里添加 */}.prize-table td {height: 100px; /* 特定于单元格的高度设置 */overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width: 300px;}.prize-table img{width: 76px;height: 76px;border-radius: 8px;object-fit: scale-down;border:1px solid #e4e4e4;}.pagination {display: flex;justify-content: flex-end;margin-top: 18px;padding-right: 16px;}.pagination button {margin: 0 5px; /* 按钮之间的间距保持不变 */border-radius: 5px; /* 设置圆角为20像素,可以根据需要调整 */border: 1px solid #007bff;background-color: #fff;padding: 0px 8px; /* 可以添加一些内边距,使按钮看起来更饱满 */cursor: pointer; /* 将鼠标光标改为指针形状,提升用户体验 */font-size: 13px;}.pagination span{margin: 0 10px;font-size: 14px;}.pagination input{width: 80px;text-align: center;}</style>
</head>
<body>
<div class=" prize-table"><h2>奖品列表</h2><div class="table-box"><table class="table"><thead><tr><th>奖品id</th><th>奖品图</th><th>奖品名</th><th>奖品描述</th><th>奖品价值</th></tr></thead><tbody><tbody id="prizeList"><!-- 奖品列表将动态插入这里 --></tbody></tbody></table></div><div class="pagination"><button class="btn-outline-primary" onclick="fetchPrizes(1)">首页</button><button class="btn-outline-primary" onclick="previousPage()">上一页</button><span>第 <input type="number" id="pageInput" min="1" value="1" /> 页</span><button class="btn-outline-primary" onclick="nextPage()">下一页</button><button class="btn-outline-primary" onclick="fetchPrizes(totalPages)">尾页</button></div>
</div><script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>var userToken = localStorage.getItem("user_token");var currentPage = 1;var pageSize = 10;var totalPages;// 发送AJAX请求的函数function fetchPrizes(page) {console.log(page);// 如果页码小于1,则重置为1if (page < 1) {page = 1;}// 更新当前页码currentPage = page;// 构建要发送的数据对象var dataToSend = {currentPage: currentPage,pageSize: pageSize};// 发送AJAX请求$.ajax({url: '/prize/find-list',type: 'GET',data: dataToSend, // 将分页参数作为请求数据发送headers: {// jwt"user_token": userToken},dataType: 'json', // 期望从服务器接收的数据类型是JSONsuccess: function(result) {if (result.code != 200) {alert("查询奖品列表失败!" + result.msg);} else {var prizes = result.data.records;var prizesHtml = '';// 清空现有的表格内容var prizeList = $('#prizeList');prizeList.empty();prizes.forEach(function(prize) {var imageUrl = prize.imageUrl ? prize.imageUrl : '/pic/defaultPrizeImg.png';prizeList.append('<tr> ' +'<td>' + prize.prizeId + '</td>' +'<td><img src="' + imageUrl + '" alt="' + prize.prizeName + '" class="prize-image"></td>' +'<td>' + prize.prizeName + '</td>' +'<td>' + prize.description + '</td>' +'<td>' + prize.price + '元</td>' +'</tr>');});// 更新分页控件的总页数totalPages = Math.ceil(result.data.total / pageSize);// 更新输入框的值$('#pageInput').val(currentPage);} // else end},error:function(err){console.log(err);if(err!=null && err.status==401){alert("用户未登录, 即将跳转到登录页!");// 跳转登录页window.location.href = "/blogin.html";window.parent.location.href = "/blogin.html";//让父页面一起跳转}}});}function previousPage() {if (currentPage > 1) {fetchPrizes(currentPage - 1);} else {alert("已经是第一页");}}function nextPage() {if (currentPage < totalPages) {fetchPrizes(currentPage + 1);} else {alert("已经是最后一页");}}$(document).ready(function() {fetchPrizes(1);});// 绑定输入框回车事件$('#pageInput').on('keypress', function(e) {if (e.key === 'Enter') {var page = parseInt(this.value, 10);if (!isNaN(page) && page >= 1 && page <= totalPages) {fetchPrizes(page);}}});</script>
</body>
</html>

测试:

点击首页跳转、点击尾页跳转、输入页面回车跳转、在首页点击上一页提示已经是第一页

相关文章:

抽奖系统(3——奖品模块)

1. 图片上传 application.properties 配置上传文件路径 ## 文件上传 ## # 目标路径 pic.local-pathD:/PIC # spring boot3 升级配置名 spring.web.resources.static-locationsclasspath:/static/,file:${pic.local-path} tip&#xff1a; 1. 如果访问的是本地路径&#xff0c…...

36.centos7上安装python3.6.5、安装卸载依赖包

查看openssl的版本号&#xff0c;默认python3.6.5需要OpenSSL 1.0.2以上的版本支持。 监测安装好的python,是否可以正确导入ssl和_ssl包 pip3安装依赖包 通过Pycharm工具导出requirements.txt文件 查看/usr/bin/目录下的软连接 pip3, python...

微透镜阵列精准全检,白光干涉3D自动量测方案提效70%

广泛应用的微透镜阵列 微透镜是一种常见的微光学元件&#xff0c;通过设计微透镜&#xff0c;可对入射光进行扩散、光束整形、光线均分、光学聚焦、集成成像等调制&#xff0c;进而实现许多传统光学元器件难以实现的特殊功能。 微透镜阵列&#xff08;Microlens Array&#x…...

nature genetics | scATAC-seq预测scRNA-seq,识别影响基因表达的新染色质区域

–https://doi.org/10.1038/s41588-024-01689-8 Single-cell multi-ome regression models identify functional and disease-associated enhancers and enable chromatin potential analysis 研究团队和单位 Christina S. Leslie–Memorial Sloan Kettering Cancer Center …...

简述mysql 主从复制原理及其工作过程,配置一主两从并验证。

MySQL 主从同步是一种数据库复制技术&#xff0c;它通过将主服务器上的数据更改复制到一个或多个从服务器&#xff0c;实现数据的自动同步。 主从同步的核心原理是将主服务器上的二进制日志复制到从服务器&#xff0c;并在从服务器上执行这些日志中的操作。 MySQL主从同步是基…...

Java API:封装自定义响应类

本文介绍 Web 服务开发中自定义响应&#xff0c;涵盖标准 HTTP 响应状态码局限性、自定义响应价值、设计原则与实现、在 Spring Boot 项目应用、与其他响应格式对比总结及应用场景。 1. 标准HTTP响应与自定义响应 1.1标准HTTP响应状态码 在 Web 服务开发中&#xff0c;HTTP…...

【Unity3D】利用Hinge Joint 2D组件制作绳索效果

目录 一、动态绳索 &#xff08;可移动根节点&#xff09; 二、静态绳索 三、利用Skinning Editor(Unity2022.3.15f1正常使用) 四、注意事项 一、动态绳索 &#xff08;可移动根节点&#xff09; 动态绳索 DynamicRope空物体 Anchor和whitecircle是相同位置的物体&#xff…...

vim练级攻略(精简版)

vim推荐配置: curl -sLf https://gitee.com/HGtz2222/VimForCpp/raw/master/install.sh -o ./install.sh && bash ./install.sh 0. 规定 Ctrl-λ 等价于 <C-λ> :command 等价于 :command <回车> n 等价于 数字 blank字符 等价于 空格&#xff0c;tab&am…...

嵌入式硬件篇---PID控制

文章目录 前言第一部分&#xff1a;连续PID1.比例&#xff08;Proportional&#xff0c;P&#xff09;控制2.积分&#xff08;Integral&#xff0c;I&#xff09;控制3.微分&#xff08;Derivative&#xff0c;D&#xff09;控制4.PID的工作原理5..实质6.分析7.各种PID控制器P控…...

技术洞察:C++在后端开发中的前沿趋势与社会影响

文章目录 引言C在后端开发中的前沿趋势1. 高性能计算的需求2. 微服务架构的兴起3. 跨平台开发的便利性 跨领域技术融合与创新实践1. C与人工智能的结合2. C与区块链技术的融合 C对社会与人文的影响1. 提升生产力与创新能力2. 促进技术教育与人才培养3. 技术与人文的深度融合 结…...

C语言程序设计之小系统

&#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 目录 系统说明 1.1 系统概述 1.2 功能模块总体设计详细设计 3.1 程序中使用的函数 3.2各类问…...

pyinstaller : 无法将“pyinstaller”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。

pyinstaller : 无法将“pyinstaller”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确保路径正确&#xff0c;然后再试一次。 所在位置 行:1 字符: 1pyinstaller --onefile --windowed 过年烟花.py~~~~~~~~~~~ …...

接口传参 data格式和json格式区别是什么

接口传参 data格式和json格式区别是什么 以下是接口传参 data 格式和 JSON 格式的区别&#xff1a; 定义和范围 Data 格式&#xff1a; 是一个较为宽泛的概念&#xff0c;它可以指代接口传递参数时所使用的任何数据的组织形式。包括但不限于 JSON、XML、Form 数据、纯文本、二进…...

ClickHouse 入门

简介 ClickHouse 是一个列式数据库&#xff0c;传统的数据库一般是按行存储&#xff0c;而ClickHouse则是按列存储&#xff0c;每一列都有自己的存储空间&#xff0c;并且只存储该列的数值&#xff0c;而不是存储整行的数据。这样做主要有几个好处&#xff0c;压缩率高&#x…...

Python自动化:基于faker批量生成模拟数据(以电商行业销售数据为例)

引言&#xff1a;个人认为&#xff0c;“造数据”是一个数据分析师的一项基本技能&#xff0c;当然啦&#xff0c;“造数据”不是说胡编乱造&#xff0c;而是根据自己的需求去构造一些模拟数据集&#xff0c;用于测试等用途&#xff0c;而且使用虚拟数据不用担心数据隐私和安全…...

3.3 OpenAI GPT-4, GPT-3.5, GPT-3 模型调用:开发者指南

OpenAI GPT-4, GPT-3.5, GPT-3 模型调用:开发者指南 OpenAI 的 GPT 系列语言模型,包括 GPT-4、GPT-3.5 和 GPT-3,已经成为自然语言处理领域的标杆。无论是文本生成、对话系统,还是自动化任务,开发者都可以通过 API 调用这些强大的模型来增强他们的应用。本文将为您详细介…...

【Spring Boot】掌握 Spring 事务:隔离级别与传播机制解读与应用

前言 &#x1f31f;&#x1f31f;本期讲解关于spring 事务传播机制介绍~~~ &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 &#x1f525; 你的点赞就是小编不断更新的最大动力 &#x1f386;那么废话…...

力扣203题—— 移除链表元素

题目 递归法使用 if(headnull){return null; }//假设remove返回后面已经去掉val值的链表 我们用head.next去存放他&#xff0c;接着我们要判断此时head head值是否等于val&#xff0c;如果等于我们就返回后继元素即可 head.nextremove(head.next,val); if(head.valval){return…...

Express中间件

目录 Express中间件 中间件的概念 next函数 全局中间与局部中间件 多个中间件 中间的5个注意事项 中间的分类 应用级中间件 路由级中间件 错误级中间件 Express内置中间件 express.json express.urlencoded 第三方中间件​编辑 自定义中间件 Express中间件 中间…...

【AIGC】SYNCAMMASTER:多视角多像机的视频生成

标题&#xff1a;SYNCAMMASTER: SYNCHRONIZING MULTI-CAMERA VIDEO GENERATION FROM DIVERSE VIEWPOINTS 主页&#xff1a;https://jianhongbai.github.io/SynCamMaster/ 代码&#xff1a;https://github.com/KwaiVGI/SynCamMaster 文章目录 摘要一、引言二、使用步骤2.1 TextT…...

模块化架构与微服务架构,哪种更适合桌面软件开发?

前言 在现代软件开发中&#xff0c;架构设计扮演着至关重要的角色。两种常见的架构设计方法是模块化架构与微服务架构。它们各自有独特的优势和适用场景&#xff0c;尤其在C#桌面软件开发领域&#xff0c;模块化架构往往更加具有实践性。本文将对这两种架构进行对比&#xff0…...

Ubuntu 24.04 LTS 安装 tailscale 并访问 SMB共享文件夹

Ubuntu 24.04 LTS 安装 tailscale 安装 Tailscale 官方仓库 首先&#xff0c;确保系统包列表是最新的&#xff1a; sudo apt update接下来&#xff0c;安装 Tailscale 所需的仓库和密钥&#xff1a; curl -fsSL https://tailscale.com/install.sh | sh这会自动下载并安装 …...

fgets、scanf存字符串应用

题目1 夺旗&#xff08;英语&#xff1a;Capture the flag&#xff0c;简称 CTF&#xff09;在计算机安全中是一种活动&#xff0c;当中会将“旗子”秘密地埋藏于有目的的易受攻击的程序或网站。参赛者从其他参赛者或主办方偷去旗子。 非常崇拜探姬的小学妹最近迷上了 CTF&am…...

C#高级:用Csharp操作鼠标和键盘

一、winform 1.实时获取鼠标位置 public Form1() {InitializeComponent();InitialTime(); }private void InitialTime() {// 初始化 Timer 控件var timer new System.Windows.Forms.Timer();timer.Interval 100; // 设置为 100 毫秒&#xff0c;即每 0.1 秒更新一次timer.…...

关于AI agent的学术论文实验部分:准确率,响应时间,用户满意度

关于AI agent的学术论文实验部分 在撰写关于AI agent的学术论文时,实验设计和实施是关键部分,仅搭建完成AI agent通常是不够的,需要通过严谨的实验来验证其性能、效果和创新性。以下以一个在智能客服场景中应用AI agent的例子,说明如何完成实验: 明确实验目的:确定通过实…...

消息队列实战指南:三大MQ 与 Kafka 适用场景全解析

前言&#xff1a;在当今数字化时代&#xff0c;分布式系统和大数据处理变得愈发普遍&#xff0c;消息队列作为其中的关键组件&#xff0c;承担着系统解耦、异步通信、流量削峰等重要职责。ActiveMQ、RabbitMQ、RocketMQ 和 Kafka 作为市场上极具代表性的消息队列产品&#xff0…...

postgresql表分区及测试

本文主要采用list类型实现表分区&#xff0c;并对表分区数据进行查询对比&#xff0c;数据量6000万条以上&#xff0c;速度相差10倍以上。 一、创建表&#xff0c;以substationcode字段为ist类型表分区 CREATE TABLE "public"."d_population_partition" …...

VUE学习笔记(入门)1__创建VUE实例

核心步骤 <div id"app"><!-- 这里存放渲染逻辑代码 --><h1>{{ msg }}</h1><a href"#">{{count}}</a> </div><!-- 引入在线的开发版本核心包 --> <!-- 引入核心包后全局可使用VUE构造函数 --> <…...

STL—stack与queue

目录 Stack stack的使用 stack的模拟实现 queue queue的使用 queue的模拟实现 priority_queue priority_queue的用法 priority_queue的模拟实现 容器适配器 种类 Stack http://www.cplusplus.com/reference/stack/stack/?kwstack stack是栈&#xff0c;后入先出 stack的…...

pthread_create函数

函数原型 pthread_create 是 POSIX 线程&#xff08;pthread&#xff09;库中的一个函数&#xff0c;用于在程序中创建一个新线程。 #include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *a…...

suctf2025

Suctf2025 --2标识为看的wp&#xff0c;没环境复现了 所有参考资料将在文本末尾标明 WEB SU_photogallery 思路&#x1f447; 构造一个压缩包&#xff0c;解压出我们想解压的部分&#xff0c;然后其他部分是损坏的&#xff0c;这样是不是就可以让整个解压过程是出错的从而…...

二、点灯基础实验

嵌入式基础实验第一个就是点灯&#xff0c;地位相当于编程界的hello world。 如下为LED原理图&#xff0c;要让相应LED发光&#xff0c;需要给I/O口设置输出引脚&#xff0c;低电平&#xff0c;二极管才会导通 2.1 打开初始工程&#xff0c;编写代码 以下会实现BLINKY常亮&…...

ESP8266-01S、手机、STM32连接

1、ESP8266-01S的工作原理 1.1、AP和STA ESP8266-01S为WIFI的透传模块&#xff0c;主要模式如下图&#xff1a; 上节说到&#xff0c;我们需要用到AT固件进行局域网应用&#xff08;ESP8266连接的STM32和手机进行连接&#xff09;。 ESP8266为一个WiFi透传模块&#xff0c;和…...

微服务学习:基础理论

一、微服务和应用现代化 1、时代的浪潮&#xff0c;企业的机遇和挑战 在互联网化数字化智能化全球化的当今社会&#xff0c;IT行业也面临新的挑战&#xff1a; 【快】业务需求如“滔滔江水连绵不绝”&#xff0c;企业需要更快的交付【变】林子大了&#xff0c;百色用户&…...

【c++继承篇】--继承之道:在C++的世界中编织血脉与传承

目录 引言 一、定义二、继承定义格式2.1定义格式2.2继承关系和访问限定符2.3继承后子类访问权限 三、基类和派生类赋值转换四、继承的作用域4.1同名变量4.2同名函数 五、派生类的默认成员构造函数5.1**构造函数调用顺序&#xff1a;**5.2**析构函数调用顺序&#xff1a;**5.3调…...

Java操作Excel导入导出——POI、Hutool、EasyExcel

目录 一、POI导入导出 1.数据库导出为Excel文件 2.将Excel文件导入到数据库中 二、Hutool导入导出 1.数据库导出为Excel文件——属性名是列名 2.数据库导出为Excel文件——列名起别名 3.从Excel文件导入数据到数据库——属性名是列名 4.从Excel文件导入数据到数据库…...

基于VSCODE+GDB+GDBSERVER远程单步调试设备篇(可视化界面)

目录 说明 配置方法 1&#xff09;VSCODE必备插件 2&#xff09;配置launch.json文件&#xff0c;用于GDB调试 调试步骤 ​​​​​​目标板运行程序 1&#xff09;已启动程序&#xff0c;通过attach方式进入调试 2&#xff09;通过gdbserver启动时加载程序(程序路径根据实际情…...

【设计模式】 单例模式(单例模式哪几种实现,如何保证线程安全,反射破坏单例模式)

单例模式 作用&#xff1a;单例模式的核心是保证一个类只有一个实例&#xff0c;并且提供一个访问实例的全局访问点。 实现方式优缺点饿汉式线程安全&#xff0c;调用效率高 &#xff0c;但是不能延迟加载懒汉式线程安全&#xff0c;调用效率不高&#xff0c;能延迟加载双重检…...

lvm快照备份

前提 数据文件要在逻辑卷上&#xff1b; 此逻辑卷所在卷组必须有足够空间使用快照卷&#xff1b; 数据文件和事务日志要在同一个逻辑卷上&#xff1b; 前提&#xff1a;MySQL数据lv和将要创建的快照要在同一vg&#xff0c;vg要有足够的空间存储 优点 几乎是热备&…...

PHP CRM售后系统小程序

&#x1f4bc; CRM售后系统 &#x1f4fa;这是一款基于PHP和uniapp深度定制的CRM售后管理系统&#xff0c;它犹如企业的智慧核心&#xff0c;精准赋能销售与售后管理的每一个环节&#xff0c;引领企业步入精细化、数字化的全新管理时代。系统集成了客户管理、合同管理、工单调…...

ETL 数据抽取

ETL ETL 数据抽取 ETL&#xff08;Extract, Transform, Load&#xff09;是数据集成和处理的重要过程&#xff0c;其中数据抽取&#xff08;Extract&#xff09;是第一步&#xff0c;负责从各种数据源中提取数据。以下是ETL数据抽取的详细说明和常用工具&#xff1a; 1. 数据…...

FANUC机器人系统镜像备份与恢复的具体步骤(图文)

FANUC机器人系统镜像备份与恢复的具体步骤(图文) 镜像备份: 如下图所示,进入文件—工具—切换设备,找到插入的U盘UT1, 如下图所示,进入U盘目录后,创建目录,这里目录名称为11, 如下图所示࿰...

MindsDB - 构建企业数据源 AI 对话

一、关于 MindsDB MindsDB是世界上最有效的解决方案&#xff0c;用于构建与混乱的企业数据源对话的AI应用程序。把它想象成图书管理员Marie Kondo。 github : https://github.com/mindsdb/mindsdb官网&#xff1a;https://www.mindsdb.com/官方文档&#xff1a;https://docs.…...

正则表达式(python版最全面,最易懂)

正则表达式 正则表达式英文称regular expression 定义&#xff1a;正则表达式是一种文本模式匹配的工具&#xff0c;用于字符串的搜索&#xff0c;匹配和替换。在excel,word以及其他的文本编辑器都可直接适配。 一、基本匹配规则 字面值字符&#xff1a;例如字母、数字、空格…...

QT 使用QTableView读取数据库数据,表格分页,跳转,导出,过滤功能

文章目录 效果图概述功能点代码分析导航栏表格更新视图表格导出表格过滤 总结 效果图 概述 本案例用于对数据库中的数据进行显示等其他操作。数据库的映射&#xff0c;插入等功能看此博客框架&#xff1a;数据模型使用QSqlTableModel&#xff0c;视图使用QTableView&#xff0…...

golang标准库path/filepath使用示例

文章目录 前言一、常用方法示例1.将相对路径转换为绝对路径2.获取路径中最后一个元素3.获取路径中除去最后一个元素的部分4.路径拼接5.将路径拆分为目录和文件名两部分6.返回一个相对路径7.文件路径遍历8.根据文件扩展名过滤文件9.使用正则表达式进行路径匹配 前言 path/filep…...

【日志篇】(7.6) ❀ 01. 在macOS下刷新FortiAnalyzer固件 ❀ FortiAnalyzer 日志分析

【简介】FortiAnalyzer 是 Fortinet Security Fabric 安全架构的基础&#xff0c;提供集中日志记录和分析&#xff0c;以及端到端可见性。因此&#xff0c;分析师可以更有效地管理安全状态&#xff0c;将安全流程自动化&#xff0c;并快速响应威胁。具有分析和自动化功能的集成…...

12 分布式事务

分布式事务产生的原因 我们拿mysql数据库来说&#xff0c;当数据库为单体数据库的时候&#xff0c;我们打开事务&#xff0c;执行sql为预执行阶段&#xff0c;最后commit时通过日志控制最终全部提交后存储到磁盘中&#xff0c;如果commit失败&#xff0c;可以通过日志控制回滚…...

移远通信多模卫星通信模组BG95-S5获得Skylo网络认证,进一步拓展全球卫星物联网市场

近日&#xff0c;全球领先的物联网整体解决方案供应商移远通信正式宣布&#xff0c;其支持“卫星蜂窝”多模式的高集成度NTN卫星通信模组BG95-S5已成功获得NTN网络运营商Skylo的网络认证。BG95-S5也成为了获得该认证的最新款移远卫星通信模组。 BG95-S5模组顺利获得Skylo认证&a…...

51.WPF应用加图标指南 C#例子 WPF例子

完整步骤&#xff1a; 先使用文心一言生成一个图标如左边使用Windows图片编辑器编辑&#xff0c;去除背景使用正方形&#xff0c;放大图片使图标铺满图片使用格式工程转换为ico格式&#xff0c;分辨率为最大 在资源管理器中右键项目添加ico类型图片到项目里图片属性设置为始终…...