文章目录
- 1、简要介绍
- 2、数据库设计
- 3、解决的问题
- 1、图片和音频的上传和存储
- 2、分页功能
- 4、数据返回
也算是进行了半个学期,跟着老师讲的进行
后端使用SpringBoot
前端 vue + layui
jdk 18
项目地址:gitee
1、简要介绍
只有管理端,但是对用户端的判断功能在数据库中已经体现,
实现了mp3
音频文件和图片文件的上传,原理是存储在静态资源中,数据库中存的也是路径而已
以下为部分截图,整体风格亦是如此
2、数据库设计
CREATE TABLE `role` (`id` int NOT NULL AUTO_INCREMENT COMMENT '角色id',`role_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色名称',`role_status` int NULL DEFAULT NULL COMMENT '角色状态 0 不可用 1 可用',`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP,`update_time` datetime NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,`dis` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,`rule` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;CREATE TABLE `singer` (`id` int UNSIGNED NOT NULL AUTO_INCREMENT,`name` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,`sex` tinyint NULL DEFAULT NULL,`pic` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,`birth` datetime NULL DEFAULT NULL,`location` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,`introduction` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 44 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;CREATE TABLE `song` (`id` int UNSIGNED NOT NULL AUTO_INCREMENT,`singer_id` int UNSIGNED NOT NULL,`name` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,`introduction` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '发行时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,`pic` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,`lyric` text CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL,`url` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 124 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;CREATE TABLE `sys_user` (`id` int NOT NULL AUTO_INCREMENT,`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户名',`nick_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '昵称',`gender` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '性别',`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密码 密文存储 ',`phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '手机号码',`status` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '1 可用 0 禁用',`image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '服务器路径',`created_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建者',`role_uid` int NULL DEFAULT NULL COMMENT '角色 严格遵循外键约束,但在表设计时候,不使用约束语句',`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
3、解决的问题
1、图片和音频的上传和存储
首先肯定是要有一个对于存储文件的配置,然后,在前端发送的时候,注意使用
post + headers: { 'content-type': 'multipart/form-data' }
的格式,
package com.whd.system.controller;import com.github.pagehelper.util.StringUtil;
import com.whd.system.common.AxiosResult;
import com.whd.system.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.time.LocalDate;
import java.util.UUID;@RestController
@RequestMapping("/fileup")
public class FileUpController {public static final String UPLOAD_PATH = "F:\\javaweb\\study\\spingBootMaven\\src\\main\\resources\\upload\\";@PostMapping("/image")public AxiosResult<String> upload(@RequestParam(value = "name") String name, MultipartFile photo, HttpServletRequest request) throws IOException {if(name==null || name.equals("")){return AxiosResult.error("参数错误");}String extension = StringUtils.getFilenameExtension(photo.getOriginalFilename());String fileName = UUID.randomUUID().toString().replaceAll("-", "") + "." + extension;String curDateStr = LocalDate.now().toString();File file = new File(UPLOAD_PATH+name+"\\"+ curDateStr);if (!file.exists()) {file.mkdirs();}photo.transferTo(new File(UPLOAD_PATH +name+ "\\" + curDateStr + "/" + fileName));return AxiosResult.success(name+ "\\" + curDateStr + "/" + fileName);}@PostMapping("/video")public AxiosResult<String> uploadViedo(@RequestParam(value = "name") String name, MultipartFile video, HttpServletRequest request) throws IOException {name+="\\viedo";if(!video.getContentType().equals("audio/mpeg")) {return AxiosResult.error("只允许上传mp3格式的音频文件");}String extension = StringUtils.getFilenameExtension(video.getOriginalFilename());String fileName = UUID.randomUUID().toString().replaceAll("-", "") + "." + extension;String curDateStr = LocalDate.now().toString();System.out.println(UPLOAD_PATH+name+"\\"+ curDateStr);File file = new File(UPLOAD_PATH+name+"\\"+ curDateStr);if (!file.exists()) {file.mkdirs();}video.transferTo(new File(UPLOAD_PATH +name+ "\\" + curDateStr + "/" + fileName));return AxiosResult.success(name+ "\\" + curDateStr + "/" + fileName);}}
2、分页功能
通过自定义的一个组件(当然不是我写的),在后端进行判断处理,其实没有想到那么复杂
组件如下:
<script>
Vue.component('zpagenav', {template: `<nav class="zpagenav">` +`<ul class="page-ul">` +`<li v-bind:key="index" v-for="(item,index) in pageList" v-bind:class ="item.class" @click.stop="setPage(item)" v-html="item.html">` +`</li>` +`</ul>` +`<span class="total">共 {{total}} 条</span>` +`</nav>`,props: {prevHtml: String,nextHtml: String,page: Number,total: Number,pageSize: Number,maxPage: Number},computed: {pageList: function() {var _this = this,pageList = [];let pageCount = Math.ceil(_this.total / _this.pageSize);let page = _this.page;let prevHtml = _this.prevHtml ? _this.prevHtml : '<';let nextHtml = _this.nextHtml ? _this.nextHtml : '>';let maxPage = _this.maxPage ? _this.maxPage : 9;let hasPrev = page > 1;let hasNext = page < pageCount;//上一页pageList.push({class: hasPrev ? '' : 'disabled',page: hasPrev ? page - 1 : page,html: prevHtml});//首页pageList.push({class: page == 1 ? 'active' : '',page: 1,html: 1});var p0 = Math.floor(maxPage / 2);var p1 = 1 + 2 + p0; //首页+省略至少2个页码+中间页面数的一半var start, end;if(page >= p1) {start = page - p0;//前置省略号pageList.push({class: 'dot',page: page,html: '...'});} else {start = 2;}var p2 = page + p0;if(p2 < pageCount) {end = p2;} else {end = pageCount - 1;}//页码列表for(let i = start; i <= end; i++) {pageList.push({class: page == i ? 'active' : '',page: i,html: i});}if(end < pageCount - 1) {//后置省略号pageList.push({class: 'dot',page: page,html: '...'});}//尾页if(pageCount > 1) {pageList.push({class: page == pageCount ? 'active' : '',page: pageCount,html: pageCount});}//下一页pageList.push({class: hasNext ? '' : 'disabled',page: hasNext ? page + 1 : page,html: nextHtml});return pageList;}},methods: {setPage: function(item) {if(item.class == '') {this.$emit('pagehandler', item.page);}}}
});</script><style>
.zpagenav {text-align: center;-webkit-user-select: none;
}.zpagenav {font-family: arial;color: #48576a;
}.zpagenav ul {display: inline-block;margin: 20px 20px;padding: 0;
}.zpagenav ul li {display: inline-block;margin: 0;padding: 0 4px;border: 1px solid #d1dbe5;border-right: 0;background: #fff;font-size: 13px;min-width: 28px;height: 28px;line-height: 28px;cursor: pointer;box-sizing: border-box;text-align: center;
}.zpagenav ul li:last-child {border-right: 1px solid #d1dbe5;
}.zpagenav ul li:hover {color: #20a0ff;
}.zpagenav ul li.active {border-color: #20a0ff;background-color: #20a0ff;color: #fff;cursor: default;
}.zpagenav ul li.active:hover {color: #fff;
}.zpagenav ul li.disabled {cursor: not-allowed;color: #e4e4e4;
}.zpagenav ul li.dot {cursor: default;
}</style>
使用的时候,注意引入该文件,和该文件的样式文件,然后,将这一坨复制到分页的地方
<div class="page"><div class="wrap"><zpagenav v-bind:page="page" v-bind:page-size="pageSize" v-bind:total="total" v-bind:max-page="maxPage"v-on:pagehandler="pageHandler"></zpagenav></div></div>
注意,在vue 的 data 中加上 page: 1, pageSize: 10, total: 0, maxPage: 5,
这三个属性,
然后,就是后端处理返回数据了,正常查询结果,通过 PageHelper PageInfo
进行分页处理
处理逻辑如下例子:
@PostMapping("/select/selectAllPage")public AxiosResult<PageResult<SongVo>> getAllSongByPage(@RequestBody PageVO pageVO){Integer page = pageVO.getPage();Integer size = pageVO.getSize();// 开始分页PageHelper.startPage(page, size);List<SongVo> songs=songMapper.getSongList();songs.forEach(song -> {song.setPic(PIC_URL + song.getPic());song.setUrl(PIC_URL + song.getUrl());});PageInfo<SongVo> pageInfo = new PageInfo<>(songs);//将数据封装在自己写的PageResultPageResult<SongVo> pageResult = new PageResult<>();pageResult.setPage(pageInfo.getPageNum());pageResult.setTotalPage(pageInfo.getPages());pageResult.setList(pageInfo.getList());pageResult.setTotal(pageInfo.getTotal());pageResult.setPageSize(pageInfo.getPageSize());return AxiosResult.success(pageResult);}
对于AxiosResult
也是自己写的一个实体类, 用于返回信息和状态码, 具体代码可查看项目中的代码:
对于PageResult 类
:
@Data
public class PageResult<T> {private int page;private int pageSize;private long total;private List<T> list;private int totalPage;
}
PageVO 类
:
@Data
public class PageVO {private Integer page=1;//用户查询的第几页private Integer size=10;//每页展示的数量
}
4、数据返回
通过SpringBoot
返回的数据类型,会自动转为json
格式,但是上面所提到的AxiosResult
也不是必须的,但是这样子看起来更方便,该类可以作为模板重复使用