前端

JS基础

打印出对象的全部属性

1
console.log(JSON.stringify(params, null, 2));

这行代码使用了JavaScript中的console.log函数和JSON.stringify方法来打印一个JavaScript对象的字符串表示形式到控制台。

JSON.stringify(params, null, 2):

  • JSON.stringify是一个用于将JavaScript对象转换为JSON字符串的方法。

  • params是要转换的对象。

  • 第二个参数是用于转换过程的replacer函数,这里传入了null,表示不使用任何替换函数。

  • 第三个参数是用于缩进输出的空格数,这里传入了2,表示每一层嵌套的缩进为2个空格。

综合起来,这行代码的作用是将JavaScript对象 params 转换为一个格式化的JSON字符串,然后将该字符串输出到控制台。通过传递第三个参数为2,输出的JSON字符串会以每一层嵌套缩进2个空格的方式格式化,使其更易读。这通常用于调试和查看对象的结构和内容。

去除对象中的其中几个属性并赋值给另一个变量

起因是出现了参数不对的bug,发现直接直接把row赋值给params,而row中的imageUrlList1, imageUrlList2参数都是不需要的

1
let { imageUrlList1, imageUrlList2, ...params } = row;

这段代码是使用解构赋值语法,它通常用于从对象中提取值。让我们逐步解释这行代码:

  1. { imageUrlList1, imageUrlList2, ...params }: 这是解构赋值的语法。通过这个语法,我们从对象 row 中提取了 imageUrlList1imageUrlList2 这两个属性的值,并将它们分别赋给了同名的变量 imageUrlList1imageUrlList2。另外,...params 表示将对象中除了 imageUrlList1imageUrlList2 之外的所有属性都放入一个名为 params 的新对象中。

  2. = row: 这表示将解构得到的值从对象 row 中获取。也就是说,row 是包含了我们想要解构的属性的源对象。

综合起来,这行代码的作用是从对象 row 中提取 imageUrlList1imageUrlList2 以及其他所有属性的值,并分别赋给变量 imageUrlList1imageUrlList2params。这样,就实现了去除对象中的其中几个属性并赋值给另一个变量的需求

CSS基础

在flex布局下如何使得其中一个元素位于最右侧

1
2
3
4
5
<div class="line1" style="width: 100%;">
<el-button-group style="margin-left: auto;" v-if="userInfo.position == 'teacher'">
<el-button @click="downloadExcel()">导出Excel</el-button>
</el-button-group>
</div>

外层的 line1 为 flex 布局,而需要导出Excel按钮位于最右侧,此时采用margin-left: auto就可以轻松实现

框架

Element-UI

el-select

需要注意如何设置默认值,为 chapterName 赋上值就行

1
2
3
<el-select v-model="chapterName" placeholder="筛选章节来源">
<el-option v-for="(item, index) in modelList" :key="index" :label="item.name" :value="item.name" />
</el-select>

Bug复盘

参数错误

错误描述:

网络请求接口时,后端报参数错误,原因在于前端传递的参数

解决方法:

将作为参数传递的params使用解构赋值语法改为正确参数

1
let { imageUrlList1, imageUrlList2, ...params } = row;

网络请求卡死(提示信息:注意:请求尚未完成!)

错误描述:

在访问成绩统计这个界面时,当选择第九章时,网络请求卡死,网站也会被卡死,然后进入开发者模式的网络部分查看,发现网络请求是发出了,但是没有任何数据

解决方法:

这个解决的过程比较曲折,首先是搜索了注意:请求尚未完成这个信息,搜出来的结果是,可能前端对不合规的值没有做好判断(比如某些地方不能负数),这个情况之前也出现过,当时是 Echarts 框架的饼图部分接受的数据要大于0

所以接下来,我就去后端打印返回的饼图配置等数据,发现数据都是大于0的,这时候我就有点迷糊了

便转向前端找问题所在,首先我想到,如果是语法或者空值或者类型的问题的话,try-catch 肯定会直接抛出错误,而不是像现在这样

然后我再去 try 语句里面每个为配置赋值的语句前面加上注释,查看是进行到哪一步卡死,最后发现来自于好评Bug这个饼图的配置中,但是差评Bug这个饼图的配置,却能够正常赋值

于是我再一次回到后端,查看这两个配置的返回值有所不同,思索一些时间后,首先是数据类型一样,接着是属性一样,最后发现是长度不一样,一个长度为5,一个长度为4,在我理所应当以为长度不一样怎么会影响前端的时候

突然看到limit 5这个语句,想起来自己加这条语句是为了预防太多数据放在饼图里面,我灵光一闪,会不会是 Echarts 这个框架不予许饼图里面的数据超过4个,最后修改语句为limit 4,成功解决bug

只报错can't read length.....,可视化数据却不显示

错误原因:

没有意识到 catch 到错误后,try后面的内容不会被执行

解决方法:

去除错误的部分代码,使网页不报错,可视化数据就会进行显示,这是头疼砍头的做法,下一个Bug会具体讲怎么解决

报错can't read length of null

错误原因:

类型不对,我返回的是数据是[],但是作为 js 看来这不是一个数组,后面改为返回null,也是一样的问题

解决方法:

首先是对于前端传来的dislikedBugPieChartOption做一个空值判断

1
2
3
4
5
6
7
8
// 更新饼图配置
this.dislikedBugPieChartOption.legend.data = this.analyticsData.dislikedBugPieChartOption?.map(item => item.name) || [];

this.dislikedBugPieChartOption.series[0].data = this.analyticsData.dislikedBugPieChartOption?.map(item => ({
name: item.name,
value: item.count,
itemStyle: { color: this.getRandomColor() },
})) || [];

然后是修改判断的逻辑,不使用length==0来判断,改为非空判断,因为后来是改为返回null,如果后端返回的是[]是不能这样判断的

1
2
3
4
5
if (!this.analyticsData.dislikedBugPieChartOption) {
this.dislikedBugPieChartOptionNull = true;
} else {
this.dislikedBugPieChartOptionNull = false;
}

后端

基础

Mybatis-plus

queryWrapper.orderByDesc():降序排序

queryWrapper.orderByAsc():升序排序

queryWrapper.last(“LIMIT 1”):限定第1条数据

1
2
3
4
5
6
LambdaQueryWrapper<Bug> bugLambdaQueryWrapper = new LambdaQueryWrapper<>();
bugLambdaQueryWrapper
.eq(Bug::getChapterName, chosenChapter)
.orderByDesc(Bug::getLikedCount) // 降序排序
.orderByAsc(Bug::getDislikedCount) // 升序排序
.last("LIMIT 1"); // 限定第1条数据

queryWrapper.or():构建或者关系

1
2
3
4
5
6
queryWrapper
.eq(Bug::getIsApproved, false)
.eq(Bug::getCreateUser, userId)
.or()
.eq(Bug::getIsRemake, true)
.eq(Bug::getCreateUser, userId);

实现根据ratingList的bugId值找到list中id一样的值的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 遍历 BugDto 列表
for (BugDto bugDto : list) {
boolean found = false;

// 在 Rating 列表中查找匹配的 bugId
for (Rating rating : ratingList) {
if (bugDto.getId().equals(rating.getBugId())) {
// 找到匹配的 bugId,设置 isLiked 和 isDisliked 属性
bugDto.setIsLiked(rating.getIsLiked());
bugDto.setIsDisliked(rating.getIsDisliked());
found = true;
break; // 找到匹配的数据后可以提前结束循环
}
}

// 如果在 Rating 列表中没有找到匹配的 bugId,设置 isLiked 和 isDisliked 属性为 false
if (!found) {
bugDto.setIsLiked(false);
bugDto.setIsDisliked(false);
}
}

I/O流

参考链接:Java IO流(超详细!)-CSDN博客

目前我觉得比较常用的有下面的内容

文件专属:
java.io.FileInputStream
java.io.FileOutputStream

转换流:(将字节流转换成字符流)
java.io.InputStreamReader
java.io.OutputStreamWriter

下面展示具体代码

操作文件下载:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void writeDataToExcel(List<String[]> data, String filePath) {
try (Workbook workbook = new XSSFWorkbook()) {
// 将工作簿写入文件路径 filePath 中
try (FileOutputStream outputStream = new FileOutputStream(filePath)) {
workbook.write(outputStream);
}

System.out.println("Excel 文件已成功创建:" + filePath);

} catch (IOException e) {
e.printStackTrace();
}
}

转化为二进制流:

注意参数HttpServletResponse的设置,以及 try-catch 包裹,最后还有惯例的 outputStream.flush()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@GetMapping("/downloadExcel")
public void downloadExcel(HttpServletRequest request, HttpServletResponse response, String chosenChapter) {
try {
// 获取输出流
OutputStream outputStream = response.getOutputStream();
// 写入数据到 Excel 文件
writeDataToExcel(result, outputStream);
// 刷新流
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
// 处理异常
}
}
public static void writeDataToExcel(List<String[]> data, OutputStream outputStream) {
try (Workbook workbook = new XSSFWorkbook()) {
// 将工作簿写入文件
workbook.write(outputStream);

} catch (IOException e) {
e.printStackTrace();
}
}

功能实现

导出Excel

导入依赖

1
2
3
4
5
6
7
8
<dependencies>
<!-- 其他依赖 ... -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version> <!-- 请根据最新版本进行更改 -->
</dependency>
</dependencies>

构建表格列表

注意第一行就是表格最后的标题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@GetMapping("/downloadExcel")
public void downloadExcel(HttpServletRequest request, HttpServletResponse response, String chosenChapter) {
// 创建一个列表用于存储结果
List<String[]> result = new ArrayList<>();
// 添加固定的标题行
result.add(new String[]{"学号", "姓名", "班级", "章节", "完成bug数", "未完成bug数"});

// 构建查询条件
LambdaQueryWrapper<Grade> gradeLambdaQueryWrapper = new LambdaQueryWrapper<>();

// 获取符合条件的 Grade 列表
List<Grade> gradeList = gradeService.list(gradeLambdaQueryWrapper);

// 转换 Grade 对象为 ExcelData 对象,去掉 createTime 和 updateTime
List<ExcelData> excelDataList = new ArrayList<>();
for (Grade grade : gradeList) {
ExcelData excelData = new ExcelData();
excelData.setStudentId(grade.getStudentId());
excelData.setId(grade.getId());

// 将 ExcelData 对象添加到列表
excelDataList.add(excelData);
}

// 将 ExcelData 对象转换为字符串数组,用于写入 Excel 文件
for (ExcelData excelData : excelDataList) {
String[] rowData = new String[]{
String.valueOf(excelData.getStudentId()),
excelData.getStudentName(),
};
result.add(rowData);
}

// 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=output.xlsx");

try {
// 获取输出流
OutputStream outputStream = response.getOutputStream();
// 写入数据到 Excel 文件
writeDataToExcel(result, outputStream);
// 刷新流
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
// 处理异常
}
}

输出表格具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void writeDataToExcel(List<String[]> data, OutputStream outputStream) {
try (Workbook workbook = new XSSFWorkbook()) {
// 创建一个工作表
Sheet sheet = workbook.createSheet("Sheet1");

// 遍历数据并写入单元格
int rowNum = 0;
for (String[] rowData : data) {
Row row = sheet.createRow(rowNum++);
int colNum = 0;
for (String cellData : rowData) {
Cell cell = row.createCell(colNum++);
cell.setCellValue(cellData);
}
}

// 将工作簿写入文件
workbook.write(outputStream);

} catch (IOException e) {
e.printStackTrace();
}
}

前端部分查看下面的内容

最后总结一下我比较疑惑的点

其实就是明明我的 java 返回的是 void 类型,没有任何的参数,那前端是如何做到获取到我的数据的呢

最后了解发现,当方法返回类型为 void 时,通常表示该方法是一个”无返回值”的方法,它不会返回具体的数据给调用方。而不是说什么都不返回了

这里回顾一下目前接触过的返回方式有:JSON 格式的响应还是 HTTP 响应流,最终返回的方式取决于你在方法参数中是否加入HttpServletResponse response

虽然从常规思路上来讲,方法参数不会影响返回方式,但是在这个方法中,HttpServletResponse 参数 response 是用于设置响应的一种手段。通过该对象,你可以设置响应头、操作输出流等,从而影响最终的响应内容。

后面测试也发现不设置相应头也能够正常下载正确的文件,目前看来更多像是一种规范,不过之前处理3d模型文件时还是用到请求头了的

唤起浏览器下载功能

在正常接口下面,增加一个设置响应类型为 blob 就行

后端部分格式和上面一样

1
2
3
4
5
6
7
8
export const downloadExcel = (params) => {
return $axios({
url: '/grade/downloadExcel',
method: 'get',
params,
responseType: 'blob', // 设置响应类型为 blob
});
};

js部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
async downloadExcel() {
const params = {
chosenChapter: this.selectedchapter,
};
try {
this.loading = true; // 开始加载
// 调用下载 Excel 文件的 API
const res = await downloadExcel(params);
// 创建 Blob 对象,用于存储二进制数据
const blob = new Blob([res.data]);
// 生成下载链接
const url = window.URL.createObjectURL(blob);
// 创建一个a标签用于下载
const link = document.createElement('a');
link.href = url;
link.download = this.selectedchapter + '.xlsx'; // 指定下载文件名
document.body.appendChild(link);
// 模拟用户点击下载链接
link.click();
// 移除临时创建的a标签
document.body.removeChild(link);
this.loading = false; // 加载完成
} catch (err) {
this.$message.error('请求出错了:' + err);
this.loading = false; // 加载完成(出错)
}
},

其他部分

教训

一定要记住任何项目开始的时候都要建立仓库,方便后续版本更新,以及项目记录🥲

明确需求,提前做好调研,第一次只需要完成初步任务

Centos 7系统的Linux服务器部署前端Vue项目

部署基础条件:Mac自带的shell软件、安装Centos7系统的Linux服务器

初步参考:前端Vue项目打包部署实战教程 - 知乎 (zhihu.com)

部署步骤:

1、通过shell连接到服务器

(1)打开Mac的终端,查看左上角的菜单,选择Shell的新建远程连接

图形用户界面, 文本, 应用程序  描述已自动生成

(2)配置远程连接的相关参数,最后点击连接

![图形用户界面, 文本, 应用程序 描述已自动生成](Users/tec/Library/Group Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_image002.png)

(3)进入终端后输入密码后,显示上一次登录时间代表成功登录

img

2、安装nginx

(1) 先查看服务器是否有nginx

命令如下:

1
whereis nginx

如果出现如下界面,则代表未安装nginx

文本  描述已自动生成

(2)使用yum安装Nginx

注意如果出现报错,可能是没有配置好yum的包管理

需要输入下面的命令:

1
sudo yum install epel-release

连续输入两次y后会显示安装成功

此时再输入安装nginx的命令:

1
sudo yum install nginx

(3) 检查nginx是否安装成功

命令如下:

1
nginx -v

img

3、修改nginx配置

(1) 先查看服务器的nginx文件地址

命令如下:

1
whereis nginx

img

(2)进入/etc/nginx目录下

此时/etc/nginx则是nginx配置文件存放位置,进入该文件夹命令如下:

1
cd /etc/nginx

img

(3)使用nano文本编辑器对nginx.conf进行修改

注意如果显示不存在nano命令,需要先安装nano,方法和上面安装nginx命令一样:

1
sudo yum install nano

修改命令如下

1
sudo nano nginx.conf

运行后修改下面第一个方框的内容,修改为前端需要部署的端口

添加第二个方框的内容,指定前端打包后存放的位置

添加第三个方框的内容,具体原因会在最后一步访问页面时解释

img

输入control+x进行保存,后续会要求输入yes,以及是否改名,直接回车就可以

如果nginx正在启动中,需要重启nginx,来适配新修改的配置,命令如下:

1
nginx -s reload

4、启动nginx

(1)命令行输入启动命令

命令如下:

1
nginx

(2)查看页面是否已经启动nginx

输入网址,比如说刚才指定的10.248.6.72:9000 端口

这时候会访问不到nginx的欢迎页

原因在于防火墙没有开放9000端口

下一步将去开放9000端口

图形用户界面, 文本, 应用程序  描述已自动生成

5、防火墙开启指定端口

注意下面的操作仅适配Centos系统

(1)查看防火墙状态

1
sudo firewall-cmd --state

图表  描述已自动生成

如果返回的是 “not running”,那么需要先输入开启防火墙的命令;

1
sudo systemctl start firewalld.service

(2)开启指定端口

1
sudo firewall-cmd --zone=public --add-port=9000/tcp --permanent

显示 success 表示成功

–zone=public 表示作用域为公共的

–add-port=443/tcp 添加 tcp 协议的端口端口号为 443

–permanent 永久生效,如果没有此参数,则只能维持当前 服 务生命周期内,重新启动后失效;

(3)重启防火墙

1
sudo systemctl restart firewalld.service

系统没有任何提示表示成功!

(4)重新加载防火墙

1
sudo firewall-cmd --reload

显示 success 表示成功

这时候就可以访问到服务器的9000端口了

6、传入nginx文件

(1)上传本地dist文件

在自己的本地找到dist文件,打开对应目录的终端,并输入命令:

1
sudo scp -r dist/ sdsy@10.248.6.72:/home/www

此时会报下面的错误:

scp: stat remote: No such file or directory

scp: failed to upload directory dist/ to /home/www

原因是我们对于/home/www这个文件夹是没有权限的

(2)为文件夹赋予权限

输入命令:

1
sudo chmod 755 /home/www

(3)重复第一步的命令,完成上传

7、访问部署的前端项目

输入http://10.248.6.72:9000/#/login即可访问部署的前端项目

但是注意可能出现404无法调用接口的情况,这时候之前在nginx里面的配置就有用了,在之前的配置中就指定了api的调用地址,这样nginx才能重定向到正确的地址

Royal TSX使用

链接:怎样在Mac上SSH和FTP?完美替代XShell是哪个软件客户端?item2吗?Royal TSX! 没有比它更好 (youtube.com)

为什么学这个使用是因为之前传dist文件的时候一直提示没有该文件,后来还是通过增加文件读写权限成功传上文件了

docker部署