从选课系统看软件开发周期

559 阅读12分钟

需求分析:

校园选课系统的目的是为每一个学生建立一个选课档案,负责管理一个学生从选课到考试结束的所用信息。

本选课系统分为用户管理,课程管理,系别管理,选课管理等几大模块,其功能涵盖了选课管理的主要方面,同时具有信息存储及时,检索迅速、查找方便、可靠性高、存储量大、保密性好、寿命长、成本低等优点,满足了学校的实际要求,大大提高了选课管理的效率。

用户需求:

(1)系统功能要求

  1. 系别/课程/用户的录入

在学期开始时由系统管理员利用系统量录入系别、录入课程、录入学生、录入教师、学生选课,以节约人员加快速度。

  1. 系别/课程/用户的修改

由系统管理员得到学校相关人员的同意后进行修改操作,包括录入成绩及修改成绩,并且保留操作记录。

  1. 系别/课程/用户的删除

学校相关人员同意后,由系统管理员删除记录,并且系统本身应记录该事件并保存原始数据。

  1. 系别/课程/用户/选课的查询

用户根据用户名以及相对应的密码登陆系统,然后可以根据其权限查看对应的相关信息。密码初始化由用户自身进行修改,系统对每次用户操作的数据应该在一定的时间段内保留。

(2)系统安全性

  1. 系统应该防止未登录及没有权限的访问,要求有较高的安全性。

可行性研究:

可行性研究的目的是用最小的代价在尽可能短的时间里确定问题是否能解决。我们具体从下面四个方面进行分析:

(1)技术上的可行性

技术上的可行性分析主要分析现有技术条件能否顺利完成开发工作,软硬件配置能否满足开发者需要等,现代计算机硬件和软件技术的飞速发展,为系统的建设提供了技术条件。由于对校园选课系统这一类的系统进行开发已有一定的时期,有很多成功的实例,技术基础也已经非常雄厚,因而技术上的准备应该不成问题。

(2)经济上的可行性

主要是对项目的经济效益进行评价,一方面是支出的费用,其中包括设备购置费,软件开发费,管理和维护费,人员工资和培训费等。一方面是效益,包括降低办公费用,提高办公室效率,为学校管理提供方便。由于选课系统是一个比较小型的系统,所以从人力、物力、财力方面来说都是可行的。

(3)管理上的可行性

主要是管理人员对开发信息系统是否支持,现有管理制度和方法是否科学,规章制度是否齐全,原始数据是否正确等,学籍管理系统比较完善的规章制度和管理方法为系统的建设提供了保障。

(4)社会可行性

社会可行性研究是对系统投入使用后对社会可能带来的影响进行分析,比如是否为人们所接受,是否为社会带来利益。

可行性分析的最后成果是可行性分析报告,可行性分析报告包括下列内容:

1、选课管理系统的总体目标及主要功能。

2、从经济,技术,管理,社会等方面论证方案的可行性。

经过对用户需求进行调查,做出了用户需求分析,从而确定了系统设计的几大模块以满足学校的实际要求。在可行性研究方面,主要从技术、经济、管理和社会四个角度对系统的可行性进行分析,并最终做出了可行性分析报告。

概念结构设计:

E-R 图

逻辑结构设计:

数据字典

主要涉及以下几个表,分别是:用户信息表、系别信息表、课程信息表、选课信息表。以下是数据字典

用户信息表:

字段备注
user_id用户号
user_name用户名
user_sex用户性别
user_age用户年龄
user_phone用户手机号
password密码
role用户角色(student/teacher)

系别信息表:

字段备注
dept_id系号
dept_name系名

课程信息表:

字段备注
course_id课程号
course_name课程名
course_credit学分
spare_time课程余量

选课信息表:

字段备注
course_id课程号
user_id用户号
grade成绩

数据流程图

业务流程图

关系模型

根据转换规则,将E-R模型转换成关系模型

用户信息表 (学号, 姓名, 年龄, 性别, 系号) 主键: 学号, 外键: 系号

课程信息表 (课程号, 课程名, 学分, 课余量) 主键: 课程号

系别信息表 (系号, 系名) 主键: 系号

选课信息表 (学号, 课程号, 成绩) 主键: 学号和课程号(联合主键)

物理结构设计:

基本表

用户信息表:

列名数据类型允许空主键约束条件
user_idint(11)自增
user_namechar(30)
user_sexint(1)0/1 默认0
user_ageint(2)
user_phoneint(11)
dept_idint(11)外键
rolechar(16)默认student
passwordchar(16)默认123456

系别信息表:

列名数据类型允许空主键约束条件
dept_idint(11)自增
dept_namechar(30)

课程信息表:

列名数据类型允许空主键约束条件
course_idint(11)自增
course_namechar(30)
course_creditint(2)
spare_timeint(2)默认20

选课信息表:

列名数据类型允许空主键约束条件
course_idint(11)
user_idint(11)
gradeint(3)0~100

索引

用户信息表的主索引列为: user_id

系别信息表的主索引列为: dept_id

课程信息表的主索引列为: course_id

选课信息表的主索引列为: (course_id,user_id)

视图

关系数据模型分析的结果,不同的用户看到的数据不一致,需要定义不同的用户视图,但考虑到数据库与前台开发程序的连接比较复杂,所以数据库没有单独定义用户视图,用有选择性select 语句代替不同的用户视图

创建数据库:

  1. 登录mysql:
    • mysql -u root -p (root为账号名)
    • root (root为密码)
  2. 创建数据库
    • show databases; 查看所有数据库
    • create database mysql_test; (mysql_test为数据库名)
  3. 选择数据库
    • use mysql_test; 选择数据库

创建表:

用户信息表:

mysql> create table user(
    -> user_id int(11) not null auto_increment,
    -> user_name char(30) not null,
    -> user_sex int(1) null default 0,
    -> user_age int(2) null,
    -> user_phone char(11) not null,
    -> password char(16) null  default '123456',
    -> dept_id int(11) not null,
    -> role char(16) null default 'student',
    -> primary key(user_id),
    -> FOREIGN KEY (`dept_id`) REFERENCES `dept` (`dept_id`)
    -> );

结果:

系别信息表:

mysql> create table dept(
    -> dept_id int(11) not null auto_increment,
    -> dept_name char(30) not null,
    -> primary key(dept_id),
    -> );

结果:

课程信息表:

mysql> create table course(
    -> course_id int(11) not null auto_increment,
    -> course_name char(30) not null,
    -> course_credit int(2) not null,
    -> spare_time int(2) null default 20
    -> primary key(course_id),
    -> );

结果:

选课信息表:

mysql> create table elective(
    -> course_id int(11) not null,
    -> user_id int(11) not null,
    -> grade int(3) not null,
    -> primary key(course_id,user_id),
    -> );

结果:

操作纪录:

用户表

// 联表查询用户列表
const USERLIST_SQL = `select user_id, user_name, user_sex, user_phone, user_age, user.dept_id, dept_name, role from user join dept on user.dept_id=dept.dept_id`
// 联表查询用户详情
const USERROLELIST_SQL = `select user_id, user_name, user_sex, user_phone, user_age, user.dept_id, dept_name, role from user join dept on user.dept_id=dept.dept_id where role=?`;
// 联表查询用户详情
const USERINFO_SQL = `select user_id, user_name, user_sex, user_phone, user_age, user.dept_id, role from user cross join dept on user.dept_id=dept.dept_id where user_id=?`; 
// 添加用户
const USERADD_SQL = `insert into user set ?`;
// 修改用户
const USERUPDATE_SQL = `update user set user_name=?, user_sex=?, user_phone=?, user_age=?, dept_id=? where user_id=?`;
// 修改密码
const PASSWORDUPDATE_SQL = `update user set password=? where user_id=?`;
// 删除用户
const USERDELETE_SQL = `delete from user where user_id=?`;

查询系别表

// 查询系别列表SQL:
const DEPTLIST_SQL = `select * from dept`;
// 查询系别详情SQL:
const DEPTINFO_SQL = `select * from dept where dept_id=?`;
// 添加系别
const DEPTADD_SQL = `insert into dept set ?`;
// 修改系别
const DEPTUPDATE_SQL = `update dept set dept_name=? where dept_id=?`;
// 删除系别
const DEPTDELETE_SQL = `delete from dept where dept_id=?`;

课程表

// 查询教师课程列表
const COURSELIST_SQL = `select * from course`;
//查询教师课表详情
const COURSEINFO_SQL = `select * from course where course_id=?`;
// 联表综合查询学生课程列表
const ELECTIVEUSERID_SQL = `select course.course_id,course.course_name,course.course_credit,course.spare_time,case when course.course_id=elective.course_id and elective.user_id=? then 1 else 0 end as isElective from mysql_test.course left join mysql_test.elective on course.course_id=elective.course_id and elective.user_id=?;`;
// 联表综合查询学生课程详情
const ELECTIVEUSERIDINFO_SQL = `select course.course_id,course.course_name,course.course_credit,course.spare_time,case when course.course_id=elective.course_id and elective.user_id=? then 1 else 0 end as isElective from course,elective where course.course_id=? and course.course_id=elective.course_id and elective.user_id=?;`;
// 添加课程
const COURSEADD_SQL = `insert into course set ?`;
// 修改课程
const COURSEUPDATE_SQL = `update course set course_name=?, course_credit=? where course_id=?`;
// 修改课余量 
const COURSESPARETIME_SQL = `update course set spare_time=? where course_id=?`;
// 删除课程
const COURSEDELETE_SQL = `delete from course where course_id=?`;

选课信息表

// 联表查询选课列表
const ELECTIVELIST_SQL = `select elective.user_id, elective.course_id, elective.grade, user.user_name, course.course_name from user,course,elective where elective.user_id=user.user_id and elective.course_id=course.course_id`;
// 联表查询用户选课详情
const ELECTIVEUSERLIST_SQL = `select elective.user_id, elective.course_id, elective.grade, user.user_name, course.course_name from user,course,elective where elective.user_id=user.user_id and elective.course_id=course.course_id and elective.user_id=?`;
// 选课详情
const ELECTIVEINFO_SQL = `select elective.user_id, elective.course_id, elective.grade, user.user_name, course.course_name from user,course,elective where elective.user_id=user.user_id and elective.course_id=course.course_id and elective.user_id=? and elective.course_id=?`;
// 添加选课
const ELECTIVEADD_SQL = `insert into elective set ?`;
// 修改成绩
const ELECTIVEUPDATE_SQL = `update elective set grade=? where user_id=? and course_id=?`;
// 删除选课
const ELECTIVEDELETE_SQL = `delete from elective where user_id=? and course_id=?`;

界面设计:

学生

数据库连接:

/**
 * 数据库连接
 * @param {*} sql 是sql语句
 * @param {*} values 是sql语句中的具体值
 * @returns 
 */
const query = (sql, values) => {
  return new Promise((resolve, reject) => {
    //初始化连接池
    pools["data"].getConnection((err, connection) => {
      if (err) {
        console.log(err, "数据库连接失败");
      } else {
        console.log("数据库连接成功");
        //操作数据库
        connection.query(sql, values, (err, results) => {
          if (err) {
            reject(err);
          } else {
            connection.release();
            resolve({
              result: true,
              data: results,
            });
          }
        });
      }
    });
  });
};

功能实现:

整体功能实现代码量较多,在此处仅以选课模块功能进行展示

教师查询选课列表

/**
 * 获取选课列表
 * @url /elective/list
 */
router.post("/list", async (ctx) => {
  const request = ctx.request.body;
  const { grade } = request;
  let res = {};
  if (grade) {
    res = JSON.parse(JSON.stringify(await query(ELECTIVEGRADE_SQL, [grade])));
  } else {
    res = JSON.parse(JSON.stringify(await query(ELECTIVELIST_SQL)));
  }
  if (res.result) {
    ctx.body = {
      status: 200,
      result: true,
      data: res.data,
      message: "获取成功",
    };
  } else {
    ctx.body = {
      status: 200,
      result: false,
      message: "获取失败",
    };
  }
});

学生查询选课列表

/**
 * 根据token获取选课列表
 * @url /elective/user/list
 */
router.post("/user/list", async (ctx) => {
  const request = ctx.request.body;
  const user_id = ctx.request.header.token;
  let res = JSON.parse(JSON.stringify(await query(ELECTIVEUSERLIST_SQL, [user_id])));

  if (res.result) {
    ctx.body = {
      status: 200,
      result: true,
      data: res.data,
      message: "获取成功",
    };
  } else {
    ctx.body = {
      status: 200,
      result: false,
      message: "获取失败",
    };
  }
});

获取选课详情

/**
 * 获取选课详情
 * @url /elective/:user_id/:course_id/info
 */
router.get("/:user_id/:course_id/info", async (ctx) => {
  const params = ctx.params;
  const { user_id, course_id } = params;
  const res = JSON.parse(
    JSON.stringify(await query(ELECTIVEINFO_SQL, [user_id, course_id]))
  );
  if (res.result) {
    ctx.body = {
      status: 200,
      result: true,
      data: {
        ...res.data[0],
      },
      message: "获取成功",
    };
  } else {
    ctx.body = {
      status: 200,
      result: false,
      message: "获取失败",
    };
  }
});

学生选课

/**
 * 添加选课
 * @url /elective/:course_id/info
 */
router.post(`/:course_id/info`, async (ctx) => {
  const params = ctx.params;
  const user_id = ctx.request.header.token;
  const { course_id } = params;
  const data = JSON.parse(
    JSON.stringify(await query(ELECTIVEINFO_SQL, [user_id, course_id]))
  );
  const queryData = {
    user_id,
    course_id,
  };
  if (data.result && data.data.length !== 0) {
    ctx.body = {
      status: 200,
      result: false,
      message: "已选课",
    };
    return;
  }

  const courseInfo = JSON.parse(
    JSON.stringify(await query(COURSEINFO_SQL, [course_id]))
  );
  let item = courseInfo.data[0];
  if (item.spare_time == 0) {
    ctx.body = {
      status: 200,
      result: false,
      message: "课程余量不足",
    };
    return;
  }
  item.spare_time--;
  const course = await query(COURSESPARETIME_SQL, [item.spare_time, course_id]);
  const res = await query(ELECTIVEADD_SQL, queryData);
  if (res.result && course.result) {
    ctx.body = {
      status: 200,
      result: true,
      message: "选课成功",
    };
  } else {
    ctx.body = {
      status: 200,
      result: false,
      message: "选课失败",
    };
  }
});

录入成绩及修改成绩

/**
 * 修改成绩
 * @url /elective/:user_id/:course_id/info
 */
router.put(`/:user_id/:course_id/info`, async (ctx) => {
  const request = ctx.request.body;
  const { user_id, course_id } = ctx.params;
  const { grade } = request;
  const queryData = [grade, user_id, course_id];
  const res = await query(ELECTIVEUPDATE_SQL, queryData);
  if (res.result) {
    ctx.body = {
      status: 200,
      result: true,
      message: "修改成功",
    };
  } else {
    ctx.body = {
      status: 200,
      result: false,
      message: "修改失败",
    };
  }
});

删除选课

/**
 * 删除选课
 * @url /elective/:user_id/:course_id/info
 */
router.delete(`/:user_id/:course_id/info`, async (ctx) => {
  const { user_id, course_id } = ctx.params;
  if (course_id && user_id) {
    const res = await query(ELECTIVEDELETE_SQL, [user_id, course_id]);
    if (res.result) {
      ctx.body = {
        status: 200,
        result: true,
        message: "删除成功",
      };
    } else {
      ctx.body = {
        status: 200,
        result: false,
        message: "删除失败",
      };
    }
  }
});

功能测试:

登录验证功能测试

编号测试内容测试结果
1输入错误的用户名和密码,应提示"用户不存在"通过
2输入正确的用户名和错误的密码,应提示"密码不正确"通过
3不输入用户名和密码,应提示"请输入用户名/请输入密码"通过
4输入学生用户名和密码,进入学生主页通过
5输入教师用户名和密码,进入教师主页通过

修改密码功能测试

编号测试内容测试结果
1原密码/新密码/确认密码不填,提示请输入通过
2原密码输入错误,应提示"原密码不正确"通过
3原密码和新密码输入一致,应提示"新密码不能与原密码一致"通过
4新密码和确认密码不一致,应提示"两次输入不一致"通过
5原密码/新密码/确认密码正确填写,应提示"修改成功,请重新登录"通过

用户管理功能测试

编号测试内容测试结果
1添加不存在的用户,应提示"添加成功"通过
2添加存在的用户,应提示"用户已存在"通过
3修改用户,应提示"修改成功"通过
4删除用户,应提示"删除成功"通过
5输入用户ID查询,结果为对应用户数据通过
6选择用户角色查询,结果为对应角色的用户数据通过

课程管理功能测试

编号测试内容测试结果
1添加不存在的课程,应提示"添加成功"通过
2添加存在的课程,应提示"课程已存在"通过
3修改课程,应提示"修改成功"通过
4删除课程,应提示"删除成功"通过
5输入课程ID查询,结果为对应课程数据通过

系别管理功能测试

编号测试内容测试结果
1添加不存在的系别,应提示"添加成功"通过
2添加存在的系别,应提示"系别已存在"通过
3修改系别,应提示"修改成功"通过
4删除系别,应提示"删除成功"通过
5输入系别ID查询,结果为对应系别数据通过

选课管理功能测试

编号测试内容测试结果
1学生选课,应提示"选课成功"通过
2录入选课成绩,应提示"录入成功"通过
3修改选课成绩,应提示"修改成功"通过
4删除选课,应提示"删除成功"通过
5选择课程,结果为对应选课数据通过
6课余量为0时再选课,应提示"课程余量不足"通过

总结:

  1. 本系统是对选课系统的的简单实现,模拟了一个简单应用的开发生命周期,实现整个的开发流程可以更好体验项目开发思想,可以很大程度提升自身技术。
  2. 本系统web端采用的技术栈Vue+Element-UI,服务端采用的是Node.js+Koa2,数据库语言采用的是MySql。
  3. 对本项目有兴趣的同学可以 移步仓库
  4. 有兴趣的同学可以关注下前端小溪公众号