0%

创建视图 url_info_sencond 用以表示第二次分配的url,筛选条件:

image-20210222215647289

查询第二次分配的标注进度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SELECT
`user`.id AS `用户ID`,
`user`.username AS `用户姓名`,
Count(distinct person_url_relation.rel_person_id) AS `已标注患者总数`,
Count(distinct person_trace.trace_id) AS `已标注轨迹总数`,
Count(distinct url_info_second.url_id) AS `已分配的url数`
FROM
`user`
LEFT JOIN url_info_second ON url_info_second.user_id = `user`.id
LEFT JOIN person_url_relation ON person_url_relation.rel_url_id = url_info_second.url_id
LEFT JOIN person_trace ON person_url_relation.rel_person_id = person_trace.person_id
WHERE `user`.id > 14
GROUP BY
`user`.id

v2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SELECT * FROM 
(
SELECT
`user`.id AS user_id,
`user`.username AS user_name,
Count(distinct person_url_relation.rel_person_id) AS totalP,
Count(distinct person_trace.trace_id) AS totalT,
Count(distinct url_info_second.url_id) AS totalU
FROM
`user`
LEFT JOIN url_info_second ON url_info_second.user_id = `user`.id
LEFT JOIN person_url_relation ON person_url_relation.rel_url_id = url_info_second.url_id
LEFT JOIN person_trace ON person_url_relation.rel_person_id = person_trace.person_id
WHERE `user`.id > 14
GROUP BY
`user`.id
) AS finshed_t WHERE finshed_t.totalU <> 0

统计第二次未完成标注的url数量:

1
2
3
4
5
6
7
8
9
10
11
12
SELECT `user`.id as '用户ID', IFNULL(k.unfinshed, 0) as '未完成标注的url数'
FROM `user` LEFT JOIN
(
SELECT
url_info_second.user_id AS user_id,
Count(distinct url_info_second.url_id) AS unfinshed
FROM url_info_second
WHERE url_condition = '待标注'
GROUP BY
url_info_second.user_id
)AS k ON k.user_id = `user`.id
WHERE `user`.id > 14

合并以上两个结果:[Err] 1248 - Every derived table must have its own alias

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
SELECT 
user_id1 AS `用户ID`,
user_name AS `用户名`,
totalP AS `已标注患者人数`,
totalT AS `已标注轨迹总数`,
totalU AS `已分配的url数`,
unfinshed AS `未完成标注的url数`
FROM
(
SELECT * FROM
(
SELECT * FROM
(
SELECT
`user`.id AS user_id1,
`user`.username AS user_name,
Count(distinct person_url_relation.rel_person_id) AS totalP,
Count(distinct person_trace.trace_id) AS totalT,
Count(distinct url_info_second.url_id) AS totalU
FROM
`user`
LEFT JOIN url_info_second ON url_info_second.user_id = `user`.id
LEFT JOIN person_url_relation ON person_url_relation.rel_url_id = url_info_second.url_id
LEFT JOIN person_trace ON person_url_relation.rel_person_id = person_trace.person_id
WHERE `user`.id > 14
GROUP BY
`user`.id
) AS finshed_t WHERE finshed_t.totalU <> 0
)AS finshed
LEFT JOIN
(
SELECT * FROM
(
SELECT `user`.id as user_id2, IFNULL(k.unfinshed, 0) as unfinshed
FROM `user` LEFT JOIN
(
SELECT
url_info_second.user_id AS user_id,
Count(distinct url_info_second.url_id) AS unfinshed
FROM url_info_second
WHERE url_condition = '待标注'
GROUP BY
url_info_second.user_id
)AS k ON k.user_id = `user`.id
WHERE `user`.id > 14
) AS unfinished_url
) ON
finished.user_id1 = unfinshed_url.user_id2
) as t

统计失效url:待审核但无关联人员:

1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT * FROM
(
SELECT
`user`.id AS user_id,
`user`.username AS user_name,
Count(distinct url_info_second.url_id) AS totalU
FROM
`user`
LEFT JOIN url_info_second ON url_info_second.user_id = `user`.id
WHERE `user`.id > 14
GROUP BY
`user`.id
) AS finshed_t WHERE finshed_t.totalU <> 0
1
2
3
4
5
6
7
8
9
10
11
12
SELECT * FROM 
(
SELECT
url_id, user_id,
Count(distinct person_url_relation.rel_person_id) AS totalP
FROM url_info_second
LEFT JOIN person_url_relation ON person_url_relation.rel_url_id = url_info_second.url_id
LEFT JOIN person_trace ON person_url_relation.rel_person_id = person_trace.person_id
WHERE url_condition = '待审核'
GROUP BY
url_id
) AS u_p_count

创建 view 时,select 语句无法嵌套

存在问题的url数量:

1
2
3
SELECT user_id,COUNT(url_id)
from url_person_count WHERE totalP = 0
GROUP BY user_id

高频流数据传输

HTTP连接池

https://www.cnblogs.com/xrq730/p/10963689.html

使用线程池

减少线程创建和和销毁的时间、空间开销

1
2
3
4
This is a demo for testRunWithThreadPool!
cost: 36622ms
This is a demo for testRunWithoutThreadPool!
cost: 1783040ms

HTTP连接池

1
2
3
4
This is a demo for HttpClientWithoutPoolTest!
233ms 163ms 241ms 242ms 129ms
This is a demo for HttpClientWithPoolTest!
219ms 64ms 54ms 65ms 37ms

长短连接

HTTP1.1支持在一个TCP连接上传送多个HTTP请求和响应(复用TCP通道),减少了建立和关闭连接的消耗延迟,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点,这就是长连接,HTTP1.1默认使用长连接。

1
2
Keep-Alive: timeout=5,max=100
//表示tcp连接最多保持5秒,长连接接受100次请求就断开
  • timeout:指定了一个空闲连接需要保持打开状态的最小时长(以秒为单位)。需要注意的是,如果没有在传输层设置 keep-alive TCP message 的话,大于 TCP 层面的超时设置会被忽略。
  • max:在连接关闭之前,在此连接可以发送的请求的最大值。在非管道连接中,除了 0 以外,这个值是被忽略的,因为需要在紧跟着的响应中发送新一次的请求。HTTP 管道连接则可以用它来限制管道的使用。

此外,建立连接使用到了syns queue(半连接队列)与accept queue(全连接队列),不使用长连接而每次连接都重新握手的话,队列一满服务端将会发送一个ECONNREFUSED错误信息给到客户端,这次请求失效;即使不失效,后来的请求需要等待前面的请求处理,排队也会增加响应的时间。

  • http的keep-alive是为了复用已有连接
  • tcp的keep-alive是为了保证对端还存活
1
2
3
4
5
private CloseableHttpClient httpClient = null;
@Before
public void before() {
initHttpClient();
}
1
private CloseableHttpClient httpClient = HttpClients.custom().build();

连接池中的连接数量:假设每秒的调用量12次,根据接口的一个平均响应时长适当加一点余量,差不多设置在15~30比较合适,根据线上运行的实际情况再做调整。

http连接池缺点:过多的长连接会占用服务器资源,导致其他服务受阻;只适用于请求是经常访问同一主机(或同一个接口)的情况下。

HTTPS协议在通信层和应用层之间多了一层TLS ( Transport Layer Security,传输层安全性协议)

多路复用是HTTP2针对HTTP1.1的一大改进,可以在一个tcp连接上同时发起多个请求和接受多个响应,无需一个长连接占着一个请求。

线程池类 ThreadPoolExecutor

ThreadPoolExecutor#execute方法:线程池会对当前自身状态做出判断来决定是否创建新的worker来立即执行task,或者是将task放置在workQueue队列中。

对于线程来讲,如果不需要它返回结果则实现Runnable,而如果需要执行结果的话则可以实现Callable。在线程池同样execute提供一个不需要返回结果的任务执行,而对于需要结果返回的则可调用其submit方法。

ThreadPoolExecutor#runWorker,Worker在执行完任务后,还会循环获取任务队列里的任务执行(其中的getTask方法),也就是说Worker不仅仅是在执行完给它的任务就释放或者结束,它不会闲着,而是继续从任务队列中获取任务,直到任务队列中没有任务可执行时,它才退出循环完成任务。

并发机制的三个特性:原子性、可见性、有序性。synchronized关键字可以保证可见性和有序性却无法保证原子性。AtomicInteger的作用就是为了保证原子性。

new Thread(Runnable target) 将当前线程作为父线程,并继承父线程的线程分组、守护线程标识、classloader、栈大小等,并将传入的Runnable参数作赋值给this.target,在run()方法中,调用了target.run()

http请求步骤

  • 使用帮助类HttpClients创建CloseableHttpClient对象.
  • 基于要发送的HTTP请求类型创建HttpGet或者HttpPost实例.
  • 使用addHeader方法添加请求头部,诸如User-Agent, Accept-Encoding等参数.
  • 可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数。
  • 通过执行此HttpGet或者HttpPost请求获取CloseableHttpResponse实例
  • 从此CloseableHttpResponse实例中获取状态码,错误信息,以及响应页面等等.
  • 释放连接。无论执行方法是否成功,都必须释放连接

Java

注解

@Before:初始化方法 对于每一个测试方法都要执行一次
@After:释放资源 对于每一个测试方法都要执行一次
@Test:测试方法,在这里可以测试期望异常和超时时间
@Test(expected=ArithmeticException.class)检查被测方法是否抛出ArithmeticException异常
@Ignore:忽略的测试方法
@BeforeClass:针对所有测试,只执行一次,且必须为static void
@AfterClass:针对所有测试,只执行一次,且必须为static void
一个JUnit4的单元测试用例执行顺序为: @BeforeClass -> @Before -> @Test -> @After -> @AfterClass;
每一个测试方法的调用顺序为: @Before -> @Test -> @After;

diamond 运算符报错
1
2
java: -source 1.5 中不支持 diamond 运算符
(请使用 -source 7 或更高版本以启用 diamond 运算符)

https://blog.csdn.net/liu16659/article/details/80230164

RPC(Remote Procedure Call, 远程过程调用)TPS 系统吞吐量 Reaction Time 响应时间

消息队列

(activeMQ,rabbitMQ,kafaKa,RocketMQ,Redis等)

适用场景:以书架作类比,解耦、提速、广播、削峰这些方面的收益,超过放置书架、监控书架这些成本。

高并发、分布式架构下优于线程池

主要特点:异步、削峰、解耦

将比较耗时而且不需要即时(同步)返回结果的操作作为消息放入消息队列。

简单实现消息队列 代码:https://www.cnblogs.com/jimisun/p/10108067.html

评教模块

image-20201214162924955

教学中心,新增评教一栏,教师可以查看研究生的评教结果(选课人数、评教人数、分项平均分、总平均分、学生文字评价)

backend/modules/api/v1/models/lessonVote/AcademicMasterStudentVote.php

前端

src\views\teacher\TeachingCenter.vue

src\router\fullpath.js中设置路径、组件

1
2
3
4
5
6
7
8
{
path: "/vote-result/:lessonID",
name: "评教结果查看",
component: resolve =>
require([
"@/views/lesson/AllStudentHomeworkInfoList"
], resolve)
},

404:路由需要设置权限,路由需要对应到菜单/vote-result/:LessonID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
getTableData(){
// 获取评教选课人数、评教人数、分项平均分、总平均分、学生文字评价
this.axios.get('/lesson-vote/get-vote-analysis', {
params: {
LessonID: this.$route.params.lessonID
}
}).then(res => {
if (res.data.success) {
this.tableData.totalStudentNum=res.data.SelectNum
this.tableData.votedStudentNum=res.data.voteNum
this.tableData.eachTermAvgScore=res.data.SGScoreArr
this.tableData.allTermAvgScore=res.data.avgScore
this.tableData.allVoteText=res.data.proposal
console.log(this.tableData)
} else {
this.$message({
type: "error",
message: res.data
})
}
}).catch(util.catchError)
}

数据库

重新设计数据库表 cc_academic_master_lesson_vote_analysis 和 cc_academic_master_sg_lesson_avgscore,把评教的分析结果存起来,前端查询时直接查表,如果没有该项课程的分析结果,再去执行类似models/lessonVote/AcademicMasterStudentVote.php里的sql语句。

文字评价直接查 cc_academic_master_student_vote 表

是否查表:LID 是否已存在

后端

编写Model

分项平均分

backend/modules/api/v1/models/lessonVote/AcademicMasterSGLessonAvgScore.php

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
50
51
52
53
//获取21项SG的平均分,data 以字典存储,key:SGName,value:AvgScore
public function getSGVoteItemAvgArray($LessonID)
{
// 21条记录
$SGLessonAvgScore=AcademicMasterSGLessonAvgScore::findAll(['LessonID'=>$LessonID]);
$res=[];
if($SGLessonAvgScore===null) {
// 需要先进行平均分计算
$StudentVoteIDArr=AcademicMasterStudentVote::findAll(['LessonID'=>$LessonID])['StudentVoteID'];
$SGItemScoreArr=AcademicMasterSGItemScore::find()->where(['in','StudentVoteID',$StudentVoteIDArr])->all();
$avgScoreArr=[];
for ($i=1;$i<22;$i++)
{
$avgScore[$i]=0.0;
}
$count=count($SGItemScoreArr)/21.0;
foreach ($SGItemScoreArr as $SGItem){
$avgScoreArr[$SGItem['SGVoteID']]+=$SGItem['Score'];
}
for ($i=1;$i<22;$i++)
{
$avgScore[$i]=$avgScoreArr[$i]/$count;
}
// 保存21条记录
$transaction=Yii::$app->db->beginTransaction('SERIALIZABLE');
try {
for ($i = 1; $i < 22; $i++)
{
$model = new AcademicMasterSGLessonAvgScore();
$model['LessonID']=$LessonID;
$model['SGVoteID']=$i;
$model['SGVoteItemAvg']=$avgScoreArr[$i];

if (!$model->save()){
$res['success'] = false;
throw new \Exception('毕业状态保存失败!');
}
}
$transaction->commit();
} catch (\Exception $exception) {
$transaction->rollBack();
$res['data'] = $exception->getMessage();
return $res;
}
}
foreach ($SGLessonAvgScore as $item) {
$SGVoteName=AcademicMasterSGVote::findOne($item['SGVoteID'])['SGVoteName'];
$SGVoteItemAvg=$item['SGVoteItemAvg'];
$res['data'][$SGVoteName]=$SGVoteItemAvg;
}
$res['success'] = true;
return $res;
}
人数及总平均分

backend/modules/api/v1/models/lessonVote/AcademicMasterLessonVoteAnalysis.php

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
50
51
52
53
54
55
56
57
58
59
60
public static function getAcademicMasterLessonVoteAnalysis($LessonID)
{
// 获取评教计算结果
$LessonVoteAnalysis=AcademicMasterLessonVoteAnalysis::findOne(['LessonID'=>$LessonID]);
$res['success'] = false;
if($LessonVoteAnalysis===null)
{ // 需要进行计算
$sql = "
SELECT
count(cc_lesson_student.StudentID) AS 'SelectNum',
c.`VoteNum`,
c.`avgScore`
FROM
(
SELECT
count( k.sum ) AS 'VoteNum',
sum( k.sum ) / count( k.sum ) AS 'avgScore'
FROM
(
SELECT
t.StudentID,
sum( Score ) / 105 * 100 AS sum
FROM
(
SELECT
cc_lesson_student.StudentID,
cc_academic_master_student_vote.StudentVoteID
FROM
cc_lesson_student
LEFT OUTER JOIN cc_academic_master_student_vote ON cc_lesson_student.StudentID = cc_academic_master_student_vote.StudentID
WHERE
cc_lesson_student.LessonID=:LessonID and cc_academic_master_student_vote.LessonID=:LessonID
) AS t,
cc_academic_master_sg_item_score
WHERE
t.StudentVoteID = cc_academic_master_sg_item_score.StudentVoteID
GROUP BY
t.StudentVoteID
) AS k
)as c, cc_lesson_student
WHERE cc_lesson_student.LessonID=:LessonID";
$db = Yii::$app->db;
$data = $db->createCommand($sql)
->bindValue(':LessonID', $LessonID)
->queryAll();
$model=new AcademicMasterLessonVoteAnalysis();
$model['LessonID']=$LessonID;
$model['SelectNum']=$data['SelectNum'];
$model['VoteNum']=$data['VoteNum'];
$model['AvgScore']=$data['AvgScore'];
if (!$model->save()) {
$res['success'] = false;
$res['data'] = $model->errors;
}
$LessonVoteAnalysis=AcademicMasterLessonVoteAnalysis::findOne(['LessonID'=>$LessonID]);
}
$res['data']=$LessonVoteAnalysis;
$res['success'] = true;
return $res;
}

编写SQL语句时结合Navicat视图

文字评教

backend/modules/api/v1/models/lessonVote/AcademicMasterStudentVote.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 根据LessonID返回文字评价
public static function getVoteProposal($LessonID)
{
$proposal=[];
$sql = "
SELECT
cc_academic_master_student_vote.Proposal1,
cc_academic_master_student_vote.Proposal2
FROM
cc_academic_master_student_vote
WHERE
cc_academic_master_student_vote.LessonID =:LessonID";
$db = Yii::$app->db;
$data = $db->createCommand($sql)
->bindValue(':LessonID', $LessonID)
->queryAll();
return array_filter(array_merge($data['Proposal1'],$data['Proposal2']));
}

PHP 要过滤数组中的所有值为空的元素(false、null、’’、0),可直接用 array_filter() 函数。

编写controller

backend/modules/api/v1/controllers/LessonVoteController.php

路由/lesson-vote默认对应LessonVoteController,在main.php里写是为了美化url,路由就是 lesson-votes 了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 获取评教分析
public function actionGetVoteAnalysis()
{
$request = \Yii::$app->request;
if ($request->getIsOptions()) {
return $this->ResponseOptions($this->verbs()['get-vote-analysis']);
}
$LessonID = $request->get("LessonID");
// 获取SG分项平均分
$SGVoteItemAvgArray=AcademicMasterSGLessonAvgScore::getSGVoteItemAvgArray($LessonID);
// 获取评教总平均分及人数
$LessonVoteAnalysis=AcademicMasterLessonVoteAnalysis::getAcademicMasterLessonVoteAnalysis($LessonID);
// 获取文字评价
$proposal=AcademicMasterStudentVote::getVoteProposal($LessonID);

$res['success']=true;
if(!$LessonVoteAnalysis['success']||!$SGVoteItemAvgArray['success']){
$res['success']=false;
$res['data']='LessonVoteAnalysis/SGVoteItemAvgArray';
}
$res['data']=array_merge($LessonVoteAnalysis['data'],$SGVoteItemAvgArray['data']);
$res['data']['proposal']=$proposal;
return $res;
}

image-20201216220627346

请求后端 API 之前也需要给基础教师加入权限

或者在backend/modules/api/v1/controllers/base/BaseController.php中去掉注释

image-20201218114223567

需要注意 Divided by 0 的情况, == 0=== 0的区别

毕业设计模块

学生账户登录,本科毕业设计模块(需要管理员先修改“毕业设计“的进度),学生打印表格时保留文档样式,以word格式导出。

解决方案

  • 在后端生成 word 文件,传输到前端。src\views\graduateProject\ExportGraduateFile.vue
  • 在前端拿到表格数据,生成 word。backend/modules/api/v1/controllers/GraduateProjectController.php 的 actionExportGraduateFile($fid) 函数

    <el-main> :主要区域容器。\ :底栏容器。

参考https://www.shuzhiduo.com/A/gVdneg9N5W/

需要的插件:docxtemplater、jszip-utils、jszip、FileSaver。

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 下载题目审批表
downloadTopicApprovalForm(i) {
let _this = this;
// 判断有无附加商品来选择word模版
// 读取并获得模板文件的二进制内容
let filePath="";
switch(i){
case 1:
filePath="static/file/TopicApprovalForm.docx";
break;
case 2:
filePath="static/file/MidtermCheckForm.docx";
break;
case 3:
filePath="static/file/TeacherScoreForm.docx";
break;
case 4:
filePath="static/file/ReplyRecordForm.docx";
break;
}
JSZipUtils.getBinaryContent(filePath, function (error, content) {
// console.log("-----", content);
// input.docx是模板。我们在导出的时候,会根据此模板来导出对应的数据
// 抛出异常
if (error) {
throw error;
}
// 创建一个JSZip实例,内容为模板的内容
let zip = new JSZip(content);
// console.log("+++++", zip);
// 创建并加载docxtemplater实例对象
let doc = new Docxtemplater();
// console.log("/////", doc);
doc.loadZip(zip);
// console.log("=====", doc);
// 设置模板变量的值
doc.setData({
// 导出价格表全部信息
..._this.studentInfoForm,
// 导出价格表商品信息
..._this.infoForm,
});
try {
// 用模板变量的值替换所有模板变量
doc.render();
} catch (error) {
// 抛出异常
let e = {
message: error.message,
name: error.name,
stack: error.stack,
properties: error.properties,
};
console.log(JSON.stringify({ error: e }));
throw error;
}
// 生成一个代表docxtemplater对象的zip文件(不是一个真实的文件,而是在内存中的表示)
let out = doc.getZip().generate({
type: "blob",
mimeType:
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
});
// 将目标文件对象保存为目标类型的文件,并命名
saveAs(out, _this.studentInfoForm.StudentName + "-题目审批表.docx");
});
},

undefined问题在前端解决;

学院由后端传值时判断backend/modules/api/v1/models/graduateProject/GraduateProjectStudentModel.php getStudentInfo($id)

实验室网站

mstsc /admin /v:192.168.190.191

dbis_atd_record表

backend\models\AttendanceRule\Attendance.php

backend\controllers\attendance\AtdOffdaysController.php

time-insert\time_insert.php

frontend\controllers\TimingInsertController.php

考勤管理导入节假日

新增页面导入数据库数据

脚本:\192.168.190.206\Personal\xulicheng\XLC-DELL-530\DBISWork\DBISWEB

新闻敏感词过滤

image-20201227211648167

新建数据库表 cc_news_sensitive,在后端依次匹配并替换敏感文本内容为“**”,在前端显示出来,没有敏感词后再上传保存。

注意附件上传时的id及对应关系,不要重复上传

feat-filter-news-sensitive-words 分支

(暂时只处理”新建“,不处理”新增“)

后端

model:backend/modules/api/v1/models/news/NewsSensitiveModel.php

关键词匹配

  • 遍历文本
  • 正则表达式
  • DFA算法。前两种方法当需要匹配的关键词数量增大时,效率很低。Deterministic Finite automation,确定性的有穷状态自动机。从一个状态输入一个字符集合能到达下一个确定的状态。https://github.com/FireLustre/php-dfa-sensitive

mb_substr() 函数返回字符串的一部分, substr() 函数只针对英文字符,如果要分割的中文文字则需要使用 mb_substr()。

backend/modules/api/v1/models/news/NewsModel.php:

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
// 过滤新闻中的敏感词
public static function filterNews($data)
{
$db=Yii::$app->db;
$sql="SELECT cc_news_sensitive.WordContent FROM cc_news_sensitive";
$words=$db->createCommand($sql)->queryAll();
$sensitiveArr=[];
foreach ($words as $word) {
$sensitiveArr[]=$word['WordContent'];
}
$res['sensitive']=false;
$content=$data['Content'];

// 构建敏感词库树
$handle = SensitiveHelper::init()->setTree($sensitiveArr);
// 检测是否含有敏感词
$isLegal = $handle->islegal($content);
if($isLegal===true)
{
// 含有敏感词
$res['sensitive']=true;
// 敏感词替换为**
$filterContent = $handle->replace($content, '**');
$res['newContent']=$filterContent;
}
return $res;
}

backend/modules/api/v1/controllers/NewsController.php:

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
//添加新闻
public function actionPostNews()
{
$request = \Yii::$app->request;
if ($request->getIsOptions()) {
return $this->ResponseOptions($this->verbs()['post-news']);
}
$data = $request->getBodyParams();
$filterRes=NewsModel::filterNews($data);


if($filterRes['sensitive']===true)
{
$res['success'] = false;
$res['sensitive']=true;
$res['newContent']=$filterRes['newContent'];
$res['data']=$data;
return $res;
}
date_default_timezone_set('PRC');
$data['PublishTime'] = date('Y-m-d H:i:s', time());
$data['UpdateTime'] = $data['PublishTime'];
$data['DeleteStatus'] = 1;

$res=NewsModel::postNews($data);
$res['sensitive']=false;
return $res;
}

前端

src\views\news\NewsCompose.vue 的 postNews()

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
.then((res) => {
if (res.data.success === true) {
// 添加新闻之后,服务器返回新闻ID,传给对应的上传组件
this.NIDForm.NID = res.data.data.NID;
// 上传附件操作
this.$refs.upload.submit();
this.$message({
type: "success",
message: "发布成功!",
});
this.newsJumpTip = false;
this.goToNewsList();
} else {
if (res.data.sensitive === true) {
// 含有敏感词
this.$message({
type: "info",
message: "已将敏感词替换为**,请检查后重新上传!",
});
// 重新显示修改后的内容
this.newsForm.newsContent = res.data.newContent;
this.$refs.tinymce.setContent(this.newsForm.newsContent);
} else {
this.$message({
type: "error",
message: "发布失败,请检查信息是否都已正确填写!",
});
}
}
})

数据修改后没有在视图中同时显示,因此使用 this.$refs 获取 tinymce 组件,调用 setContent() 方法手动更新。

导入新生,user存储成功,student、assignment存储失败

1
2
3
4
if (!$stu->save()) {
$res['errors'] = $stu->errors;
throw new \Exception(implode($stu->errors));
}
1
errors: {SID: ["Sid must be a string."]}
1
{"success":false,"errors":{"TutorID":["Tutor ID must be an integer."]},"data":"student 保存失败!"}

==注意属性类型!==

PHP 转换函数: intval()、floatval()、strval()

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
//批量导入新生,记录日志,成功时返回新生SID集合
public static function batchFreshmanStudentUpload($data){
date_default_timezone_set('PRC');
$log = LogBatchModel::beforeBatch($data);
$res['success'] = false;
if($log['success']===false)
{
//创建批量导入的日志时出现错误
$res['logBatch']=$log;
return $res;
}
$LBID=$log['data']['LBID'];
$filePath=$data['FilePath'];
try{
//获得新生集合
$StudentArray = self::readFreshmanFromExcel($filePath);
}catch (Exception $e)
{
//解析 Excel 出现错误
$res['filePath']=$filePath;
return $res;
}
// 存入数据库
$key=['SID','StudentName','Grade','Education','Major','IDCard','Passwd','Tutor'];// 与模型的属性名相对应

$transaction=Yii::$app->db->beginTransaction('SERIALIZABLE');
try {
for ($i = 0; $i < count($StudentArray); $i++)
{
$row = $StudentArray[$i][0];
$student=[];
for($j=0;$j<count($key);$j++)
{
$student[$key[$j]]=$row[$j];
}
if($student['SID']===null)throw new \Exception('学号不能为空!');
// Sid must be a string.
$student['SID']=strval($student['SID']);
if($student['StudentName']===null)throw new \Exception('姓名不能为空!');
// 查询 EducationLevelID
$EducationLevel=EducationLevelModel::findOne(['LevelName' => $student['Education']]);
if($EducationLevel===null)throw new \Exception('层次不能为空!');
$EducationLevelID = $EducationLevel['ELID'];
// 查询 MID
$Major=MajorModel::findOne(['EducationLevelID'=>$EducationLevelID,'MajorName'=>$student['Major']]);
if($Major===null)throw new \Exception('专业不能为空!');
$MID = $Major['MID'];
// 创建 MajorEnrollyear 然后查询 MEYID
$majorEnrollYear=new MajorEnrollyearModel();
$majorEnrollYear->Grade=$student['Grade'];
if($majorEnrollYear===null)throw new \Exception('年级不能为空!');
$majorEnrollYear->MajorID=$MID;
$majorEnrollYear->save();
$MajorEnrollyear=MajorEnrollyearModel::findOne(['MajorID'=>$MID,'Grade'=>$student['Grade']]);
if($MajorEnrollyear===null)throw new \Exception('学号 '.$student['SID'].' 的年级、专业、层次对应关系错误!');
$student['MajorEnrollYearID']=$MajorEnrollyear['MEYID'];

// 查询 TutorID
$Tutor=TeachersModel::findOne(['TeacherName'=>$student['Tutor']]);
if($Tutor===null)
$student['TutorID']=null;
else
$student['TutorID']=intval($Tutor['TID']);

// 创建 user
if($student['Passwd']===null)$student['Passwd']='111111';
$student['Passwd']=hash_hmac('SHA256', 'cc-frontend-vue',$student['Passwd']);
$user = new User();
$user->username = $student['StudentName'];
$user->setPassword($student['Passwd']);
$user->generateAuthKey();
if (!$user->save()){
$res['errors'] = $user->errors;
throw new \Exception("user 保存失败!");
}

// 创建学生
$stu = new StudentModel();
$stu->id = $user->id;
$stu->MajorEnrollYearID =$student['MajorEnrollYearID'];
$stu->SID = $student['SID'];
$stu->StudentName = $student['StudentName'];
$stu->TutorID=$student['TutorID'];
$stu->IDCard=$student['IDCard'];
if (!$stu->save()) {
$res['errors'] = $stu->errors;
throw new \Exception("student 保存失败!");
}

// 创建 auth_assignment
$assignment = new Assignment($user->id);
$res['authRes']=$assignment->assign(['学生','游客']);
if($res['authRes']!==2)
{
throw new \Exception("auth 保存失败!");
}
}
$transaction->commit();
for ($i = 0; $i < count($StudentArray); $i++)
$res['data'][] = $StudentArray[$i][0][0];
}catch (\Exception $exception) {
$transaction->rollBack();
LogBatchModel::afterBatch(false,$LBID);
$res['data'] = $exception->getMessage();
return $res;
}
LogBatchModel::afterBatch(true,$LBID);
$res['success'] = true;
return $res;
}

新导入学生可以成功登陆。

表格上传失败时进行重置。

  1. ==vue 和 PHP 对 Excel、word 导入导出的支持==
  2. ==安装前后端项目==
  3. ==本地数据库==
Windows 系统安装指定版本的composer:

https://pkg.phpcomposer.com/#how-to-install-composer

  1. 找到并进入 PHP 的安装目录(和你在命令行中执行的 php 指令应该是同一套 PHP)。
  2. composer.phar 复制到 PHP 的安装目录下面,也就是和 php.exe 在同一级目录。
  3. 在 PHP 安装目录下新建一个 composer.bat 文件,并将下列代码保存到此文件中。
1
@php "%~dp0composer.phar" %*

最后重新打开一个命令行窗口试一试执行 composer --version 看看是否正确输出版本号。

yii2 项目初始化命令:.\init

mklink 命令是将文件或目录建立双向连接, 该变任何一方都会发生变化, 其主要文件链接有三: 符号链接(软件接), 目录连接(软件接), 文本文件链接(硬连接), 可以这样理解, 软连接, 是建立快捷方式, 硬连接, 是进行复制

在 cmd 里面输入: mklink /? 来查看 mklink 命令和参数的使用

1
mklink /D cc C:\Users\pcy\Documents\DBISWork\cc-computer-backend
使用表前缀

https://www.yiiframework.com/doc/guide/2.0/zh-cn/db-dao

1
2
3
4
5
6
7
'components' => [
// ...
'db' => [
// ...
'tablePrefix' => 'tbl_',
],
],

src\api\http.js

1
2
// 如果出现问题,就直接 hard code 写死域名
Instance.defaults.baseURL = 'http://localhost:8081/cc-computer-homepage/backend/web/index.php/api/v1'

当前:PHP/5.6.40

Yii2 对 word、Excel 上传下载的支持——PHPOffice

PHPExcel was officially deprecated in 2017 and permanently archived in 2019. The project has not be maintained for years and must not be used anymore. All users must migrate to its direct successor PhpSpreadsheet, or another alternative.

PhpSpreadsheet

PHP 7.2+

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

require 'vendor/autoload.php';

use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', 'Hello World !');

$writer = new Xlsx($spreadsheet);
$writer->save('hello world.xlsx');
Architecture

01-schematic.png

Creating a spreadsheet

Loading a Workbook from a file:

1
2
3
$inputFileName = './sampleData/example1.xls';
/** Load $inputFileName to a Spreadsheet object **/
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($inputFileName);

Creating a new workbook:

1
2
/** Create a new Spreadsheet Object **/
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
Accessing cells

Setting a cell value by coordinate:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Set cell A1 with a string value
$spreadsheet->getActiveSheet()->setCellValue('A1', 'PhpSpreadsheet');

// Set cell A2 with a numeric value
$spreadsheet->getActiveSheet()->setCellValue('A2', 12345.6789);

// Set cell A3 with a boolean value
$spreadsheet->getActiveSheet()->setCellValue('A3', TRUE);

// Set cell A4 with a formula
$spreadsheet->getActiveSheet()->setCellValue(
'A4',
'=IF(A3, CONCATENATE(A1, " ", A2), CONCATENATE(A2, " ", A1))'
);

$spreadsheet->getActiveSheet()
->getCell('B8')
->setValue('Some value');

Documentation

PHPWord

The current version of PHPWord supports Microsoft Office Open XML (OOXML or OpenXML), OASIS Open Document Format for Office Applications (OpenDocument or ODF), and Rich Text Format (RTF).

PHPWord requires the following:

  • PHP 5.3.3+
  • XML Parser extension
  • Zend\Escaper component
  • Zend\Stdlib component
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
require_once 'bootstrap.php';

// Creating the new document...
$phpWord = new \PhpOffice\PhpWord\PhpWord();

/* Note: any element you append to a document must reside inside of a Section. */

// Adding an empty Section to the document...
$section = $phpWord->addSection();
// Adding Text element to the Section having font styled by default...
$section->addText(
'"Learn from yesterday, live for today, hope for tomorrow. '
. 'The important thing is not to stop questioning." '
. '(Albert Einstein)'
);

// Saving the document as OOXML file...
$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007');
$objWriter->save('helloWorld.docx');

前端发起ajax请求,后端生成excel并下载,同时需要在header头中,带上token验证信息,实现如下:

1
2
3
4
5
6
7
$filename = 'demo.xlsx';
$objWriter = \PHPExcel_IOFactory::createWriter($objectPHPExcel, 'Excel2007');
ob_start();
$objWriter->save("php://output");
$xlsData = ob_get_contents();
ob_end_clean();
return Api::success(['filename' => $filename, 'file' => "data:application/vnd.ms-excel;base64," . base64_encode($xlsData)]);
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
$('.download').click(function(){
var url = "http://xxxx.com/group/bi/export";
var params = {
from_date: '2017-09-01',
to_date: '2017-09-08',
group_id: 1
};
$.ajax({
type:'POST',
url: url,
data: params,
beforeSend: function(request) {
request.setRequestHeader("Authorization", "token信息,验证身份");
},
success: function(redata) {
// 创建a标签,设置属性,并触发点击下载
var $a = $("<a>");
$a.attr("href", redata.data.file);
$a.attr("download", redata.data.filename);
$("body").append($a);
$a[0].click();
$a.remove();
}
});
});

PHP 操作 office 文档

vue使用FormData上传excel文件,使用blob下载文件

BLOB (binary large object) 二进制大对象

导入excel文件
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
inputCustomProduct(e){
//声明一个FormDate对象
let formData = new FormData();
let file = e.target.files[0]
//把文件信息放入对象中
formData.append( "excelFile", file);
// let params = new URLSearchParams(formData) //当请求没有转换成formData时使用
if(file.type == "application/vnd.ms-excel" || file.type == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"){
this.$ajax({
method: "post",
url: "",
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
}).then( res => {
if(res.code === 200){
this.$message({
message: "导入成功",
type: "success"
})
}else {
this.$message({
message: "导入失败",
type: "error"
})
}
})
}else {
this.$message({
message: "请选择excel文件!",
type: "error"
})
}
//此处必须将控制导入的input值进行置空,否则不会触发change事件,会导致同一个文件不能二次导入
document.getElementById('up').value = null;
},
导出Excel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
outputCustomProduct() {
this.$ajax({
method: "",
url: "",
params: {

},
responseType: 'blob'
}).then(res => {
console.log(res)
//调用成功,在html中创建一个a元素
let aTag = document.createElement('a');
//创建一个blob对象
let blob = new Blob([res], {
//Excel文件版本
type: "multipart/form-data;charset=utf-8", //application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
});// 这个content是下载的文件内容,自己修改
aTag.download = '订单导入模板.xls';// 下载的文件名
aTag.href = window.URL.createObjectURL(blob); //创建一个URL对象
aTag.click();
document.body.removeChild(aTag)
window.URL.revokeObjectURL(blob); //释放URL对象
})
},

Vue Element UI 实现文件excel的上传及下载2种方式(文件流及a标签)

上传使用Element ui 里的 el-upload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<el-form-item label="上传">
<el-upload
class="upload-demo"
ref="upload"
action="#"
:limit="1"
:http-request="uploadOk"
:on-preview="handlePreview"
:before-upload="beforeAvatarUpload"
:on-remove="handleRemove"
:file-list="fileList"
:auto-upload="true">
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<div slot="tip" class="el-upload__tip">只能上传xls/xlsx文件</div>
</el-upload>
</el-form-item>

action 必选参数,上传的地址
:limit 最大允许上传个数
:http-request 覆盖默认的上传行为,自定义上传
:on-preview 文件上传时的钩子
:before-upload 上传文件之前的钩子
:on-remove 文件列表移除文件时的钩子
:file-list 上传的文件列表
:auto-upload 是否在选取文件后立即进行上传

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
 // 上传前验证
beforeAvatarUpload(file) {
let Xls = file.name.split('.');
if(Xls[1] === 'xls'||Xls[1] === 'xlsx'){
return file
}else {
this.$message.error('上传文件只能是 xls/xlsx 格式!')
return false
}
},
handleRemove(file, fileList) {
console.log(file, fileList);
},
handlePreview(file) {
console.log(file);
},
// 上传成功
uploadOk(val){
let fd = new FormData() // 上传文件必须为FormData 所以这边New FormData() ,把上传文件的File填进去。
fd.append('upload_file',val.file)
fd.append('dir_name','contract')
// console.log(fd.upload_file) 错误
// console.log(fd.get("upload_file") 正确
this.$post('/upload/uploadFile', fd).then((result) => {
if (result.code == 200) {
this.$message({
message: '上传成功',
type: 'success'
});
// 接收excel文件地址
this.addForm.addurl = result.data.file_url
}else{
this.message(result.msg)
}
})
},
下载

a标签方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 下载源文件
handleDownload: function(index, row){
if(row.contract_file_url == ''){
this.$message({
message: '没有源文件',
type: 'warning'
})
}else{
// 创建 a 标签
let link = document.createElement('a')
// href 链接, row.contract_file_url 是从表格里拿到的下载文件地址
link.setAttribute('href', row.contract_file_url)
// 自执行点击事件
link.click()
}
},

image-20201112194147771

==管理员批量导入新生名单==

==整理会议论文列表==

不用添加老师;导入毕业生名单是另外的功能,只需要学号,把对应学号的学生的毕业状态置位即可

新账户密码:NKcc+身份证后六位,没有身份证的话,用NKcc+学号。后台处理一下 或者 设置密码一列从excel里读取,让使用者自己录入密码。

Excel需要的列:用户名、年级、层次(本、学硕、专硕、博)、身份证号、密码

会议论文列表

https://m.ais.cn/goodMeet

电子信息,计算机科学类,ei检索。偏系统,软件的。找下会议论文列表。有的是会议网站就有,没有的看看谷歌学术或者百度学术有没有搜索指定会议的。

搜索论文:https://www.engineeringvillage.com/search/quick.url

工程索引(The Engineering Index,简称EI)

git储藏

git stash 将当前未提交到本地(和服务器)的代码推入到Git的栈中,操作后的工作区间和上一次提交的内容是完全一样的。

git checkout -b feat-import创建分支并进行切换。git新功能用的前缀是 feat

git stash pop 恢复之前缓存的工作目录。将缓存堆栈中的第一个stash删除,并将对应修改应用到当前的工作目录下。

git stash apply将以前的工作应用回来,但并不删除stash拷贝。

Windows徽标键+ V,打开剪贴板历史记录面板

批量导入毕业生

下载Excel空表

使用 a 标签的 download 属性

1
<a href="src/assets/graduate.xlsx">下载毕业生信息excel表格</a>

image-20201114093539717

路径中 @ 和 . 的区别

import store from ‘. /vuex/store’是相对路径,代表当前路径同级下vuex下的store
import store from ‘ @/vuex/store’也是相对路径,和上面意思差不多,但是具体代表什么路径,要看webpack里面对于@是如何配置的,比如:

1
2
3
4
5
alias: {
'vue$': 'vue/dist/vue. esm.js',
'@': resolve('src'),
Axios':' axios
}

上传Excel

el-upload组件

action 必选参数,上传的地址

手动提交:

1
this.$refs.upload.submit();

显示跨域问题

1
Access to XMLHttpRequest at 'http://localhost:8081/cc-computer-homepage/backend/web/index.php/api/v1/students/batch' from origin 'http://localhost:8084' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
  • 后端没有提供API

backend/config/main.php中配置方法路径和方法名。controller中补充 verbs。在controller中编写方法。

控制器 ID user 以复数形式出现在 users 末端。要获取请求参数,调用 request 组件的 get()-detail) 方法和 post()-detail) 方法。 他们分别返回 $_GET$_POST 的值。先发一个 OPTION 请求,成功之后再发 POST 请求。

1
2
// 设置上传的请求头部
headers: { Authorization: "Bearer " + getCookie("access-token")},
  • 路由管理,数据库中没有该权限

PHP调试

1
return ["success"=> false, "data"=>$UploadTime];

返回 json 格式的数据到输出流,在 Network –> Response 中可以查看

文件保存

调用 common/components/fileUpload.php 的 uploadFile() 函数,将文件保存到设置的路径。拿到该路径存入日志。

获取当前用户 ID

localStorage 和 sessionStorage 属性允许在浏览器中存储 key/value 对的数据。localStorage 用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去删除。

UserID: getCookie(“realid”)

从前端获取是不安全的,直接从后端获取 UserID :

1
$user_id = \Yii::$app->user->identity->getId();

Yii::$app->user->identity 返回当前登录用户或当前用户未通过身份验证(表示访客)的身份类的实例。

PHPExcel

composer 换源
1
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
使用 phpExcel

没找到PHPExcel的命名空间,暂时使用了简单粗暴的方式来引入第三方类库:

在web目录中index.php中:

1
require(__DIR__ . '/../../vendor/phpoffice/phpexcel/Classes/PHPExcel/IOFactory.php');

在 controller 中使用的时候 new \IOFactory 需要加一个反斜杠:

1
$inputFileType = \PHPExcel_IOFactory::identify($path);//自动识别Excel版本
读取指定列的信息

backend/modules/api/v1/models/student/StudentModel.php :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//解析Excel表格,返回毕业生学号数组
private static function readGraduateFromExcel($path)
{
//自动识别Excel版本
$inputFileType = \PHPExcel_IOFactory::identify($path);
$objReader = \PHPExcel_IOFactory::createReader($inputFileType);
$objPHPExcel = $objReader->load($path);
$sheet = $objPHPExcel->getSheet(0);//读取第一个sheet

$highestRow = $sheet->getHighestRow();//取得总行数
$result = [];
//过滤第一行表头,内容从Excel的第二行开始读
for ($i = 2; $i <= $highestRow; $i++) {
//列的索引是从0开始计数
$result[] = $sheet->getCellByColumnAndRow(0, $i)->getValue();
}
return $result;
}
没有考虑学号为空的情况,即:必须填写要添加的毕业生的学号
创建自己的packagist 安装包
  1. 创建GitHub项目并clone到本地

  2. 在项目下打开cmd,输入命令composer init用来创建composer.json,接下来需要在控制台中设置项目名称、描述、作者等信息,方括号里面是默认的信息。其中,资源包的最低稳定版本,默认为 stable。 可选 -dev、-patch、-alpha、-beta 或 -RC;包类型默认选择 library ,License []: mit

  3. 在composer文件中添加以下代码,使安装自动加载

    1
    2
    3
    4
    5
    "autoload": {
    "psr-4": {
    "joyce\\myExcel\\": ""
    }
    },

    就可以使用

    1
    use joyce\myExcel xxx
  4. 进入 packagist 官网(https://packagist.org)注册账户并在设置里面关联自己的GitHub账户,使用GitHub账户登录,点击submit 提交GitHub项目地址,点击check。

  5. 部署自动更新( packgist 官网有教程)

  6. 发布版本 (改动之后需要发布版本 packagist 里面才会更新)

    1
    2
    3
    git log --oneline
    git tag -a '1.0.1' xxxxxx
    git push --tags
  7. 遇到的问题:

    1
    2
    [UnexpectedValueException]
    Error while installing pcy/my-php-excel-packgist, composer-plugin packages should have a class defined in their extra key to be usable.

    是因为包类型选择了 composer-plugin

添加批量导入操作的日志

对于毕业生,excel 表格中只需要有学号信息。在数据库表 cc_student 中根据学号 SID 修改记录,将 IsGraduated 和 StudyStatus 两个字段对应改为 1 和 已毕业。

数据库表 cc_log_batch

用于记录在批量导入过程中,

参考批量创建活动 backend/modules/api/v1/models/activity/ActivityParticipationModel.php

1
$transaction=Yii::$app->db->beginTransaction('SERIALIZABLE');

MySQL数据库提供的四种隔离级别:

① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。

② Repeatable read (可重复读):可避免脏读、不可重复读的发生。

③ Read committed (读已提交):可避免脏读的发生。

④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。

LogBatchModel.php

参照backend/modules/api/v1/models/log/LogApiModel.php编写 LogBatchModel .

核心验证器 safe:该验证器并不进行数据验证。

  • “message”:”Setting unknown property: backend\modules\api\v1\models\log\LogBatchModel::ResStatus”

原因:afterBatch() 函数中 copy-paste 错误。

image-20201127215353617

backend/modules/api/v1/models/student/StudentModel.php:
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
//批量导入毕业生,记录日志,成功时返回毕业生SID集合
public static function batchGraduateStudentUpload($data){
date_default_timezone_set('PRC');
$log = LogBatchModel::beforeBatch($data);
$res['success'] = false;
if($log['success']===false)
{
//创建批量导入的日志时出现错误
$res['logBatch']=$log;
return $res;
}
$LBID=$log['data']['LBID'];
$filePath=$data['FilePath'];
try{
//获得毕业生的SID集合
$SIDArray = self::readGraduateFromExcel($filePath);
}catch (Exception $e)
{
//解析 Excel 出现错误
$res['filePath']=$filePath;
return $res;
}

$transaction=Yii::$app->db->beginTransaction('SERIALIZABLE');
try {
for ($i = 0; $i < count($SIDArray); $i++)
{
$SID = $SIDArray[$i];
$newStatus['IsGraduated']=1;
$newStatus['StudyStatus']='已毕业';
//将 IsGraduated 和 StudyStatus 两个字段对应改为 1 和 已毕业
self::updateStudent($SID,$newStatus);
}
$transaction->commit();
$res['data'] = $SIDArray;
} catch (\Exception $exception) {
$transaction->rollBack();
LogBatchModel::afterBatch(false,$LBID);
$res['data'] = $exception->getMessage();
return $res;
}
$res['success'] = true;
LogBatchModel::afterBatch(true,$LBID);
return $res;
}

测试 batchGraduateStudentUpload() ,batch-graduate 接口 Network -> Response:

1
{"success":true,"data":[1713650,1713429,1713032]}

==UpdateStudent() 接口或许有点问题==,在batchGraduateStudentUpload() 中重写了修改学生毕业状态的语句。

在前端给出提示,显示已修改为毕业生的学号或给出错误提示:

image-20201129162233663

src\views\student\StudentInfoManage.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 文件上传成功处理
handleFileSuccess(response, file, fileList) {
this.upload.open = false;
this.upload.isUploading = false;
this.$refs.upload.clearFiles();
if(response.success===true){
this.$alert("以下学号的学生已修改为毕业生:\n"+response.data, "导入结果", { dangerouslyUseHTMLString: true });
}
else{
this.$alert("导入失败,请检查Excel数据格式", "导入结果", { dangerouslyUseHTMLString: true });
}
// 重新获取列表
this.searchStudentInfoManage();
},

批量导入新生

在批量导入毕业生的基础上,按照相同逻辑实现导入新生的功能。尽量整合两个模块。

1
2
3
4
5
6
7
8
9
// 导入新生
handleImportFreshman() {
this.upload.filePath="src/assets/freshman.xlsx";
this.upload.title = "导入新生";
this.upload.url=this.DownBaseUrL + "/students/batch-freshman";
this.attachmentInfo.ModuleName="batchFreshmanStudentUpload";
// console.log("clicked 导入新生 button");
this.upload.open = true;
},

设计新生excel

学号、姓名、年级、层次、专业、身份证号、密码、导师。

层次和专业设置下拉菜单。将模板 freshman.xlsx 放入 cc-frontend\src\assets 文件夹下。

  • 修改Excel表格,修改可选项、必填项,增加导师列 (数据 –> 数据验证)

数据库表

需要进行插入的表:cc_major_enrollyear、cc_student、cc_user、cc_auth_assignment(如果是导入学生的话需要插两条:学生、游客)

关于“层次”属性需要查表 cc_education_level ,考虑到条目不多,可以直接在后端做映射而不去数据库查表。为了避免数据库有变更,还是查表吧。

添加 user、student 参照 backend/modules/api/v1/controllers/UserController.php 的 actionSignup()。cc_student 的 EnrollDate 设置为上半年/下半年

添加 assignment 参照 vendor/clement/yii-rest-rbac2.0/controllers/AssignmentController.php 的 actionAssign($id)

backend/config/main.php:"class" => "clement\\rest\Module",

可能会存储失败的情况:学号为空、找不到导师、找不到 MajorName & EducationLevelID 对应的 MID(cc_major表)、找不到 年级 & MajorID 对应的 MEYID(cc_major_enrollyear表)

密码明文默认111111

password加密传输

常用的签名算法:MD5 和 HMAC-SHA256

  • 验证 yii2 和 vue 对密码的 hash 算法结果是否一致,用户登录或新建单个用户时输入密码后由前端进行 HMAC-SHA256 加密存入数据库(src\components\admin\UserInfo.vue),但批量导入数据库时是由后端来加密密码存入数据库,需要保持加密结果一致。
1
2
import {sha256} from 'js-sha256';
console.log(sha256.hmac('111111', 'cc-frontend-vue'));

image-20201130221713885

1
2
(property) Hash.hmac: Hmac
(secretKey: string, message: Message) => string

注意与 PHP 参数的顺序不同:

1
function hash_hmac ($algo, $data, $key, $raw_output = false) {}
1
2
3
4
<?php
$hash = hash_hmac('SHA256', 'cc-frontend-vue','111111');
echo "result = " . $hash;
?>

xampp/htdocs/dashboard/phpinfo.php,访问http://localhost:8081/dashboard/phpinfo.php

后端 batch-freshman 接口

backend/config/main.php: (‘controller’ => ‘api/v1/student’)

1
'POST,OPTIONS batch-freshman' => 'batch-freshman-student-upload',

backend/modules/api/v1/controllers/StudentController.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//批量导入新生
public function actionBatchFreshmanStudentUpload()
{
$request = \Yii::$app->request;
if ($request->getIsOptions()) {
return $this->ResponseOptions($this->verbs()['batch-freshman-student-upload']);
}
$upload = new fileUpload(false, 2);
$res = $upload->uploadFile();
if ($res->{'success'} == false) {
return ['success' => false, 'data' => $res->{'error'}];
}
$temp['FilePath']=$res->{'data'}['Destination'];
$temp['UploadTime']=$request->post('UploadTime');
$temp['UserID'] = \Yii::$app->user->identity->getId();
$temp['ModuleName'] = $request->post('ModuleName');
$temp['IsImported']=0;
return StudentModel::batchFreshmanStudentUpload($temp);
}

解析新生Excel表格

backend/modules/api/v1/models/student/StudentModel.php :

  • 如果某一格为空?
1
public function rangeToArray($pRange = 'A1', $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
  • 由于在后面的列中编写了下拉列表,因此不能用函数来获取总行数总列数?
1
2
$highestRow = $sheet->getHighestRow();//取得总行数
$highestCol = $sheet->getHighestColumn();//取得总行数

解决:将下拉列表的选项放在新的 sheet 中

{“success”:true,

“data”:[[[2011430,”张一”,2020,”本科”,null,”123654166623564612”,111111,”温延龙”]],[[2011431,”张二”,2017,”博士”,”信息安全”,”369852232365894626”,111111,”宋春瑶”]],[[2011432,”张三”,2018,”硕士”,”软件工程”,null,111111,”王刚强”]],[[null,”张四”,2019,”专业硕士”,”运筹学与控制论”,null,null,”温延龙”]],[[2011434,”张五”,2020,”本科”,”计算机科学与技术”,null,111111,”袁晓洁”]]],

“logBatch”:{“success”:true,”data”:{“FilePath”:”/storager/2020_year/12_month/02_day/1c7b9bac371e8038e26b3081fee650e3/1c7b9bac371e8038e26b3081fee650e3.xlsx”,”UploadTime”:”2020-12-02 10:56:00”,”UserID”:64041,”ModuleName”:”batchFreshmanStudentUpload”,”IsImported”:0,”LBID”:18}}}

batchFreshmanStudentUpload($data) 得到 excel 解析内容后,先对异常数据进行预处理。

新生数组数据处理

哪些不能为空?MajorEnrollYearID –> 专业、年级、层次都不能为空;StudentName;SID。

创建 user 时仿照 backend/modules/api/v1/controllers/UserController.php 的 actionSignup()

测试 batchFreshmanStudentUpload($data) 报错:

1
{"success":false,"data":"Undefined offset: 1"}

image-20201203120812297

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$debugOffset=true;
if($debugOffset===true)
{
for ($i = 0; $i < count($StudentArray); $i++)
{
$row = $StudentArray[$i][0];
$student=[];
for($j=0;$j<count($key);$j++)
{
$student[$key[$j]]=$row[$j];
}
$res['data'][]=$student;
}
for ($i = 0; $i < count($StudentArray); $i++)
$res['SID'][] = $StudentArray[$i][0][0];
return $res;
}

测试:

1
2
3
$res['StudentArray']=$StudentArray;
$res['Student']=$StudentArray[1];
$res['SID']=$StudentArray[1][0][0];

结果:

1
2
3
4
5
6
7
{
"success":false,
"StudentArray":
[[[2011430,"张一",2017,"本科","自动化","123654166623564612",111111,null]],[[2011431,"张二",2017,"本科","信息安全","369852232365894626",111111,null]],[[2011432,"张三",2018,"博士","软件工程",null,111111,"王刚(计)"]],[[2011431,"张四",2019,"硕士","计算机应用技术",null,123456,"温延龙"]],[[2011434,"张五",2017,"本科","计算机科学与技术",null,111111,null]]],
"Student":[[2011431,"张二",2017,"本科","信息安全","369852232365894626",111111,null]],
"SID":2011431
}

是因为每行多了一层数组。

提交

前端不需要提交 package-lock.json、dev.env.js、http.js、util.js

后端:

1
2
3
git add .
git commit -m "feat: 批量导入毕业生名单;批量导入新生名单"
git push origin feat-import:feat-import

自我介绍

项目介绍

java

多态

多态是同一个行为具有多个不同表现形式或形态的能力。即同一个接口,使用不同的实例而执行不同操作。多态是设计模式中的策略模式的关键。

设计模式是软件开发人员在软件开发过程中遇到的一般问题的解决方案、经验总结。设计模式代表着最佳的实践。参考书中提到的有 23 种设计模式,比如工厂模式、单例模式、代理模式、策略模式、MVC 模式等。

策略模式指一个类的行为或其算法可以在运行时更改。策略模式使用的是面向对象的继承和多态机制。Context 是一个使用了某种策略的类,策略模式的实现:

  1. 创建一个接口 Strategy
  2. 创建接口的实体类
  3. 创建 Context 类
  4. 使用 Context 来查看当它改变策略 Strategy 时的行为变化。

img

垃圾回收

分代是指 Java 虚拟机根据对象存活的周期不同,将堆内存划分为新生代(细分为伊甸区、幸存者区)和老年代。根据各个代的特点采用最适合的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成。而老年代中因为对象存活率高、没有额外空间对他进行分配担保,就必须使用“标记-清理”或者“标记-整理”算法来进行回收。

垃圾回收算法:

  1. 复制算法:将内存区域划分成相同的两个内存块。每次仅使用一半的空间,JVM生成的新对象放在一半空间中。当一半空间用完时进行GC,把可到达对象复制到另一半空间,然后把使用过的内存空间一次清理掉。新生区的对象往往内存较小,可以使用复制算法,将对象复制到另一半内存上就可以清除,不会造成内存碎片。
  2. 标记-清除算法:标记-清除算法对根集合进行扫描,对存活的对象进行标记。标记完成后,再对整个空间内未被标记的对象扫描,进行回收。不需要进行对象进行移动。标记、清除过程效率低,产生大量不连续的内存碎片,提高了垃圾回收的频率。
  3. 标记-整理算法:进行对象的标记,但后续不直接对可回收对象进行清理,而是将所有的存活对象往一端空闲空间移动,然后清理掉端边界以外的内存空间。解决了标记-清理算法存在的内存碎片问题。仍需要进行局部对象移动,一定程度上降低了效率。

Spring IOC

开发者使用Spring框架主要是做两件事:①开发Bean;②配置Bean。对于Spring框架来说,它要做的就是根据配置文件来创建Bean实例,并调用Bean实例的方法完成”依赖注入”——这就是所谓IoC的本质。

IOC is Inversion Of Control. It means that the application won’t manage it’s lifecycle/flow of control itself. The framework (Spring) will. So you just tell the framework how you want (some) elements of your app to work together.

DI is dependency injection. It’s a specific kind of IOC where the framework will manage the dependencies that an object uses (you can call dependency: a service).

Bean is an object managed by the Framework.

map

map 接口:存储键值对,能高效通过 key 快速查找 value。

1
2
3
4
5
6
7
import java.util.HashMap;
import java.util.Map;

Map<String, String> map = new HashMap<String, String>();
map.put("key1", "value1");
map.get("key1");
map.remove("key1");

map 常用的实现类:HashMap、Hashtable、TreeMap 等。

Hashtable:与 HashMap类似,不同的是 key 和 value 的值均不允许为 null; 它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢。

TreeMap:根据键(key)排序,默认是按升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。TreeMap不允许key的值为null。非同步的。

hashmap:根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度。HashMap最多只允许一条记录的键为Null(多条会覆盖);允许多条记录的值为 Null。

img

https://blog.csdn.net/login_sonata/article/details/76598675

hashmap 的实现结构:数组+链表+红黑树(JDK1.8增加了红黑树部分)HashMap就是使用哈希表来存储的。哈希表为解决冲突,可以采用开放地址法和链地址法等来解决问题,Java中HashMap采用了链地址法

存储结构

Node[] table的初始化长度length(默认值是16),Load factor为负载因子(默认值是0.75),threshold是HashMap所能容纳的最大数据量的Node(键值对)个数。threshold = length * Load factor,超过这个数目就重新resize(扩容),扩容后的HashMap容量是之前容量的两倍。

rehash

多线程

threadLocal的实现原理;线程池的实现原理,线程运行完run方法就被回收了吗;并发锁的实现;

http连接池

对于线程来讲,如果不需要它返回结果则实现Runnable,而如果需要执行结果的话则可以实现Callable。在线程池同样execute提供一个不需要返回结果的任务执行,而对于需要结果返回的则可调用其submit方法。

https://blog.csdn.net/weixin_43138919/article/details/88948588

线程池类ThreadPoolExecutor

  1. 当线程池内线程数小于corePoolSize时,新提交的任务都会创建一个新的线程来执行。
  2. 当线程池内线程数等于corePoolSize时,新提交的任务会放入workQueue中,等待线程池中任务调度。
  3. 当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务。
  4. 当提交任务数超过maximumPoolSize时,新提交任务会交给RejectedExecutionHandler处理。
  5. 当线程池中超过corePoolSize时,空闲时间达到keepAliveTime时,关闭空闲线程。
  6. 当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭 。

ThreadPoolExecutor#runWorker,Worker在执行完任务后,还会循环获取任务队列里的任务执行(其中的getTask方法),也就是说Worker不仅仅是在执行完给它的任务就释放或者结束,它不会闲着,而是继续从任务队列中获取任务,直到任务队列中没有任务可执行时,它才退出循环完成任务。

用BlockingQueue数据结构存储任务。

LinkedBlockingQueue

  1. 使用takeLock和putLock进行并发控制,添加和删除可以同时进行,提高吞吐量。
  2. 对每个锁都提供了一个Condition用来挂起和唤醒其它线程。
  3. put 和 take 方法 (空/满 –> 阻塞、唤醒)
  4. LinkedBlockingQueue与ArrayBlockingQueue:
阻塞队列 LinkedBlockingQueue ArrayBlockingQueue
队列大小 可以有界 可以无界Integer.MAX_VALUE 有界,初始化时指定大小
存储容器 node节点的链表 数组
插入和删除 会产生额外的对象实例 不会产生额外的对象实例
添加和删除的并发 可以同时进行 用的是同一个锁,两个操作互斥

编程

创建三个线程,按序输出1, 2, 3 (\lab\ref\backend\src\main\java\test\t1.java)

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
package test;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class t1 {
private static Lock lock=new ReentrantLock();
private static int state=0;

private static class Worker extends Thread{
int mod;
int v;

Worker(int mod,int v){
this.mod=mod;
this.v=v;
}
@Override
public void run()
{
while(true){
lock.lock();
if(state%mod==v){
System.out.println(v+1);
state++;
}
lock.unlock();
}
}
}
public static void main(String[] args) {
Worker a=new Worker(3,0);
Worker b=new Worker(3,1);
Worker c=new Worker(3,2);
a.start();
b.start();
c.start();
}
}

但是这样的代码中有忙等待,性能很低,随着线程数数量增加的话,这样的程序是不适用的。

计算机网络

http报文结构

http请求报文由四个部分组成:请求行、请求头部、空行、请求数据

  • 请求行:方法 URL 版本,如:GET /data/info.html HTTP/1.1
  • 请求头部:用来准确描述正在获取的资源、服务器或者客户端的行为,定义了HTTP事务中的具体操作参数。如:Accept用于说明可接受的响应内容类型,Content-Type用于说明请求体的MIME类型 (用于POST和PUT请求中),Origin用于说明发起一个针对跨域资源共享的请求(该请求要求服务器在响应中加入一个Access-Control-Allow-Origin的消息头,表示访问控制所允许的来源)。常用的HTTP请求头与响应头
  • 空行告诉服务器请求头部到此为止。

HTTP响应报文也由四部分组成:响应行、响应头、空行、响应体。响应行:版本 状态码 描述,如:HTTP/1.1 200 OK

http和https的区别
  1. HTTPS协议需要到ca(数字证书认证机构)申请证书,可能需要一定的费用;
  2. http(超文本传输协议)是明文传输;HTTPS是ssl加密传输协议;
  3. http使用80端口,HTTPS使用443端口;
  4. http是无状态的简单连接;HTTPS是由SSL+HTTP构建的有加密传输、身份认证的网络协议,更加安全。

HTTPS协议的主要作用:建立一个信息安全通道保证数据传输的安全;确认网站的真实性。

img

为什么使用对称加密结合非对称加密

对称加密:加密和解密使用同一秘钥,秘钥通过网络传输。若对称秘钥被截获,仍有可能出现数据被中间人篡改或冒名顶替的问题。

非对称加密:A生成公钥和私钥,私钥保留在本地不再在网络上进行传输,公钥是分发给其他人(一般是下发的证书中包含公钥然后返回给请求者),公钥私钥是一对,私钥加密的只有公钥才能够解密,若能成功解密则证明跟你交互的那个人是当初给你公钥的那个人。A给B发消息,用A的私钥加密,B收到之后用A的公钥解密。即A、B要使用非对称加密,需要各自保留对方的公钥。客户端和服务器交互,服务端一个私钥,而大量客户端用公钥跟他交互。

非对称加密对秘钥的存储成本更低;对称加密(如AES)中的主要运算是位运算,非对称加密(如RSA)中用到了大数乘法、大数模等运算,因此对称加密的速度更快。

两种加密方式结合 -> 安全和效率的动态平衡,将对称加密的密钥使用非对称加密的公钥进行加密,然后发送出去,接收方使用私钥进行解密得到对称加密的密钥,然后双方可以使用对称加密来进行沟通。

客户端如何知道服务器可信 如何验证ca证书可信

等同于:如何保证发布公钥的人不是他人冒名顶替?

找一个受信任的机构”证书中心”(certificate authority,简称CA)做认证,证书中心用自己的私钥给(A的公钥+申请者A的信息等)加密,生成数字证书。当下次A给B发消息的时候A会带上CA签发的数字证书,B收到后用CA的公钥解密证书,解密成功则证明这个的确是CA签发的有效证书,而不是不知名的野鸡机构,从而拿到了里面的A的公钥,证明了A就是CA机构认证过的A,这个的的确确就是A的公钥,错了就找CA机构买单。然后拿A的公钥,去解密数字签名得到摘要,在对原文进行摘要算法之后和这个摘要进行对比,一致则说明没有被篡改。

浏览器认证中间证书的方法:
1.浏览器首先解析网站的数字证书,如果该证书的签发机构能够在可信任签发机构中找到,则为可信任证书,否则进行2
2.从授权信息访问信息段中获得该签发机构的数字证书,并判断该授权机构的数字证书的签发机构是否能够在可信任签发机构中找到,若果找到则该授权机构是可信的,并从授权机构的数字证书中获得授权机构的公钥公钥,如果不在则重复2步骤。如果最终都没有找到可信机构,则为不可信证书。

数据库

###主键索引和非主键索引的区别

以下面这个例子,ID是主键,并且还有一个字段K

img

那么ID和k的索引如下两图所示

img

索引的结构同样都是B+树,区别在于叶子节点。主键的叶子节点中存储的是整行的值,而非主键中存储的是主键的值。非主键索引也被称为二级索引,而主键索引也被称为聚簇索引

###回表查询

查询非主键索引时

(1)先通过普通索引定位到主键值

(2)在通过聚集索引定位到行记录;

回表查询需要查询两次,效率比较低。避免的方法是联合索引,然后不查没有被联合索引的字段。

###主键索引(聚簇索引)是必须的吗?

InnoDB聚集索引的叶子节点存储行记录,因此, InnoDB必须要有,且只有一个聚集索引:

(1)如果表定义了PK,则PK就是聚集索引;

(2)如果表没有定义PK,则第一个not NULL unique列是聚集索引;

(3)否则,InnoDB会创建一个隐藏的row-id作为聚集索引;

画外音:所以PK查询非常快,直接定位行记录。

索引为什么用B+树

首先为什么要用树

哈希表查询很快,但是无法进行范围查询,因此哈希表不合适作为数据库的索引

演化过程

二叉搜索树

子树的高度不平衡导致最坏情况下的查询可能会到达O(n)

#####平衡二叉搜索树(AVL)

平衡的树高避免了最坏的情况,可以保证查询的时间复杂度是O(log(n))。但是数据库不只有查询操作,还有增删改。AVL需要频繁的维护树的结构,这会带来很大的开销

多路平衡搜索树 B树

B树改进了AVL树,减少了平衡树的操作,即降低了维护树结构的开销。

B+树

那么B树和B+树的区别在哪呢?

  1. B+跟B树不同B+树的非叶子节点不保存键值对应的数据,这样使得B+树每个节点所能保存的键值大大增加;
  2. B+树叶子节点保存了父节点的所有键值和键值对应的数据,每个叶子节点的键值从小到大链接;
  3. B+树的根节点键值数量和其子节点个数相等;
  4. B+的非叶子节点只进行数据索引,不会存实际的键值对应的数据,所有数据必须要到叶子节点才能获取到,所以每次数据查询的次数都一样;

放个图理解的更清楚一点,B树

img

img

在B+树的基础上每个节点存储的关键字数更多,树的层级更少所以查询数据更快,所有关键字数据都存在叶子节点,所以每次查找的次数都相同,查询速度比B树更稳定。除此之外,B+树的叶子节点是跟后序节点相连接的,这对范围查找是非常有用的。

乐观锁

乐观锁底层使用原子指令CAS实现(Compare and Swap)。由于是用硬件支持的原子指令,效率非常高,很少有额外的开销。而悲观锁底层用Mutex实现,涉及到一系列的系统调用,额外的开销会比较大。在具体场景上,乐观锁在进行更改时,需要对比之前的的版本号,如果版本号一致即认为数据没有被修改过,可以进行更新;而悲观锁则保证修改数据时线程之间的互斥,在critical section中只有一个线程允许进入。

1、功能限制

与悲观锁相比,乐观锁适用的场景受到了更多的限制,无论是CAS还是版本号机制。

例如,CAS只能保证单个变量操作的原子性,当涉及到多个变量时,CAS是无能为力的,而synchronized则可以通过对整个代码块加锁来处理。再比如版本号机制,如果query的时候是针对表1,而update的时候是针对表2,也很难通过简单的版本号来实现乐观锁。

2、竞争激烈程度

如果悲观锁和乐观锁都可以使用,那么选择就要考虑竞争的激烈程度:

  • 当竞争不激烈 (出现并发冲突的概率小)时,乐观锁更有优势,因为悲观锁会锁住代码块或数据,其他线程无法同时访问,影响并发,而且加锁和释放锁都需要消耗额外的资源。
  • 当竞争激烈(出现并发冲突的概率大)时,悲观锁更有优势,因为乐观锁在执行更新时频繁失败,需要不断重试,浪费CPU资源。

####IO次数

如果比较成功进行更新的话就是三次IO,如果比较失败不更新的话应该是两次IO

1
2
3
4
5
6
7
8
// 假设需要更新的值为A
oldval = load(A)
// 进行一系列的计算
newval = ......
......
val = load(A)
//如果 val == oldval 将 A更新为newval
A.CAS(oldval, val, newval)

字节跳动飞书后端一面

上来先自我介绍,再问掌握的==技术栈==。然后开始问基础知识:

Java的反射机制

Java的反射(Reflection)机制提供动态获取类的信息,以及动态调用对象的方法的功能。反射机制的几大功能:==在运行时==,

  • 判断任意一个对象所属类。
  • 构造任意一个对象。
  • 判断任意一个类所具备的成员变量和方法
  • 调用任意一个对象的方法。

    通常而言:程序运行时容许改变程序结构和变量类型称为动态语言。因而可知Java并非动态语言,可是Java的Reflection机制,能够运行时加载、探知、使用编译期间彻底未知classes。因此Reflection是Java视为动态语言的关键性质,容许在程序运行时经过Reflection APIs 取得任何一个已知名称的class的内部信息(包括父类,接口,变量,方法等)。

IOC:Inversion of Control —— 控制反转,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象。

DI:Dependency Injection —— 依赖注入,在系统运行中,动态的向某个对象提供它所需要的其他对象。

AOP —— Asepct-Orentid-Programming,面向切面编程,把与核心业务逻辑无关的代码全部抽取出来,放置到某个地方集中管理,然后在具体运行时,再由容器动态织入这些共有代码。

Java虚拟机内存回收机制,什么是Full GC,如何避免Full GC

堆内存划分为 Eden、Survivor 和 Tenured/Old 空间。

  • 从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC;
  • 对老年代GC称为Major GC;
  • 而Full GC是对整个堆来说的;

发生Full GC的情况及避免方法:

  1. 直接调用System.gc
  2. 旧生代空间不足。调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。
  3. Permanet Generation空间满。增大Perm Gen空间或转为使用CMS GC。
  4. CMS GC时出现promotion failed和concurrent mode failure。增大survivorspace、旧生代空间或调低触发并发GC的比率。
  5. 统计得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间
  6. 对于使用RMI来进行RPC或管理的Sun JDK应用而言,默认情况下会一小时执行一次Full GC
http和https什么区别

https://blog.csdn.net/xionghuixionghui/article/details/68569282

1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。

2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。

3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

(好像还问了一道网络题,记不清了……)

什么是跨域问题?如何解决跨域问题?

由浏览器的同源策略造成。同源是指,域名,协议,端口均相同。

解决方法:

  1. JSONP
  2. 代理
  3. PHP端修改header(XHR2方式)
算法:二分查找

是指在有序数组中查找某一特定元素的算法。搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每-次比较都使搜索范围缩小一半。

索引是什么?索引过多会出现什么问题?

索引是关系数据库中对某一列或多个列的值进行预排序的数据结构。通过使用索引,可以让数据库系统不必扫描整个表,而是直接定位到符合条件的记录,这样就大大加快了查询速度。

1.一个索引会在update或insert时增加一次I/O, 对于操作系统底层来说是非常损耗性能的。
2.索引过多会导致索引文件过大, 系统在寻址时查询时间增长。

在表上建立的每个索引都会增加存储开销,索引对于插入、删除、更新操作也会增加处理上的开销。另外,过多的复合索引,在有单字段索引的情况下,一般都是没有存在价值的;相反,还会降低数据增加删除时的性能,特别是对频繁更新的表来说,负面影响更大。

hashmap 数据结构
真 每次必问啊

hasnmap是一个链表的数组,对于每个 key-value对元素,根据其key的哈希,该元素被分配到某个桶当中,桶使用链表实现,链表的节点包含了一个key,一个value,以及一个指向下一个节点的指针。当桶的大小大于8时会转化为红黑树存储数据。

img

https://www.jianshu.com/p/8b372f3a195d

你对飞书怎么看?有没有了解过飞书的开放平台?

我不想看https://www.feishu.cn/product/overview

没有,下次一定https://www.feishu.cn/webinar/feishu-openapi

居然只面了十五分钟??就挺突然的。而且用的是飞书视频,没有考编程??算法题 也挺 意外的……整理之后发现一半的题目回答都有点凉啊