该笔记是记录听课的笔记,在上一篇文章的基础上加上了前端的开发,实现前后端完整功能的实现。
此项目使用的是VUE+ELEMENTUI以及Springboot+mybatis-plus的技术进行的开发,基于IDEA软件。此笔记在上一篇文章:【项目笔记】后端框架的搭建用法以及一些常用功能的实现,使用Springboot+mybatis-plus等技术的基础上,记录了前端联合后端的开发。
此项目希望插入新用户的时候统一设置初始化密码,即在插入用户的时候不用输入密码自动填充
在后端的Admin类中password属性设置了自动填充。
直接在@TableField()括号里加上fill = FieldFill.INSERT
插入式填充:注意字段、类型和值
Admin.java在上一篇文章里自动生成架构的时候,后面的字段就直接生成为自动填充值的架构,故一点开就会发现后面的value、version等属性直接就有fill=FieldFill.INSERT
ps:逻辑删除字段需要加上@TableLogic注解
生成架构的关于自动填充的代码:
自动加了注解但还是需要在MyMetaObjectHandler中的insertFill插入填充的方法中编写设置了自动填充的字段、类型以及值
此项目设置在进行更新操作后,update_time自动进行填充此时的更新时间。
当进行更新后,Date.class类型的updateTime这个字段将更新为现在的时间new Date()
ps:平时测试后端功能的时候,url地址栏填写格式
url:http://localhost:8081/一级路径(到类)/二级路径(到方法)
删除的时候不是删除,而是将isdelete设置为1,而且将修改时间更新
在上一篇文章写了分页查询和条件查询分开的,此处将分页和条件进行合并查询。
@PostMapping("/selectAdminByConditionAndPage/{currentPage}/{pageSize}")
public Result selectAdminByConditionAndPage(@PathVariable Long currentPage, @PathVariable Long pageSize, @RequestBody AdminCondition adminCondition) {
String name = adminCondition.getName();
String username = adminCondition.getUsername();
String phone = adminCondition.getPhone();
String beginTime = adminCondition.getBeginTime();
String endTime = adminCondition.getEndTime();
QueryWrapper<Admin> adminQueryWrapper = new QueryWrapper<>();
if (!name.isEmpty()) {
adminQueryWrapper.like("name", name);
}
if (!username.isEmpty()) {
adminQueryWrapper.like("username", username);
}
if (!phone.isEmpty()) {
adminQueryWrapper.like("phone", phone);
}
if (!beginTime.isEmpty() && !endTime.isEmpty()) {
adminQueryWrapper.between("create_time", beginTime, endTime);
}
adminQueryWrapper.orderByDesc("create_time");
Page<Admin> adminPage = new Page<>(currentPage, pageSize);
// List list = adminService.list(adminQueryWrapper);
Page<Admin> page = adminService.page(adminPage, adminQueryWrapper);
List<Admin> list = page.getRecords();
long total = page.getTotal();
return Result.success().data("items", list).data("total", total);//返回两个结果,一个是结果集一个是总数
}
ps:加上required = false,查询的时候可以加上查询条件,也可以没有查询条件
对象里面的参数为null和这个对象为null都是代表没有条件的意思
在mybatis-plus中找到分页插件
实现分页查询还需要新建一个类:可以在此类中添加想操作的插件
ps:Version乐观锁可以用于不能两个人同时选择某一个东西。至此后端结束。
Public打包的
Src项目主要内容
Api负责接口的管理
Vue不是跳转而是在一个页面中不断渲染
API是负责发送的接口 controller是负责接收的接口
希望别人可以用就必须export导出
Vue通过路由进行跳转的,从导入组件import,children可能是二级菜单。
Views就是写的页面,建议一个文件夹对应一个页面,页面都叫index,只用修改文件夹的名字即可。
Token类似门票
Templete里面有且只有一个div,放标签。取代了body标签。
Script写js
Style写css
跨域:ip不同端口不同,在vue.config.js中
只要请求的url中有/dev-api就跨域:跨域连接到后端
只用将这段插入config.js中
proxy: {
'/dev-api': {
target: 'http://localhost:8081/',
ws: true,
changeOrigin: true,//允许跨域
pathRewrite: {
'^/dev-api': ''
}
}
},
后端:AdminController中的login方法
@PostMapping("/login")
public Result login(@RequestBody Admin admin){
String username = admin.getUsername();
String password = admin.getPassword();
QueryWrapper<Admin> adminQueryWrapper = new QueryWrapper<>();
if (!username.isEmpty()) {
adminQueryWrapper.eq("username", username);
}else {
return Result.fail().message("账号不能为空");
}
Admin one = adminService.getOne(adminQueryWrapper);//getone用于唯一的学号确定某一个人
if (one == null) {
return Result.fail().message("该账号不存在");
}
if (one.getPassword().equals(password)) {
Map<String, Object> data = new HashMap<>();
data.put("id", one.getId());
String token = JWTUtils.generateToken(data);
return Result.success().message("登录成功").data("token", token);
}
return Result.fail().message("登陆失败");
}
此处需要提到token的定义:Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。
使用Token的目的:Token的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。
token的依赖:
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.1version>
dependency>
关于token的方法:
@GetMapping("/info")
public Result info(@RequestParam("token") String token){
if (token == null) {
return Result.fail().message("token不能为空");
}
Map<String, Object> data = JWTUtils.parseToken(token);
if (data == null) {
return Result.fail().message("token错误");
}
String id = (String) data.get("id");
Admin admin = adminService.getById(id);
return Result.success().data("name", admin.getName()).data("avatar", "");
}
实际vue代码以及其对应页面上的展示元素
图标的使用可以在这个网站:
给属性绑定一个变量:
传对象用data,传参数用param
**ps:在字符串里面插入变量:不用单引号,而是使用反引号` **
// 字符串中嵌入变量
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?` // Hello Bob, how are you today?
后端写的返回data(“items”,list),则前端的adminList会去接收后端返回的items对应的list
可以通过{{scope.row.xxx}}拿到某条数据的xxx
此处是对拿到的对象的phone属性进行取子串的操作
数据的双向绑定:
无限封装成一个方法
HandleDelete是处理
图表可以使用echarts:
注意,要export后其他页面才能import导入
index.vue
<template>
<div class="app-container">
<el-button type="primary" @click="handleInsertAdmin()">添加员工</el-button>
<el-table ref="multipleTable" :data="adminList" tooltip-effect="dark" style="width: 100%; margin-top: 10px;" border>
<el-table-column type="selection" width="55">
</el-table-column>
<el-table-column prop="username" label="学号">
</el-table-column>
<el-table-column prop="name" label="姓名">
</el-table-column>
<el-table-column prop="createTime" label="创建时间">
</el-table-column>
<el-table-column>
<template slot-scope="scope">
<el-button type="danger" size="mini" @click="">编辑</el-button>
<el-button type="danger" size="mini" @click="handleDeleteAdmin(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-upload ref="uploadFile" class="avatar-uploader" action="http://localhost:9528/dev-api/example.demo/users/uploadFile" :auto-upload="false" accept="image/png"
:on-change="handelFileChange" :on-success="handleUploadSuccess">
<img v-if="imageUrl" :src="imageUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
<el-button @click="handleUploadFile()">上传</el-button>
<el-dialog title="收货地址" :visible.sync="dialogFormVisible">
<el-form :model="adminEntity">
<el-form-item label="姓名">
<el-input v-model="adminEntity.name"></el-input>
</el-form-item>
<el-form-item label="学号">
<el-input v-model="adminEntity.username"></el-input>
</el-form-item>
<el-form-item label="电话">
<el-input v-model="adminEntity.phone"></el-input>
</el-form-item>
<el-form-item label="权限">
<el-select v-model="adminEntity.level">
<el-option v-for="(item, index) in levelList" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="handleInsertAdminEntity()">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import adminApi from '@/api/admin'
export default {
data() {
return {
isSelete : false,
adminEntity: {},
adminCondition: {
name: '',
username: '',
phone: '',
beginTime: '',
endTime: ''
},
currentPage: 1,
pageSize: 20,
adminList: [],
dialogFormVisible: false,
imageUrl : '',
levelList: [
{
value: 1,
label: '员工'
},
{
value: 2,
label: '管理员'
},
{
value: 3,
label: '超级管理员'
},
]
}
},
created() {
this.getAdminList()
},
methods: {
getAdminList() {
adminApi.selectAdminByConditionAndPage(this.adminCondition, this.currentPage, this.pageSize).then(res => {
this.adminList = res.data.items
})
},
handleInsertAdmin() {
this.dialogFormVisible = true
},
handleInsertAdminEntity() {
adminApi.insertAdmin(this.adminEntity).then(res => {
if (res.success) {
this.getAdminList()
}
this.dialogFormVisible = false
this.adminEntity = {}
})
},
handleDeleteAdmin(id) {
adminApi.deleteAdmin(id).then(res => {
if (res.success) {
this.getAdminList()
}
})
},
handelFileChange(file, fileList) {
// 当用户选择图片后,将图片回显到页面,实现预览功能
this.imageUrl = URL.createObjectURL(file.raw)
},
handleUploadFile() {
this.$refs.uploadFile.submit()
},
handleUploadSuccess(response, file, fileList) {
if (response.success) {
// 成功消息提示框
this.$message({
message: '上传成功',
type: 'success'
});
}else {
// 失败消息提示框
this.$message.error(response.message);
}
},
}
}
</script>
<style>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
margin-top: 10px;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>
admin.js
import request from '@/utils/request'
const adminApi = {
selectAdminByConditionAndPage(adminConditon, currentPage, pageSize) {
return request({
url: `/example.demo/admin/selectAdminByConditionAndPage/${currentPage}/${pageSize}`,
method: 'post',
data: adminConditon
})
},
insertAdmin(admin) {
return request({
url: `/example.demo/admin/insertAdmin`,
method: 'post',
data: admin
})
},
deleteAdmin(id) {
return request({
url: `/example.demo/admin/deleteAdmin/${id}`,
method: 'get',
})
}
}
export default adminApi
AdminController.java
package com.example.demo.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.entity.Admin;
import com.example.demo.entity.Users;
import com.example.demo.entity.condition.AdminCondition;
import com.example.demo.entity.condition.UsersCondition;
import com.example.demo.service.AdminService;
import com.example.demo.utils.JWTUtils;
import com.example.demo.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/example.demo/admin")
public class AdminController {
@Autowired
private AdminService adminService;
@PostMapping("/insertAdmin")
public Result insertAdmin(@RequestBody Admin admin) {
boolean save = adminService.save(admin);
if (save) {
return Result.success().message("添加成功");
}
return Result.fail().message("添加失败");
}
@GetMapping("/deleteAdmin/{id}")
public Result deleteAdmin(@PathVariable String id) {
boolean flag = adminService.removeById(id);
if (flag) {
return Result.success();
}
return Result.fail();
}
@PostMapping("/updateAdmin")
public Result updateAdmin(@RequestBody Admin admin) {
boolean flag = adminService.updateById(admin);
if (flag) {
return Result.success().message("修改成功");
}
return Result.fail().message("修改失败");
}
@GetMapping("/selectAdmin")
public Result selectAdmin(){
List<Admin> list = adminService.list();
return Result.success().data("items", list);
}
@PostMapping("/selectAdminByConditionAndPage/{currentPage}/{pageSize}")
public Result selectAdminByConditionAndPage(@PathVariable Long currentPage, @PathVariable Long pageSize, @RequestBody AdminCondition adminCondition) {
String name = adminCondition.getName();
String username = adminCondition.getUsername();
String phone = adminCondition.getPhone();
String beginTime = adminCondition.getBeginTime();
String endTime = adminCondition.getEndTime();
QueryWrapper<Admin> adminQueryWrapper = new QueryWrapper<>();
if (!name.isEmpty()) {
adminQueryWrapper.like("name", name);
}
if (!username.isEmpty()) {
adminQueryWrapper.like("username", username);
}
if (!phone.isEmpty()) {
adminQueryWrapper.like("phone", phone);
}
if (!beginTime.isEmpty() && !endTime.isEmpty()) {
adminQueryWrapper.between("create_time", beginTime, endTime);
}
adminQueryWrapper.orderByDesc("create_time");
Page<Admin> adminPage = new Page<>(currentPage, pageSize);
// List list = adminService.list(adminQueryWrapper);
Page<Admin> page = adminService.page(adminPage, adminQueryWrapper);
List<Admin> list = page.getRecords();
long total = page.getTotal();
return Result.success().data("items", list).data("total", total);
}
@PostMapping("/login")
public Result login(@RequestBody Admin admin){
String username = admin.getUsername();
String password = admin.getPassword();
QueryWrapper<Admin> adminQueryWrapper = new QueryWrapper<>();
if (!username.isEmpty()) {
adminQueryWrapper.eq("username", username);
}else {
return Result.fail().message("账号不能为空");
}
Admin one = adminService.getOne(adminQueryWrapper);
if (one == null) {
return Result.fail().message("该账号不存在");
}
if (one.getPassword().equals(password)) {
Map<String, Object> data = new HashMap<>();
data.put("id", one.getId());
String token = JWTUtils.generateToken(data);
return Result.success().message("登录成功").data("token", token);
}
return Result.fail().message("登陆失败");
}
@GetMapping("/info")
public Result info(@RequestParam("token") String token){
if (token == null) {
return Result.fail().message("token不能为空");
}
Map<String, Object> data = JWTUtils.parseToken(token);
if (data == null) {
return Result.fail().message("token错误");
}
String id = (String) data.get("id");
Admin admin = adminService.getById(id);
return Result.success().data("name", admin.getName()).data("avatar", "");
}
/*上传文件*/
@PostMapping("/uploadFile")
public Result uploadFile(@RequestParam("file") MultipartFile multipartFile){
//根路径
String bassPath = "D:\\IdeaProjects\\demo\\src\\main\\resources\\files"; //基本路径
// 时间路径,格式化时间,以时间对文件夹进行分类
String dataPath = new SimpleDateFormat("YYYY\\MM\\dd").format(new Date());
//基本路径加上时间路径形成上传的文件夹,先创建文件夹再创建文件
String dirPath = bassPath +"\\"+ dataPath;
//System.out.println(dirPath);
File dirFile = new File(dirPath);//使用io的File
if(!dirFile.exists()){
boolean mkdirs = dirFile.mkdirs();
if(!mkdirs){//先创建文件夹再创建文件
return Result.fail().message("文件夹创建失败");
}
}
//originalname才是文件名,获取文件名
String originalFilename = multipartFile.getOriginalFilename();
if(originalFilename == null){
return Result.fail().message("文件名为空");
}
//对文件名通过"."进行分割 比如"文件.exe" => {"文件","exe"}
String[] fileName = originalFilename.split("\\.");
//每次上传的文件前面添加了随机值防止文件上传覆盖,通过UUID随机生成的字符串并且去除其中的横线,再加上文件类型组成完整的文件名
String targetFileName = UUID.randomUUID().toString().replace("-","") + "." + fileName[1];
File targetFile = new File(dirFile, targetFileName);
try(
InputStream inputStream = multipartFile.getInputStream();
OutputStream outputStream = Files.newOutputStream(targetFile.toPath());
){
byte[] buffer = new byte[1024];
int len = -1;
while ((len = inputStream.read(buffer)) != -1){ //拷贝,-1就表示已经读取完毕
outputStream.write(buffer,0,len);
}
}catch (Exception e){
return Result.fail().message("保存失败");
}
String path = targetFile.getPath();
return Result.success().data("url",path);
}
}
浅浅记录一下师兄给我们讲解的vue前端以及前后端是如何结合的,第一版笔记可能有点粗糙,后面会一点点进行精细的修改。