这个作业属于哪个课程 | 软件工程-23年春季学期 |
---|---|
这个作业要求在哪里 | 软件工程实践结对作业二 |
这个作业的目标 | 1、fork仓库,和伙伴商讨协作细节等 2、编程实现 3、撰写博客 |
结队成员学号 | 052003113 呆、222000211 最爱吃香菜 |
其他参考文献 | element-ui 手册 |
目录
一、Gitcode仓库链接和代码规范链接
1.1 Gitcode仓库地址
1.2 代码规范地址
引用 《阿里规约》 中的一段话:
对软件来说,适当的规范和标准绝不是消灭代码内容的创造性、优雅性,而是限制过度个性化,以一种普遍认可的统一方式一起做事,提升协作效率,降低沟通成本。代码的字里行间流淌的是软件系统的血液,质量的提升是尽可能少踩坑,杜绝踩重复的坑,切实提升系统稳定性,码出质量。
规范的代码可以让人赏心悦目,能让团队更加和谐,为此我们指定了如下规范:
二、PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 20 |
• Estimate | • 估计这个任务需要多少时间 | 30 | 20 |
Development | 开发 | 1880 | 2395 |
• Analysis | • 需求分析 (包括学习新技术) | 300 | 500 |
• Design Spec | • 生成设计文档 | 60 | 90 |
• Design Review | • 设计复审 | 20 | 30 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 30 | 60 |
• Design | • 具体设计 | 30 | 45 |
• Coding | • 具体编码 | 1330 | 1550 |
• Code Review | • 代码复审 | 10 | 30 |
• Test | • 测试(接口测试,修改代码,提交修改) | 100 | 90 |
Reporting | 报告 | 120 | 180 |
• Test and use Repor | • 接口、使用报告 | 90 | 130 |
• Size Measurement | • 计算工作量 | 10 | 15 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 20 | 25 |
合计 | 2030 | 2585 |
三、项目的访问链接
由于时间关系,以及学习成本的原因,这次只把前端部署上去了,后端部署上去却和前端连接不起来
四、项目成果展示
-
主要的UI设计 因为原型设计时对页面进行了精细的讨论和设计,所以本次项目我们决定继续沿用原型设计时的UI样式,降低UI设计时的工作量。这次设计的网站是体育赛事网站,考虑到网页的特点,我们决定使用
顶端导航栏
+内容栏
的方式来呈现 -
首页 使用面包屑导航
-
选手排名 选手排名我们采用表格的形式向用户展示了女子与男子,并高亮了中国代表团。
-
晋级图 我们实现了一个
logicFlow
这个饿了吗
提供的可视化编程,调用上面的api以及对应数据的提取即可实现流程图逻辑的实现- 经过对应会边框显示
- 高亮显示获胜选手
五、结对讨论过程描述
我们此次遇到问题时大部分时间采用qq或者vx沟通,晚上休息时间以后在线上继续交流。
-
前期任务分工
由于呆同学之前利用vue开发过项目,而且对前端比较熟悉,所以交给呆同学前端,由呆同学来完成前端部分的开发
因为香菜同学想主动实践自己学到的ssm框架知识,所以本次项目就由香菜同学负责后端代码的编写。
我们经过一番讨论后,决定采用
vue+ssm+tomcat+mysql+element-ui
来进行本次结对项目的开发 -
相关教程收集 由于之前有相关项目的经验,所以本次没有查看相关的教程,看官方对应的api文档进行开发
-
相关细节和问题探讨 在项目实现过程中我们会对出现的问题和相关的细节进行及时地讨论和处理,比如对方出现一些问题的时候,队友也会积极的帮忙寻找解决方案,并且也会对对方的一些内容进行思考,提出改进建议的
-
部分吐槽(浅看)
[外链图
- 结对期间进度交流 双方都会主动把自己的任务做完,而且都会提前去完成自己的任务不会拖对方时间,我们在这点上还是比较默契的
六、设计实现过程
6.1 功能结构图
6.2 数据库设计
如下图:
6.3 设计概述
前端的实现过程:
-
采用技术:
Vue框架
、axios
、element-ui
-
设计思路: 本次作业的前端部分的要点主要是页面的设计、后台数据的获取和渲染以及晋级图的实现。
-
页面的设计: 本次项目根据结对作业1的页面设计实现对应的功能,
-
后台数据的获取和渲染: 后台数据通过
基于 promise 的 HTTP 库axios
向后端接口发送网络请求来获取。后台传送的数据是json对象数组,所以我们使用一个数组
来存放后台返回的结果数组。 -
晋级图
在制作的过程中发现可以使用
logicFlow
对数据进行可视化编程,所以这次就采用了logicFlow
对晋级图进行代码的编写,采用了脚本管理工具node导入logicFlow的数据包,但是由于logicFlow内原先的数据展示是ts,所以要将ts转换成js 使用dos指令tsc xxx.ts
进行ts与js文件之间的转换
-
后端的实现过程:
-
采用技术:
tomcat
(Web轻量级应用服务器)、mysql
(关系型数据库管理系统)、ssm框架
(后端框架) -
设计思路:
- 数据获取思路: 爬取的json数据经过jackson解析,然后通过利用mybatis存储到数据库
- 流程图展示:
-
API生成思路: dao层提供数据库数据支持,经由service进行相关业务处理,最后controllrt层调用sevice返回数据形成api
-
流程图展示(以为例):
6.4 遇到的问题以及解决方式
前端问题:
-
前端问题:
-
晋级图的实现
问题描述
:如果使用h5c3的定位去解决晋级图那未免过于繁琐而且复杂,线条之间的关系还不太好处理- 解决方案:通过github上的搜索得知了有一个工具叫做logicFlow的工具可以实现给一个模板,然后我们传入数据即可渲染出来结果,使用node工具进行导入然后查看代码,发现可以在创建对象new logicFlow时候改变下面的参数width改变画布的大小,并在TBnode的文件中的getStyle方法中改变结点的大小,数据就从data.js中修改
-
选手比赛获胜选手高亮显示
问题描述
:
上头的表格第一和第二列是合并的单元格,但是我在高亮的时候会一整行高亮,那样效果就达不成后头三个高亮的效果,而会将前两格一起高亮
- **解决方案:**仔细查阅资料发现
cell-style
这个el-table
的属性可以对单个单元格产生影响,但是逻辑也是只遍历单个单元格。所以,使用vue的特点
,数据在data区域共享
,直接从data区域获取到对应的数据,然后每次遍历单元格的时候,判断是不是获胜,如果获胜就高亮,对应函数如下
cellStyle({row,column,rowIndex,columnIndex}) { let win_times = 0; if(rowIndex % 2 == 0 && columnIndex>=2){ for(let i = 0; i < this.tableData[rowIndex].scores.length; i++) { if(this.tableData[rowIndex].scores[i] > this.tableData[rowIndex + 1].scores[i]) { win_times++; } } if(win_times >= Math.floor(this.tableData[rowIndex].scores.length /2) + 1){ return { backgroundColor: "#f1f3f4",fontWeight:"800" }; } win_times = 0; }else if(rowIndex % 2 == 1&& columnIndex>=2){ for(let i = 0; i < this.tableData[rowIndex].scores.length; i++) { if(this.tableData[rowIndex].scores[i] > this.tableData[rowIndex - 1].scores[i]) { win_times++; } } if(win_times >= Math.floor(this.tableData[rowIndex].scores.length /2) + 1){ return { backgroundColor: "#f1f3f4",fontWeight:"800" }; } win_times = 0; } }
使用
对2取余
就可以实现互斥的效果
,不会有出现一组内的两个单元格都高亮
-
后端问题:
- 云服务器部署问题
问题描述: 因为选择的是腾讯云服务器,部署的是Linux系统
,但是由于没有学过Linux指令相关的内容,所以敲起来有点费劲,而且linux完全的黑窗口,不是像windows这样的可视化,所以需要熟练掌握指令的操作 解决方案: 通过csdn
查阅各种资料,查阅各种资料,最后完成相应的功能
七、代码说明
7.1 前端关键代码
-
表格数据的渲染:
- 通过
:data = "tableData" 绑定数据
,然后通过的el-table-column
的属性prop
进行数据的传输,要与下面tableData
内的key值
对应,才能成功渲染数据,el-table内部使用v-for
自动遍历表格数据
<el-table :data="tableData" :span-method="objectSpanMethod" border fit max-height="550" :row-class-name="tableRowClassName" :cell-style="cellStyle" style="width: 100%; margin-top: 20px" > <el-table-column prop="matches" label="Matches" /> <el-table-column prop="time" label="Time" /> <el-table-column prop="nationality" label="Nationality" /> <el-table-column prop="name" label="Name" /> <el-table-column prop="scores" label="Scores" /> </el-table> //这个是tableData的结构 tableData : [ { matches: 'Women\'s Singles ', time: '00:14', nationality: 'fc', name: 'K.Siniakova', scores: '7 7' }]
表格数据通过
axios库
向后端接口发送GET请求
来获取将数据通过接口获取到tableData中,然后实现数据的渲染
mounted() { //获取接口 request.get('接口',this.tableData).then (res=>{})}
- 通过
-
晋级图的实现: 晋级图使用
logicFlow
第三方数据可视化库导入。通过axios
向后台发送GET
请求获取选手晋级数据。晋级图相关参数设置
const lf = new LogicFlow({ container: this.$refs.container, //设置结点的不可编辑 isSilentMode: true, //设置画布的宽高 width: 1500, height: 700, grid: false, textEdit: false, keyboard: { enabled: true }, edgeType: 'better-line', plugins: [Plugin, Menu, SelectionSelect] }); lf.openSelectionSelect(); lf.setTheme({ polyline: { stroke: 'rgb(130, 179, 102)', strokeWidth: 1 } });
7.2后端关键代码
Json数据解析:
public ArrayList<Set>readJsonToFile(String patnIn) throws IOException { String jsonString = new String(Files.readAllBytes(Paths.get(patnIn))); // 将JSON文件解析为JSON对象 JSONObject jsonObject = new JSONObject(jsonString); // 获取matchesJSON JSONArray matchesJsonA = jsonObject.getJSONArray("matches"); //获得team JSONArray teams = jsonObject.getJSONArray("teams"); //获得player JSONArray players = jsonObject.getJSONArray("players"); String contain = ""; Set<Matches> setM = new HashSet<>(); Set<Score> setS = new HashSet<>(); ArrayList<Set> setArray=new ArrayList<>(); for (int i = 0; i < matchesJsonA.length(); i++) { JSONObject match = matchesJsonA.getJSONObject(i); //判断是否弃赛 if (match.getJSONObject("match_status").getString("abbr").equals("W/O")) { contain += "W/O\n" + "-----" + "\n"; continue; } String actual_start_time = match.getString("actual_start_time"); String match_id = match.getString("uuid"); String date = match.getString("date"); String round_id = match.getString("round_id"); String duration = match.getString("duration"); String event_uuid = match.getString("event_uuid"); String type; if(event_uuid.equals("675d28ec-b177-46b2-959c-595f3ca862a0")){ type="Men's Singles"; }else{ type="Women's Singles"; } //teams数组 JSONArray teamsJSONArray = match.getJSONArray("teams"); String team1Id = ""; String team2Id = ""; team1Id = teamsJSONArray.getJSONObject(0).getString("team_id"); team2Id = teamsJSONArray.getJSONObject(1).getString("team_id"); Matches matches1 = new Matches(date,actual_start_time,match_id,round_id,duration,team1Id,team2Id,type);// //teamid数组 ArrayList<String> teamIdArr =new ArrayList<String>(); teamIdArr.add(team1Id); teamIdArr.add(team2Id); setM.add(matches1); //得到一场比赛的所有score for (int j = 0; j <teamsJSONArray.length(); j++) { JSONArray scores = teamsJSONArray.getJSONObject(j).getJSONArray("score"); for (int k = 0; k <scores.length() ; k++) { //得到score类 String matchSet=String.valueOf(scores.getJSONObject(k).getInt("set")); String game =scores.getJSONObject(k).getString("game"); String winner =""+scores.getJSONObject(k).getBoolean("winner"); String id=teamIdArr.get(j); Score score = new Score(matchSet,winner,game,id,match_id); setS.add(score); } } } setArray.add(setM); setArray.add(setS); return setArray; } @Autowired private PlayerDao playerDao; public Set<Players> readToFile(String patnIn) throws IOException { String jsonString = new String(Files.readAllBytes(Paths.get(patnIn))); // 将JSON文件解析为JSON对象 JSONObject jsonObject = new JSONObject(jsonString); // 获取players JSON数组 JSONArray jsonArray = jsonObject.getJSONArray("players"); String contain =""; // 遍历JSON数组 Set<Players> set = new HashSet<>(); // 获取players JSON数组 JSONArray teams = jsonObject.getJSONArray("teams"); Set<Teams> teamsSet=new HashSet<>(); for (int i = 0; i < jsonArray.length(); i++) { // 获取JSON对象 JSONObject playerObject = jsonArray.getJSONObject(i); // 读取JSON对象中的属性 String fullName = playerObject.getString("full_name"); String last_name = playerObject.getString("last_name"); String first_name = playerObject.getString("first_name"); String short_name = playerObject.getString("short_name"); String uuid = playerObject.getString("uuid"); String gender = playerObject.getString("gender"); String player_icn= playerObject.getJSONObject("player_icon").getString("url"); System.out.println("player_icn = " + player_icn); String hero_image = playerObject.getJSONObject("image").getString("url"); System.out.println("hero_image = " + hero_image); String nationality_id = playerObject.getJSONObject("nationality").getString("uuid"); Players player=new Players(uuid,first_name,last_name,fullName,short_name,gender,nationality_id,hero_image,player_icn); set.add(player); /**国籍读取 String name = playerObject.getJSONObject("nationality").getString("name"); String uuid = playerObject.getJSONObject("nationality").getString("uuid"); String code = playerObject.getJSONObject("nationality").getString("code"); String flag = "https://ausopen.com/"+playerObject.getJSONObject("nationality").getJSONObject("flag").getString("url"); Nationality N = new Nationality(uuid,name,code,flag); set.add(N); **/ } return set; }
-
形成API接口—解决跨域题
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Override
public void addCorsMappings(CorsRegistry registry){
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("POST","GET","PUT","OPTIONS")
.maxAge(168000).allowedHeaders("*")
.allowCredentials(false);
}
}
- 形成API接口—获取数据库中的数据
@Mapper
public interface RakingDao {
@Select("select * from ranking where gender= 1 order by aces desc")
public List<Ranking> getRrakingM();
@Select("select * from ranking where gender= 2 order by aces desc")
public List<Ranking> getRrakingW();
}
@Service
public class GetService {
@Autowired
private TeamsDao teamDao;
@Autowired
private NationalityDao nationalityDao;
@Autowired
private MatchDao matchDao;
@Autowired
private PlayerDao playerDao;
@Autowired
private ScoreDao scoreDao;
public ArrayList<MatchWO> getMatcheWithOther(Integer from,Integer size){
ArrayList<MatchWO> matchWOSArray=new ArrayList<>();
List<Matches> allMatches = matchDao.getAllMatches(from,size);
for (int i = 0; i <allMatches.size() ; i++) {
Matches matches = allMatches.get(i);
String date = matches.getDate();
String actualStartTime = matches.getActualStartTime();
String matchId = matches.getMatchId();
//类型
String type = matches.getType();
//teamId
String team1Id = matches.getTeam1Id();
String team2Id = matches.getTeam2Id();
//t1
String palyer1Id = teamDao.getTeamsById(team1Id).getPalyer1Id();
String palyer2Id = teamDao.getTeamsById(team1Id).getPalyer2Id();
String shortName1 = playerDao.getPlayer(palyer1Id).getShortName();
String shortName2 ;
if(palyer2Id==null){
shortName2="";
}else{
shortName2 = playerDao.getPlayer(palyer2Id).getShortName();
}
ArrayList<String> gameArray1 = scoreDao.getGame(matchId, team1Id);
//国籍
String nationalityId1 = playerDao.getPlayer(palyer1Id).getNationalityId();
String nationalityId2;
String nationlity2 ;
if(palyer2Id==null){
nationalityId2="";
nationlity2="";
}else{
nationalityId2 = playerDao.getPlayer(palyer2Id).getNationalityId();
nationlity2 = nationalityDao.getNationlityByid(nationalityId2);
}
String nationlity1 = nationalityDao.getNationlityByid(nationalityId1);
//t2
String palyer3Id = teamDao.getTeamsById(team2Id).getPalyer1Id();
String palyer4Id = teamDao.getTeamsById(team2Id).getPalyer2Id();
String shortName3 = playerDao.getPlayer(palyer3Id).getShortName();
String shortName4 ;
if(palyer4Id==null){
shortName4="";
}else{
shortName4 = playerDao.getPlayer(palyer4Id).getShortName();
}
ArrayList<String> gameArray2 = scoreDao.getGame(matchId, team2Id);
//国籍
String nationalityId3 = playerDao.getPlayer(palyer3Id).getNationalityId();
String nationalityId4;
String nationlity4 ;
if(palyer4Id==null){
nationalityId4="";
nationlity4="";
}else{
nationalityId4 = playerDao.getPlayer(palyer4Id).getNationalityId();
nationlity4 = nationalityDao.getNationlityByid(nationalityId4);
}
String nationlity3=nationalityDao.getNationlityByid(nationalityId3);
String nationlityname1=nationlity1+" "+nationlity2;
String nationlityname2=nationlity3+" "+nationlity4;
String PlayerName1=shortName1+" "+shortName2;
String PlayerName2=shortName3+" "+shortName4;
matchWOSArray.add(new MatchWO(date+" "+actualStartTime,type,PlayerName1,nationlityname1,gameArray1));
matchWOSArray.add(new MatchWO(date+" "+actualStartTime,type,PlayerName2,nationlityname2,gameArray2));
}
return matchWOSArray;
}
}
- 形成API接口—将数据响应给浏览器
public class MatcheController {
@Autowired
private GetService getService;
@GetMapping("/match")
public ArrayList<MatchWO> getMatches( @RequestParam("from") Integer from,@RequestParam("size") Integer size){
System.out.println(from+":"+size);
return getService.getMatcheWithOther(from,size);
}
}
public class RankingCotroller {
@Autowired
private RakingDao rakingDao;
@GetMapping("/ranking")
//
public List<List<Ranking>> getRanking(){
List<List<Ranking>> LR = new ArrayList<>();
LR.add(rakingDao.getRrakingM());
LR.add(rakingDao.getRrakingW());
return LR;
}
}
八、心路历程和收获
-
呆同学的心路历程和收获:
-
因为时间安排的比较吃紧,所以很遗憾真正开发出的项目和原型图设计的功能有一些出入。
-
再一次体会到框架的强大之处。框架的使用真的能大大提高项目开发的效率
-
虽然寒假时已经系统地学习过git的相关知识,但当时只是通读了一遍,并没有进行实际演练,导致知识过目就忘,学完以后还是两眼一摸黑,啥都不会做。
-
本次项目的开发我最大的感受就是自己距离一名真正的软件工程师真的还有太远太远的路要走了。自己的心态还是不够成熟,遇到问题容易慌乱阵脚,消极应对困难。 在网站开发的过程中我也从许多优秀的网站中学到了很多开发过程中的技巧,也意识到自己项目开发经验的匮乏。归根结底,还是自己了解的不够多,学的不够多,做的不够多,
”路漫漫其修远兮“
,我还需要好好努力!
-
-
香菜同学的心路历程和收获:
-
需求分析是软件工程中最重要的步骤之一。在开始编写代码之前,一定要仔细分析需求,确保你
-
理解用户的需求,并将其转化为可执行的软件需求规格说明书。
-
设计是软件工程的另一个重要步骤。在编写代码之前,你应该设计软件的架构和模块,并将其记录在设计文档中。这有助于确保代码的可维护性和可扩展性。
-
代码质量是非常重要的。在编写代码时,你应该注重代码的可读性和可维护性。代码应该遵循编码规范,并且应该经过代码审查和单元测试来确保其质量和可靠性。
-
团队合作是这次实践不可或缺的一部分。与团队成员进行有效的沟通,并与他们合作,可以帮助确保项目的成功。
-
总的来说,软件工程实践需要不断学习和改进。并不在一次次的实践中得到提升。
-
九、 结对队友互评/span>
-
呆同学对香菜同学的评价: 这一次的作业我们总结了上一次结对作业中存在的问题,一起努力改正。面对问题都会主动参与,不会埋怨。我认为香菜同学称得上是非常优秀的合作伙伴。总而言之,这次合作非常愉快的过程非常愉快,我也从香菜同学身上学到了很多,对我来说是一段非常宝贵的财富!
-
-
香菜同学对呆同学的评价: 积极认真,高效,能带飞。