【團隊作業總結】個人總結(從後端來看整個項目)
團隊作業總結(byPB16061082耿子鋼):
本次項目做的是一個作業管理系統,包括教師端和學生端,主要功能簡單來說為教師通過教師端來上傳學生名單,發布作業,批改作業,管理作業這樣一個平臺,學生通過學生端來看自己被布置的作業,提交作業這幾個功能。
我們項目後端是用nodejs+mysql來做的,後端主要負責:前後端的交互,mysql數據庫的設計,數據庫的增刪改查,前端的動態渲染,數據的管理還有一些其他的補充功能。接下來我分部分介紹我們的項目。
前後端的交互:
一。首先配置好proxytable,來實現代理和跨域,這樣的話一個後端的路由就可以處理來自兩個url的請求了:
1 dev: {2 env: require(‘./dev.env‘), 3 host:‘localhost‘, 4 port: 8080, 5 autoOpenBrowser: true, 6 assetsSubDirectory: ‘static‘, 7 assetsPublicPath: ‘/‘, 8 proxyTable: { 9 ‘/api/‘ : { 10 target:‘http://localhost:3000/‘, 11 changeOrigin: true, 12 } 13 }, 14 cssSourceMap: false15 },
二。寫好後端接受的代碼:這樣的話後端接收到post請求可以進行處理:
1 const userApi = require(‘./api/userApi‘); 2 const fs = require(‘fs‘); 3 const path = require(‘path‘); 4 const bodyParser = require(‘body-parser‘); 5 const cookieParser = require(‘cookie-parser‘) 6 const cors = require(‘cors‘); 7 const express = require(‘express‘);8 const app = express(); 9 10 app.use(cors({ 11 origin:[‘http://localhost:8080‘], 12 methods:[‘GET‘,‘POST‘], 13 alloweHeaders:[‘Conten-Type‘, ‘Authorization‘] 14 })); 15 16 app.set(‘port‘, (process.env.port || 3000)) 17 app.use(bodyParser.json()) 18 app.use(bodyParser.urlencoded({extended: false})) 19 app.use(cookieParser()) 20 21 app.use(userApi) 22 23 app.listen(app.get(‘port‘), function () { 24 console.log(‘Visit http://localhost:‘ + app.get(‘port‘)) 25 }) 26 27 router.post(‘/api/login‘, function (req, res) { 28 let userName = req.body.username, 29 password = req.body.password, 30 resBody = {state: ‘‘} 31 if (userName !== undefined && userName.length > 0) { 32 conn.query("SELECT * FROM users WHERE ?", {name: userName}, function (err, doc) { 33 if (err) { 34 resBody.state = ‘賬號不存在‘; 35 res.send(resBody); 36 } else { 37 if (doc.length === 0) { 38 resBody.state = ‘賬號不存在‘; 39 res.send(resBody); 40 } else { 41 console.log(doc); 42 resBody.state = ‘登錄成功‘; 43 res.send(resBody); 44 } 45 } 46 }) 47 } 48 else { 49 resBody = {state: ‘請輸入用戶名‘} 50 res.send(resBody) 51 } 52 });
MYSQL數據庫的設計:
最終的數據庫:5個表:users-class-student homework_single-homework_file
users:主要記錄了用戶在註冊時的一些信息,一些比較特殊的地方如下解釋:
①老師學生以is_stu這一項來區分
②enable在點開註冊郵件激活可以登錄。
class:主要記錄一個課程,包含了以下信息:課程名稱,任課老師,上課時間,課程的學生,課程的公告。
主要用於老師的第一個界面--teacherclass界面的動態渲染。
student:看到這裏可能有人要問,明明users已經記錄了所有的信息,為什麽還要有student這個table,我解釋一下:
student對於我們的項目是至關重要的,雖然他只比users多了一個信息:class,也就是這個學生屬於的班級,但是有了這個很多事情可以簡化。
比如說如果一個學生屬於多個課程,那麽就有多個student對應於這個學生。但如果多個users的話,就不合理了,因為註冊時有檢查一個學號,一個郵箱只能有一次註冊。
這個表可以說很有用了,在加載學生的作業界面時思路就是,先查到學生所在的所有班級,在查到這些班級的所有作業,顯示出來。
homework_single:這個table主要記錄了老師布置的一次作業。它的主要信息有:作業名稱,截止日期,歸屬於哪個班級,作業要求,上交數量,沒交人數,布置時間等等。歸屬於哪個班級,記錄的是歸屬班級的id,因為id是獨一無二的。
homework_file:記錄的是學生提交的單次作業,主要記錄了作業路徑,學生姓名,上交時間,作業標題,是否已經批改。分數與評語。
感想:
①數據庫的設計是個特別特別關鍵的步驟。一招不慎滿盤皆輸。設計一個好的數據庫可以讓自己的代碼更簡潔,思路更完美。如果設計不好的話就各種想辦法,有時候就走進了死胡同。
②我們這個項目的主要特點是層次特別多,也比較淩亂。因為一個同學屬於一個班級也屬於一次作業,一次作業也屬於一個班級。所以,數據庫盡量信息要豐富,不能單純為了追求數據庫的簡潔,而無意中增加了查詢數據庫的層次。
③如果需要一個表中保留一個信息來在另一個表中查詢數據的話,ex:在student裏面保存他的class的一個標誌信息,用來從class表中查更多信息,我建議保存id,因為只有id是獨一無二的,其他不管學號啊,郵箱啊,都不靠譜。
數據庫的增刪改查+前端的動態渲染:
經典的程序:選取了動態渲染老師的作業界面來展示:
前端:
1 created() { 2 this.$http.post(‘api/gethws‘, { 3 }).then((response) => { 4 let name = response.body.name; 5 let deadline = response.body.deadline; 6 let id = response.body.id; 7 let announcement = response.body.announcement; 8 let nohand = 0; 9 let hand = 0; 10 let nodata = []; 11 let data = []; 12 let final = id[id.length-1]; 13 for (let i = 0; i < id.length; i++) { 14 this.$http.post(‘api/getidh‘, { 15 id: id[i], 16 }).then((response) => { 17 var obj ={}; 18 let body1 = response.data; 19 let temp1 = body1.data; 20 if(response.body.state === "未交作業"){ 21 obj.name2 = name[i]; 22 obj.demand = announcement[i]; 23 obj.deadline = deadline[i]; 24 nodata[nohand] = obj; 25 nohand++; 26 } 27 else if(response.body.state === "已交作業"){ 28 obj.name = name[i]; 29 obj.title = temp1[0].hwname; 30 if(temp1[0].already === 0) obj.scores = ‘未評分‘; 31 else obj.scores = temp1[0].grade; 32 if(temp1[0].already === 0|temp1[0].comment ===‘ ‘) obj.comment = ‘沒有評語‘; 33 else obj.comment = temp1[0].comment; 34 data[hand] =obj; 35 hand++; 36 } 37 if(hand+nohand === id.length){ 38 this.scoresTable = data; 39 this.undoTable = nodata; 40 } 41 }, (response) => { 42 console.log(response) 43 } 44 ) 45 } 46 }, (response) => { 47 console.log(response) 48 }) 49 },
後端:
1 router.post(‘/api/posthws‘, function (req, res) { 2 var cookies = req.headers.cookie; 3 req.cookies = Object.create(null); 4 req.cookies = cookie.parse(cookies); 5 6 let xuehao = req.cookies.xuehao; 7 let homeworkid = req.cookies.hwids; 8 let title = req.body.title; 9 let remark = req.body.remark; 10 let content = req.body.content; 11 let student = req.cookies.userName; 12 let handin_time = sd.format(new Date(), ‘YYYY-MM-DD HH:mm:ss‘); 13 let already = 0; 14 let resBody = {state:‘hhh‘}; 15 16 let sql_1 = {sql:‘select * from homework_file Where homeworkid=? AND student=?‘}; 17 let param_1 = [homeworkid,xuehao]; 18 19 conn.query(sql_1, param_1, function(err, doc){ 20 if(err){ 21 resBody.state = ‘error:交互失敗‘; 22 return res.send(resBody); 23 }else{ 24 if (doc.length !== 0) { 25 resBody.state = ‘error:已經存在‘; 26 return res.send(resBody); 27 } 28 let sql_2 = {sql:‘insert into homework_file (filepath,student,hwname,handin_time,homeworkid,xuehao,already) VALUES (?, ?, ?, ?, ?, ?, ?)‘}; 29 let param_2 = [content,student,title,handin_time,homeworkid,xuehao,already]; 30 31 conn.query(sql_2, param_2, function(err){ 32 if(err){ 33 resBody.state = ‘error:提交失敗‘; 34 return res.send(resBody); 35 }else { 36 resBody.state = ‘success‘; 37 return res.send(resBody); 38 } 39 }) 40 } 41 }) 42 });
感想:
①not null最好要慎用,因為你insert一個數據的時候,如果要求notnull,而你沒加這一項,就會報錯。
②在查詢數據比較兩個數據是否一樣時,一定要註意數據庫中數據的類型:比如說int與varchar肯定不一樣,輸出類型會看到一個是num,一個是string,在這個上面踩得坑特別多。更坑的是char和varchar都不一樣。
③刪除一個東西的時候,要把和他相關的都刪幹凈。比如說刪除一個班級,就要把homework_single老師在這個班級下面布置的所有作業都刪掉,也要把學生homework_file在這個班級下的也刪掉,還有student中屬於這個班級的。
④改數據庫覺得一次只能改一個column。還有就是一定要註意數據庫的命名,因為所有命名有前端發送時的命名,有後端接收後的命名,還有數據庫的命名,很容易混淆。一個不好的命名可能會浪費你一下午的時間。
其他一些附加功能:
發郵件的功能+一鍵提醒的功能:
1 router.post(‘/api/sendmail‘, function (req, res) { 2 let temp = req.body.temp1; 3 console.log(temp); 4 resBody = {state: ‘‘} 5 for(i = 0;i<temp.length;i++){ 6 email = temp[i].email; 7 sendmail(email); 8 } 9 resBody.state = ‘發送成功‘; 10 return res.send(resBody); 11 }) 12 13 14 var sendmail = function(email) { 15 var transporter = nodemailer.createTransport({ 16 service: ‘qq‘, 17 auth: { 18 user: ‘@qq.com‘, 19 pass: ‘uuevruvffnrcbg‘ 20 } 21 }); 22 var mailOptions = { 23 from: ‘@qq.com‘, // 發送者 24 to: email, 25 subject: ‘提醒作業‘, // 標題 26 text: ‘同學,您有還未提交的作業,別玩了,請趕緊去交作業"‘ 27 }; 28 transporter.sendMail(mailOptions, function (err, info) { 29 if (err) { 30 console.log(err) 31 return; 32 } 33 }); 34 }
總結與體悟:
①nodejs的異步回調:
主線程發出一個異步請求,相應的工作線程接受請求並告知主線程已收到(異步函數返回);主線程可以繼續執行後面的代碼,同時工作線程執行異步任務;工作線程完成工作後,通知主線程;主線程收到通知後,執行一定的動作(調用回調函數)。
工作線程在異步操作完成後通知主線程的過程:工作線程將消息放到消息隊列,主線程通過事件循環過程去取信息。實際上,主線程只會從消息隊列裏面取信息,執行信息,再取信息,再執行。當消息隊列為空時,就會等待消息隊列變成非空。只有當前消息執行完成後,才會去取下一個信息,這就是事件循環機制。
事件驅動程序:當server接收到請求,就把它關閉然後處理,然後去服務下一個請求。當這個請求完成,它被放回處理隊列,當到達隊列開頭,這個結果被返回給用戶。
這樣做的優點是不用多線程就可以極大地提高工作效率, 但是有一個問題是,只要變量在回調函數外面,他的值都不會被這個函數改變,這樣就跟直覺相反,也跳入了很多這種坑,經歷了幾次undefined之後,才徹底理解了這種機制。
還有一個缺點是,外部的計數器對於回調函數來說會失效,經過反復琢磨,還想過把異步操作換成同步,後來想明白加一個函數內部的計數器就可以很好地解決這個問題,這也算是一個經驗吧。
②一個大的項目,需要有很好的全局意識,需要考慮到的東西太多了,這就需要你的工程能力和清晰的思路了。
一般大的項目都是牽一發而動全身,這就要求我們盡可能把他們之間的聯系變得簡單化,模塊化,這樣才不會有太大的絕望。
這個項目中,原本以為能用學生的學號做唯一的標識,但是後來才發現這個是極其幼稚的,導致了代碼的第一次重構。
③從前期的團隊作業進度中,可以明顯感覺到技術掌握的不夠把進度拖了又拖,後來發現不能等到都學會然後用,要邊學邊用,邊踩坑中成長。
後來又發現團隊管理真的特別重要,如果管理得好,多個人的效率遠遠超過一個人,如果管理的不好,一個人的效率也會遠遠超過多個人。
做任何事都不能靠別人,尤其在大學,不要幻想著老師還會教給你什麽知識,什麽技術,只有自己去學,去黑暗中摸索,才能會用一些東西。
現在做網頁的狀態,是把零散的細碎的點滴的知識搭建成一個大項目(目前來說,對大項目整體構架仍然沒有一個很明確地認識),這坑,必須要自己踩了。
④對項目整體沒有一個清晰的認識,經常會帶來很多負面的情緒,嚴重拖累項目進度,會讓人喪失動力。
對技術本身沒有清楚的認識,只是會使用它,這樣的學習我覺得是悲劇的,致使經常遇到這種情況:
遇到問題,google一下,按照他的一步一步去做,結果自己做和他上面應該有的不一樣,這時候有兩種方案。
第一,自己想想,想不明白這個什麽,然後瞎搞,搞壞了就壞了,轉到第二種方案;搞好了,就有一種劫後余生的欣喜,但是回頭想想自己高興什麽,這個不是隨便一個人都會的嗎?然後跳出這個循環。
第二,找個看起來更靠譜的從頭再一步一步去做,同樣面臨著循環回去的可能,也就是自己做的和他上面的不一樣。
這麽循環著循環著,一天就過去了,就很絕望。
到最後發覺,
自己為什麽又在隨便找一個人就可以做的按照教程一步一步走這個過程中浪費了一天呢?
這一天又收獲了什麽呢?可能google的能力又進步了一點?可能對問題有了更加深入一點的了解?
⑤雖然這個過程很艱苦,但是我想著,一定做什麽事情,既然選擇了,不管成功失敗,都要有一個結果,都要有東西可以展示出來,這才是最重要的。所有事情都要追求一個結果,成功的或者失敗的。
【團隊作業總結】個人總結(從後端來看整個項目)