抽奖系统(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: 1. 如果访问的是本地路径,…...
36.centos7上安装python3.6.5、安装卸载依赖包
查看openssl的版本号,默认python3.6.5需要OpenSSL 1.0.2以上的版本支持。 监测安装好的python,是否可以正确导入ssl和_ssl包 pip3安装依赖包 通过Pycharm工具导出requirements.txt文件 查看/usr/bin/目录下的软连接 pip3, python...
微透镜阵列精准全检,白光干涉3D自动量测方案提效70%
广泛应用的微透镜阵列 微透镜是一种常见的微光学元件,通过设计微透镜,可对入射光进行扩散、光束整形、光线均分、光学聚焦、集成成像等调制,进而实现许多传统光学元器件难以实现的特殊功能。 微透镜阵列(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 主从同步是一种数据库复制技术,它通过将主服务器上的数据更改复制到一个或多个从服务器,实现数据的自动同步。 主从同步的核心原理是将主服务器上的二进制日志复制到从服务器,并在从服务器上执行这些日志中的操作。 MySQL主从同步是基…...
Java API:封装自定义响应类
本文介绍 Web 服务开发中自定义响应,涵盖标准 HTTP 响应状态码局限性、自定义响应价值、设计原则与实现、在 Spring Boot 项目应用、与其他响应格式对比总结及应用场景。 1. 标准HTTP响应与自定义响应 1.1标准HTTP响应状态码 在 Web 服务开发中,HTTP…...
【Unity3D】利用Hinge Joint 2D组件制作绳索效果
目录 一、动态绳索 (可移动根节点) 二、静态绳索 三、利用Skinning Editor(Unity2022.3.15f1正常使用) 四、注意事项 一、动态绳索 (可移动根节点) 动态绳索 DynamicRope空物体 Anchor和whitecircle是相同位置的物体ÿ…...
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字符 等价于 空格,tab&am…...
嵌入式硬件篇---PID控制
文章目录 前言第一部分:连续PID1.比例(Proportional,P)控制2.积分(Integral,I)控制3.微分(Derivative,D)控制4.PID的工作原理5..实质6.分析7.各种PID控制器P控…...
技术洞察:C++在后端开发中的前沿趋势与社会影响
文章目录 引言C在后端开发中的前沿趋势1. 高性能计算的需求2. 微服务架构的兴起3. 跨平台开发的便利性 跨领域技术融合与创新实践1. C与人工智能的结合2. C与区块链技术的融合 C对社会与人文的影响1. 提升生产力与创新能力2. 促进技术教育与人才培养3. 技术与人文的深度融合 结…...
C语言程序设计之小系统
🌟 嗨,我是LucianaiB! 🌍 总有人间一两风,填我十万八千梦。 🚀 路漫漫其修远兮,吾将上下而求索。 目录 系统说明 1.1 系统概述 1.2 功能模块总体设计详细设计 3.1 程序中使用的函数 3.2各类问…...
pyinstaller : 无法将“pyinstaller”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。
pyinstaller : 无法将“pyinstaller”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。 所在位置 行:1 字符: 1pyinstaller --onefile --windowed 过年烟花.py~~~~~~~~~~~ …...
接口传参 data格式和json格式区别是什么
接口传参 data格式和json格式区别是什么 以下是接口传参 data 格式和 JSON 格式的区别: 定义和范围 Data 格式: 是一个较为宽泛的概念,它可以指代接口传递参数时所使用的任何数据的组织形式。包括但不限于 JSON、XML、Form 数据、纯文本、二进…...
ClickHouse 入门
简介 ClickHouse 是一个列式数据库,传统的数据库一般是按行存储,而ClickHouse则是按列存储,每一列都有自己的存储空间,并且只存储该列的数值,而不是存储整行的数据。这样做主要有几个好处,压缩率高&#x…...
Python自动化:基于faker批量生成模拟数据(以电商行业销售数据为例)
引言:个人认为,“造数据”是一个数据分析师的一项基本技能,当然啦,“造数据”不是说胡编乱造,而是根据自己的需求去构造一些模拟数据集,用于测试等用途,而且使用虚拟数据不用担心数据隐私和安全…...
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 事务:隔离级别与传播机制解读与应用
前言 🌟🌟本期讲解关于spring 事务传播机制介绍~~~ 🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 🔥 你的点赞就是小编不断更新的最大动力 🎆那么废话…...
力扣203题—— 移除链表元素
题目 递归法使用 if(headnull){return null; }//假设remove返回后面已经去掉val值的链表 我们用head.next去存放他,接着我们要判断此时head head值是否等于val,如果等于我们就返回后继元素即可 head.nextremove(head.next,val); if(head.valval){return…...
Express中间件
目录 Express中间件 中间件的概念 next函数 全局中间与局部中间件 多个中间件 中间的5个注意事项 中间的分类 应用级中间件 路由级中间件 错误级中间件 Express内置中间件 express.json express.urlencoded 第三方中间件编辑 自定义中间件 Express中间件 中间…...
【AIGC】SYNCAMMASTER:多视角多像机的视频生成
标题:SYNCAMMASTER: SYNCHRONIZING MULTI-CAMERA VIDEO GENERATION FROM DIVERSE VIEWPOINTS 主页:https://jianhongbai.github.io/SynCamMaster/ 代码:https://github.com/KwaiVGI/SynCamMaster 文章目录 摘要一、引言二、使用步骤2.1 TextT…...
模块化架构与微服务架构,哪种更适合桌面软件开发?
前言 在现代软件开发中,架构设计扮演着至关重要的角色。两种常见的架构设计方法是模块化架构与微服务架构。它们各自有独特的优势和适用场景,尤其在C#桌面软件开发领域,模块化架构往往更加具有实践性。本文将对这两种架构进行对比࿰…...
Ubuntu 24.04 LTS 安装 tailscale 并访问 SMB共享文件夹
Ubuntu 24.04 LTS 安装 tailscale 安装 Tailscale 官方仓库 首先,确保系统包列表是最新的: sudo apt update接下来,安装 Tailscale 所需的仓库和密钥: curl -fsSL https://tailscale.com/install.sh | sh这会自动下载并安装 …...
fgets、scanf存字符串应用
题目1 夺旗(英语:Capture the flag,简称 CTF)在计算机安全中是一种活动,当中会将“旗子”秘密地埋藏于有目的的易受攻击的程序或网站。参赛者从其他参赛者或主办方偷去旗子。 非常崇拜探姬的小学妹最近迷上了 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 毫秒,即每 0.1 秒更新一次timer.…...
关于AI agent的学术论文实验部分:准确率,响应时间,用户满意度
关于AI agent的学术论文实验部分 在撰写关于AI agent的学术论文时,实验设计和实施是关键部分,仅搭建完成AI agent通常是不够的,需要通过严谨的实验来验证其性能、效果和创新性。以下以一个在智能客服场景中应用AI agent的例子,说明如何完成实验: 明确实验目的:确定通过实…...
消息队列实战指南:三大MQ 与 Kafka 适用场景全解析
前言:在当今数字化时代,分布式系统和大数据处理变得愈发普遍,消息队列作为其中的关键组件,承担着系统解耦、异步通信、流量削峰等重要职责。ActiveMQ、RabbitMQ、RocketMQ 和 Kafka 作为市场上极具代表性的消息队列产品࿰…...
postgresql表分区及测试
本文主要采用list类型实现表分区,并对表分区数据进行查询对比,数据量6000万条以上,速度相差10倍以上。 一、创建表,以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是栈,后入先出 stack的…...
pthread_create函数
函数原型 pthread_create 是 POSIX 线程(pthread)库中的一个函数,用于在程序中创建一个新线程。 #include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *a…...
suctf2025
Suctf2025 --2标识为看的wp,没环境复现了 所有参考资料将在文本末尾标明 WEB SU_photogallery 思路👇 构造一个压缩包,解压出我们想解压的部分,然后其他部分是损坏的,这样是不是就可以让整个解压过程是出错的从而…...
二、点灯基础实验
嵌入式基础实验第一个就是点灯,地位相当于编程界的hello world。 如下为LED原理图,要让相应LED发光,需要给I/O口设置输出引脚,低电平,二极管才会导通 2.1 打开初始工程,编写代码 以下会实现BLINKY常亮&…...
ESP8266-01S、手机、STM32连接
1、ESP8266-01S的工作原理 1.1、AP和STA ESP8266-01S为WIFI的透传模块,主要模式如下图: 上节说到,我们需要用到AT固件进行局域网应用(ESP8266连接的STM32和手机进行连接)。 ESP8266为一个WiFi透传模块,和…...
微服务学习:基础理论
一、微服务和应用现代化 1、时代的浪潮,企业的机遇和挑战 在互联网化数字化智能化全球化的当今社会,IT行业也面临新的挑战: 【快】业务需求如“滔滔江水连绵不绝”,企业需要更快的交付【变】林子大了,百色用户&…...
【c++继承篇】--继承之道:在C++的世界中编织血脉与传承
目录 引言 一、定义二、继承定义格式2.1定义格式2.2继承关系和访问限定符2.3继承后子类访问权限 三、基类和派生类赋值转换四、继承的作用域4.1同名变量4.2同名函数 五、派生类的默认成员构造函数5.1**构造函数调用顺序:**5.2**析构函数调用顺序:**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)VSCODE必备插件 2)配置launch.json文件,用于GDB调试 调试步骤 目标板运行程序 1)已启动程序,通过attach方式进入调试 2)通过gdbserver启动时加载程序(程序路径根据实际情…...
【设计模式】 单例模式(单例模式哪几种实现,如何保证线程安全,反射破坏单例模式)
单例模式 作用:单例模式的核心是保证一个类只有一个实例,并且提供一个访问实例的全局访问点。 实现方式优缺点饿汉式线程安全,调用效率高 ,但是不能延迟加载懒汉式线程安全,调用效率不高,能延迟加载双重检…...
lvm快照备份
前提 数据文件要在逻辑卷上; 此逻辑卷所在卷组必须有足够空间使用快照卷; 数据文件和事务日志要在同一个逻辑卷上; 前提:MySQL数据lv和将要创建的快照要在同一vg,vg要有足够的空间存储 优点 几乎是热备&…...
PHP CRM售后系统小程序
💼 CRM售后系统 📺这是一款基于PHP和uniapp深度定制的CRM售后管理系统,它犹如企业的智慧核心,精准赋能销售与售后管理的每一个环节,引领企业步入精细化、数字化的全新管理时代。系统集成了客户管理、合同管理、工单调…...
ETL 数据抽取
ETL ETL 数据抽取 ETL(Extract, Transform, Load)是数据集成和处理的重要过程,其中数据抽取(Extract)是第一步,负责从各种数据源中提取数据。以下是ETL数据抽取的详细说明和常用工具: 1. 数据…...
FANUC机器人系统镜像备份与恢复的具体步骤(图文)
FANUC机器人系统镜像备份与恢复的具体步骤(图文) 镜像备份: 如下图所示,进入文件—工具—切换设备,找到插入的U盘UT1, 如下图所示,进入U盘目录后,创建目录,这里目录名称为11, 如下图所示...
MindsDB - 构建企业数据源 AI 对话
一、关于 MindsDB MindsDB是世界上最有效的解决方案,用于构建与混乱的企业数据源对话的AI应用程序。把它想象成图书管理员Marie Kondo。 github : https://github.com/mindsdb/mindsdb官网:https://www.mindsdb.com/官方文档:https://docs.…...
正则表达式(python版最全面,最易懂)
正则表达式 正则表达式英文称regular expression 定义:正则表达式是一种文本模式匹配的工具,用于字符串的搜索,匹配和替换。在excel,word以及其他的文本编辑器都可直接适配。 一、基本匹配规则 字面值字符:例如字母、数字、空格…...
QT 使用QTableView读取数据库数据,表格分页,跳转,导出,过滤功能
文章目录 效果图概述功能点代码分析导航栏表格更新视图表格导出表格过滤 总结 效果图 概述 本案例用于对数据库中的数据进行显示等其他操作。数据库的映射,插入等功能看此博客框架:数据模型使用QSqlTableModel,视图使用QTableView࿰…...
golang标准库path/filepath使用示例
文章目录 前言一、常用方法示例1.将相对路径转换为绝对路径2.获取路径中最后一个元素3.获取路径中除去最后一个元素的部分4.路径拼接5.将路径拆分为目录和文件名两部分6.返回一个相对路径7.文件路径遍历8.根据文件扩展名过滤文件9.使用正则表达式进行路径匹配 前言 path/filep…...
【日志篇】(7.6) ❀ 01. 在macOS下刷新FortiAnalyzer固件 ❀ FortiAnalyzer 日志分析
【简介】FortiAnalyzer 是 Fortinet Security Fabric 安全架构的基础,提供集中日志记录和分析,以及端到端可见性。因此,分析师可以更有效地管理安全状态,将安全流程自动化,并快速响应威胁。具有分析和自动化功能的集成…...
12 分布式事务
分布式事务产生的原因 我们拿mysql数据库来说,当数据库为单体数据库的时候,我们打开事务,执行sql为预执行阶段,最后commit时通过日志控制最终全部提交后存储到磁盘中,如果commit失败,可以通过日志控制回滚…...
移远通信多模卫星通信模组BG95-S5获得Skylo网络认证,进一步拓展全球卫星物联网市场
近日,全球领先的物联网整体解决方案供应商移远通信正式宣布,其支持“卫星蜂窝”多模式的高集成度NTN卫星通信模组BG95-S5已成功获得NTN网络运营商Skylo的网络认证。BG95-S5也成为了获得该认证的最新款移远卫星通信模组。 BG95-S5模组顺利获得Skylo认证&a…...
51.WPF应用加图标指南 C#例子 WPF例子
完整步骤: 先使用文心一言生成一个图标如左边使用Windows图片编辑器编辑,去除背景使用正方形,放大图片使图标铺满图片使用格式工程转换为ico格式,分辨率为最大 在资源管理器中右键项目添加ico类型图片到项目里图片属性设置为始终…...