122
社区成员
发帖
与我相关
我的任务
分享| 这个作业属于哪个课程 | 2302软件工程社区 |
|---|---|
| 这个作业要求在哪里 | 结对第二次作业——编程实现 |
| 结对学号 | 222100305庞财莹 222100401丁念 |
| 这个作业的目标 | 不限框架编程实现;完成博客撰写 |
| 其他参考文献 | CSDN 、Springboot、Vue |
| PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | ||
| • Estimate | • 估计这个任务需要多少时间 | 10 | 10 |
| Development | 开发 | ||
| • Analysis | • 需求分析 (包括学习新技术) | 240 | 480 |
| • Design Spec | • 生成设计文档 | 60 | 30 |
| • Design Review | • 设计复审 | 10 | 10 |
| • Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
| • Design | • 具体设计 | 60 | 30 |
| • Coding | • 具体编码 | 600 | 1200 |
| • Code Review | • 代码复审 | 30 | 30 |
| • Test | • 测试(自我测试,修改代码,提交修改) | 240 | 300 |
| Reporting | 报告 | ||
| • Test Repor | • 测试报告 | 120 | 180 |
| • Size Measurement | • 计算工作量 | 20 | 20 |
| • Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 30 | 60 |
| 合计 | 1450 | 2380 |
需求分析(包括学习新技术):本次作业我们决定要以前后端分离SpringBoot + Vue的形式完成,但由于后端同学之前未接触过SpringBoot,比预计多花费了很多时间在后端环境搭建,后端框架学习上。在学习初期,难免会遇到很多配置问题,这无疑很耗时间。在后续实践中,应尽量提早掌握技术,以防出现不可预料的难题。
生成设计文档与具体设计:由于在上次结对作业中,我们就已经 基本完成网页的原型设计,节省了非常多我们设计阶段的时间。故在今后进行开发时,因先经过充分沟通后,合作完成基础的原型设计,这不仅能帮助我们明确具体设计,更有助于后续开发过程中前后端保持一致,提高团队合作效率。
具体编码:以前后端分离的形式实现此次作业,由于后端同学对后端知识的不熟悉,具体编码阶段时间长、效率低,对于学习初期的后端同学是正常的。在具体编码过程中,常常会遇到因过于面向实践开发,技术底层原理不扎实而导致的各种困难,在今后,有充足的时间给后端同学对补足这一块的知识,希望在今后的实践中,能熟能生巧,提高编码效率。






于首页上方,采用了轮播图的形式,展示该赛事的精彩瞬间。
随后图文并茂的展示该赛事的最新新闻,以增加网页趣味。
具体显示效果如下:


在每日赛况页面,我们利用下拉框组件来支持用户以如下三种方式来筛选赛程信息,方便用户快速定位到想要查看的赛事,增加网站与用户的交互性。
按赛程筛选信息,默认显示各阶段(初赛、半决赛、决赛)赛程信息,具体显示如下:





以下截图记录了,我们遇到问题,共同讨论到共同解决的过程。











技术相关:Vue,axios,element-ui,echarts,vue-awesome-swiper
实现思路:
跨域
问题描述:前端访问后端接口的跨域问题
是否解决:是
解决收获:前端配置proxy代理,重写路径。
echarts
问题描述:echarts柱状图横坐标名字不显示
是否解决:是
解决收获:不是因为渲染数据的数组有问题,是因为给的容器宽度不够。
前端请求
前端统一封装了接口,只需要写路径和方法即可
export function getAll() {
return request.request({
method: "get",
url: `/players`
})
}
后端接口
@RestController
@CrossOrigin(origins = "*")
public class PlayersController {
@RequestMapping("/api/players")
public JSONArray players(){
Lib lib = new Lib();
return lib.showplayers();
}
}
后端返回效果
[
{
"PhotoPath": "http://120.26.225.240:8089/uploadimg/c71d6025-9088-4aae-b276-a9416f1cd115.png",
"CountryImagePath": "http://120.26.225.240:8089/uploadimg/Austria.png",
"FirstName": "Alexander",
"DOB": "1999-03-07",
"Country": "Austria",
"LastName": "HART",
"Gender": "Male",
"Sports": "DV"
},
...
]
前端请求
//获取赛程
export function getSchedule() {
return request.request({
method: "get",
url: `/schedule`
})
}
后端接口
@RequestMapping("/api/schedule")
public JSONArray schedule(){
Lib lib = new Lib();
return lib.showpschedule();
}
@RequestMapping("/api/scheduletime")
@ResponseBody
public JSONArray schedule(@RequestBody Map name){
Lib lib = new Lib();
return lib.showpscheduletime(name.get("Date").toString());
}
@RequestMapping("/api/scheduletype")
@ResponseBody
public JSONArray scheduletype(@RequestBody Map name){
Lib lib = new Lib();
return lib.showpscheduletype(name.get("PhaseName").toString());
}
@RequestMapping("/api/schedulelocal")
@ResponseBody
public JSONArray schedulelocal(){
Lib lib = new Lib();
return lib.showpschedulelocal();
}
@RequestMapping("/api/scheduleday")
@ResponseBody
public JSONArray scheduleday(){
Lib lib = new Lib();
return lib.showpscheduleday();
}
后端返回效果
以/api/schedule为例,后端返回按日期分组的JSONArray。
[
[
{
"SportCode": "DV",
"DisciplineId": "108c795d-5e4f-4dc6-acea-0bc70bfd1928",
"DisciplineName": "Women 1m Springboard",
"Time": "10:00:00",
"PhaseName": "Preliminaries",
"Date": "2024-01-18"
},
...
]
...
]
import axios from "axios";
// 创建axios,赋给变量service
const request = axios.create({
baseURL: 'http://120.26.225.240:8089/api',
timeout: 15000, // 超时
});
//添加请求拦截器
request.interceptors.request.use(
function (config) {
config.headers['Access-Control-Allow-Origin'] = '*';
//在发送请求之前做一些事情
return config;
},
function (error) {
//请求错误做些什么
return Promise.reject(error);
}
);
//添加响应拦截器
request.interceptors.response.use(
function (response) {
//使用响应数据返回响应;
const data = response;
// 如果data.code
if (data) {
return Promise.resolve(data.data);
}
return response;
},
function (error) {
//使用响应错误返回
return Promise.reject(error);
}
);
// 将service 导出
export default request;
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave:false,
devServer: {
port: 8080,
proxy: { //设置代理
'/api': { //设置拦截器
target: 'http://120.26.225.240:8089', //代理的目标地址
changeOrigin: true,
pathRewrite: { //路径重写
'/api': '' //选择忽略拦截器里面的内容
}
}
},
host: '0.0.0.0',
client: {
},
headers: {
'Access-Control-Allow-Origin': '*',
}
}
})
//带参数跳转
handleSelect(key) {
if(this.activeIndex != key)
this.$router.push(
{
name:key,
}
)
else{
alert('当前已经是目标页面')
}
},
//在router.js定义各个路径情况,此处展示奖牌榜的
import Medal from '../views/Medal.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
redirect: "/home",
},
{
path: "/medal",
name: 'Medal',
component: Medal
}
]
const router = new VueRouter({
base: process.env.BASE_URL,
routes
})
export default router
//使用element-ui的table组件,自定义列,加入选手国家图片等
<el-table-column
label="Country"
width="180">
<template slot-scope="scope">
<div class="tem">
<img :src="scope.row.CountryImagePath" alt="" class="countryPic">
<span class="country">{{scope.row.Country }}</span>
</div>
</template>
</el-table-column>
<TopBar :index="index"/>//将导航栏抽离成组件,在需要的页面使用
<Buttom/>//抽离底部
处理上传的handler
@RestController
public class UploadHandler {
private Logger logger = LoggerFactory.getLogger(UploadHandler.class);
@Autowired
private UploadService uploadService;
@PostMapping("/uploadimg")
public String uploadimg(HttpServletRequest request) throws IOException, ServletException {
// handler调用文件上传的service 得到文件的虚拟路径
String filepath = uploadService.uploadImg(request);
return filepath;
}
}
处理文件上传的service,在service中将上传的文件夹和映射的文件夹都放在配置文件中
@Service
public class UploadService {
private Logger logger = LoggerFactory.getLogger(UploadService.class);
// 文件的真实路径
@Value("${file.uploadFolder}")
private String realBasePath;
@Value("${file.accessPath}")
private String accessPath;
public String uploadImg(HttpServletRequest request) throws IOException, ServletException {
InputStream inputStream = request.getInputStream();
//获取请求头中Contect-Type的值
// 图片后缀
String imgSuffix = "png";
Enumeration enumeration=request.getHeaderNames();
while(enumeration.hasMoreElements()) {
String name=(String)enumeration.nextElement();
if(name.equals("content-type")){
String value=request.getHeader(name);
imgSuffix = value.split("/")[1];
logger.info("header中" + name + " " + value);
logger.info("文件后缀:" + imgSuffix);
}
}
// 文件唯一的名字
String fileName = UUID.randomUUID().toString() + "." +imgSuffix;
Date todayDate = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String today = dateFormat.format(todayDate);
// 域名访问的相对路径(通过浏览器访问的链接-虚拟路径)
String saveToPath = accessPath + today + "/";
// 真实路径,实际储存的路径
String realPath = realBasePath + today + "/";
// 储存文件的物理路径,使用本地路径储存
String filepath = realPath + fileName;
logger.info("上传图片名为:" + fileName+"--虚拟文件路径为:" + saveToPath +"--物理文件路径为:" + realPath);
// 判断有没有对应的文件夹
File destFile = new File(filepath);
if (!destFile.getParentFile().exists()) {
destFile.getParentFile().mkdirs();
}
// 输出流 输出到文件
OutputStream outputStream = new FileOutputStream(destFile);
// 缓冲区
byte[] bs = new byte[1024];
int len = -1;
while ((len = inputStream.read(bs)) != -1) {
outputStream.write(bs,0,len);
}
inputStream.close();
outputStream.close();
// 返回虚拟路径,给链接访问
return saveToPath+fileName;
}
}
设置虚拟路径,访问绝对路径下资源
@Configuration
public class UploadConfig implements WebMvcConfigurer{
@Value("${file.staticAccessPath}")
private String staticAccessPath;
@Value("${file.uploadFolder}")
private String uploadFolder;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler(staticAccessPath).addResourceLocations("file:" + uploadFolder);
}
}
@RestController
@CrossOrigin(origins = "*")
public class PlayersController {
}
public JSONArray showplayers(){
//获取按国籍分类的选手数组
JSONArray jsonArray = JSONArray.parseArray(getJsonStr("src/datas/athletes.json"));
JSONArray allplayers = new JSONArray();
for(int i = 0; i < 5; i++){
//获取第i个国家的信息
JSONObject jsonObject = jsonArray.getJSONObject(i);
//获取国籍信息
String country = jsonObject.getString("CountryName");
String countryImagePath = "http://120.26.225.240:8089/uploadimg/" + country + ".png";
//获取多位选手信息
JSONArray Participations = jsonObject.getJSONArray("Participations");
for(int j = 0;j < Participations.size(); j++){
JSONObject player = new JSONObject();
JSONObject athlete = Participations.getJSONObject(j);
String firstName = athlete.getString("PreferredFirstName");
String lastName = athlete.getString("PreferredLastName");
String dob = athlete.getString("DOB").substring(0,10);
String gender = (athlete.getInteger("Gender") == 0 ? "Male" : "Female");
String sports =athlete.getString("Sports");
String photoPath = "http://120.26.225.240:8089/uploadimg/" + athlete.getString("ScoreboardPhotoId") + ".png";
if (athlete.getString("ScoreboardPhotoId") == null){
photoPath = null;
}
//获取每位选手所需传输数据
player.put("Country",country);
player.put("FirstName",firstName);
player.put("LastName",lastName);
player.put("Gender",gender);
player.put("DOB",dob);
player.put("Sports",sports);
player.put("PhotoPath",photoPath);
player.put("CountryImagePath",countryImagePath);
allplayers.add(player);
}
}
return allplayers;
}
222100401丁念:在本次作业中,我负责后端与服务器部署部分,由于之前没有接触过后端开发,在本次作业发布之前就抓紧去学习了SpringBoot相关知识,尽管如此,此次作业还是困难重重,配置问题、跨域问题、部署问题等等,都使本就不富裕的作业完成时间变得更加紧巴巴。好在,最后也算顺利地交上了一个满意的答卷。此次作业,丰富了我的实践开发经验,不仅让我接触学习了SpringBoot等知识,更让我明白在团队写作中,沟通的重要性。尽管此次我与我的队友是舍友,沟通非常便携,充分,但意外还会发生,我们仍然还是出现了因为信息不一致而导致的返工现象。在此次作业中,我收获良多,我想这次经历也为我在后续团队作业中打下一个很好的基础。
222100305庞财莹:在本次作业中,我负责前端内容的编写。我使用了一些提高开发效率的工具,比如使用element-ui编写页面,可以让自适应更好且避免了写原生内容的繁杂,抽离可复用的内容,提高代码复用率和可维护性。在本次结对作业中,我意识到了有一些内容要提前沟通完成,比如接口的数据结构等等,这样的提高两个人的开发效率,更快更好地实现页面功能。
222100401丁念:在本次作业中,队友有着丰富的前端开发经历,给我安全感满满!并且,能在我后端学习时,非常明确的告诉我前端需要什么,我需要完成哪些事,这大大提高了我的学习与编码效率。队友的工作步调与我非常一致,我们合作的非常舒服,希望下次还能与队友共同合作。
222100305庞财莹:队友虽然没有开发过后端的内容,但是学习能力非常强,解决问题也很积极迅速,可以很快的一起寻找问题的出处,共同解决问题。让我们的开发非常顺利的进行,非常愉快地合作写完了此次作业。