基于springboot+spring cloud+jpa+vue实现学生成绩管理系统
1、基本技术
通过之前的微服务入门,这次不再mybatis上开发,改为基于spring jpa开发一个简单的学生成绩管理系统,基本SpringBoot + SpringCloud + JPA + VUE框架开发
2、需求说明
使用微服务架构完成《学生成绩管理系统》,它包含学生表和成绩表(科目是动态保存和动态展示)。
具体思路是:
- 使用Eureka搭建注册中心,并实现高可用。
- 开发学生成绩管理业务服务,并向注册中心注册。
- 使用ZUUL或Spring Cloud Gateway开发网关服务。
- 使用VUE+element-ui开发前端工程。
3、页面效果
页面需求
1、学生成绩列表页面
科目是动态列表
学生姓名: |
|
|
学号: |
|
|
查询 |
|
新增 |
|
|
|
|
|
|
|
|
|
学生姓名 |
语文 |
数学 |
英语 |
物理 |
化学 |
生物 |
总成绩 |
操作 |
张三 |
87 |
45 |
98 |
56 |
78 |
55 |
419 |
修改,删除 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
分页 |
1,2,3 |
|
|
|
|
2、修改和新增页面
成绩可以动态添加一行
学生姓名 |
李四 |
|
学生学号 |
3458986 |
|
|
|
|
|
|
|
|
|
科目 |
语文 |
分数 |
90 |
|
新增 |
删除 |
科目 |
数学 |
分数 |
80 |
|
新增 |
删除 |
科目 |
历史 |
分数 |
100 |
|
新增 |
删除 |
|
|
|
|
|
|
|
|
保存 |
|
|
|
|
|
4、数据库设计
学生表:(student)
字段名 |
字段说明 |
字段类型 |
为空 |
备注 |
id |
主键 |
int |
否 |
唯一主键,自增长 |
sname |
学生姓名 |
varchar(50) |
否 |
|
sno |
学号 |
varchar(50) |
否 |
学号唯一 |
成绩表:(score)
字段名 |
字段说明 |
字段类型 |
为空 |
备注 |
id |
主键 |
int |
否 |
唯一主键,自增长 |
cname |
课程名称 |
varchar(50) |
否 |
|
fraction |
课程分数 |
int |
否 |
|
sid |
学生ID |
int |
是 |
学生表外键 |
5、后台实现
后端
提供者(grade-service-provider)项目结构
后端实体类,本次采用spring jpa自动生成表的方法,需要在yml配置
贴出yml代码
server:
port: 8081
spring:
application:
name: grade-provider
main:
allow-bean-definition-overriding: true
profiles:
active: '@aaa@qq.com'
datasource:
url: jdbc:mysql://localhost:3306/grade?serverTimezone=GMT%2B8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
mybatis:
type-aliases-package: cn.grade.service.entity
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
jpa内部封装了很多的方法,所以我们直接可以调用就行了,非常方便
给出StudentDao接口的代码
@Repository
public interface StudentDao extends JpaRepository<Student,Long>, JpaSpecificationExecutor<Student> {
//带条件分页查询
Page<Student> findAllBySnameContainingAndSnoContaining(String name, String sno, Pageable pageable);
//根据学号查询学生是否存在
Student findBySno(String sno);
}
给出StudentService代码
/**
* @program: grade-service-provider
* @author: Mr.M
* @create: 2020-07-14
**/
@Service
public class StudentService {
@Autowired
private StudentMapper studentMapper;
@Autowired
private StudentDao studentDao;
public Student queryById(Long id) {
return this.studentMapper.selectByPrimaryKey(id);
}
/*分页学生成绩以及根据姓名与学号查询*/
public R findAllBySnameContainingAndSnoContaining(PageStudentREQ req) {
//获取每页条数
req.setPageSize(req.getPageSize() < 1 ? 3 : req.getPageSize());
//获取起始页
req.setPageNow(req.getPageNow() < 1 ? 0 : (req.getPageNow() - 1));
//定义分页条件
System.out.println("service分页:"+req.getPageNow());
PageRequest pageRequest = PageRequest.of(req.getPageNow(), req.getPageSize());
System.out.println(req.getPageNow()+"----------"+req.getPageSize());
//获取分页数据
//在jpa方法调用中,由于传进来的数据不能为null,所以将其设置为""
if(req.getSname() == null) {
req.setSname("");
}
if(req.getSno()==null) {
req.setSno("");
}
Page<Student> page = studentDao.findAllBySnameContainingAndSnoContaining(req.getSname(), req.getSno(), pageRequest);
//System.out.println("stuName:"+req.getStuName()+"------------"+"stuNo:"+req.getStuNo());
//新建一个map集合,用来存放返回的数据
Map map = new HashMap<>();
//新建一个科目的集合,用来存放所有的科目
Set courseNames = new HashSet<>();
//将分页总条数放入map集合中
map.put("count",page.getTotalElements());
//创建一个PageStudentRESQ集合
List<PageStudentRESQ> resqList = new ArrayList<>();
//定义一个PageStudentRESQ对象
PageStudentRESQ resq;
//定义一个成绩的集合
Map scoreMap;
//循环分页数据中的list
for (Student s:page.getContent()) {
resq = new PageStudentRESQ();
scoreMap = new HashMap();
// 将获取到的学生信息赋值到resq中
resq.setId(s.getId());
resq.setSname(s.getSname());
resq.setSno(s.getSno());
int score = 0;
// 将成绩循环放入成绩集合中
for (Score h:s.getScores()) {
//通过键值对的方式存入成绩
scoreMap.put(h.getCname(),h.getFraction());
//通过set集合的特性存入科目名称并去重
courseNames.add(h.getCname());
//计算学生总分
score += h.getFraction();
}
resq.setScore(score);
//将成绩存入resq中
resq.setScores(scoreMap);
//将赋值后的StudentPageRESQ放入resqList中
resqList.add(resq);
}
//将分页数据存入map中
map.put("list",resqList);
//将所有科目的名称的集合存入map中
map.put("courseNames",courseNames);
//System.out.println("map:"+map);
return R.ok().data("map",map);
}
@Transactional
/*新增或修改学生成绩*/
public R add(Student stu){
/*遍历获取学生相应科目成绩*/
Set<Score> scores =stu.getScores();
scores.forEach(h->{
h.setStudent(stu);
});
/*保存新增或者修改后的学生信息*/
Student save = studentDao.save(stu);
return R.ok().data("stu",save);
}
/**
* @Author: M
* @Description: 根据ID查询学生信息
* @DateTime: 2020/7/14
* @Params: [id]
* @Return cn.grade.service.entity.R
*/
public R findById(Long id) {
Student one = studentDao.getOne(id);
return R.ok().data("one",one);
}
/**
* @Author: M
* @Description: 根据id删除
* @DateTime: 2020/7/14
* @Params: [id]
* @Return void
*/
public boolean del(Long id){
studentDao.deleteById(id);
return true;
}
/**
* @Author: M
* @Description: 检查学号是否存在
* @DateTime: 2020/7/14
* @Params: [sno]
* @Return boolean
*/
public boolean stuNoValid(String sno){
//调用学号的接口
Student stu = studentDao.findBySno(sno);
//如果返回值为空,说明不存在该学号,则返回true
if(stu == null) {
return true;
}else {
//如果返回值不为空,则已经存在该学号,则返回false
return false;
}
}
}
给出Student实体类代码
@Entity
@Table(name = "student")
@ApiModel(value="学生表",description = "这是学生表")
@JsonIgnoreProperties(value = { "hibernateLazyInitializer"})
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
// 学生姓名
@Column(name = "sname")
private String sname;
// 学生学号
@Column(name = "sno")
private String sno;
//targetEntity属性表示默认关联的实体类型,默认为当前标注的实体类;
//cascade属性表示与此实体一对一关联的实体的联级样式类型。联级样式上当对实体进行操作时的策
略。
//·CascadeType.PERSIST (级联新建)
//·CascadeType.REMOVE (级联删除)
//·CascadeType.REFRESH (级联刷新)
//·CascadeType.MERGE (级联更新)中选择一个或多个。
//·还有一个选择是使用CascadeType.ALL ,表示选择全部四项
//fetch属性是该实体的加载方式,有两种:LAZY和EAGER。
//optional属性表示关联的实体是否能够存在null值。默认为true,表示可以存在null值。如果为false,则要同时配合使用@JoinColumn标记。
//mappedBy属性用于双向关联实体时,标注在不保存关系的实体中。
@OneToMany(targetEntity = Score.class,mappedBy = "student",cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Score> scores = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public String getSno() {
return sno;
}
public void setSno(String sno) {
this.sno = sno;
}
public Set<Score> getScores() {
return scores;
}
public void setScores(Set<Score> scores) {
this.scores = scores;
}
}
给出Score实体类代码
@Entity
@Table(name = "score")
@ApiModel(value="成绩表",description = "这是成绩表")
@JsonIgnoreProperties(value = { "hibernateLazyInitializer"})
public class Score implements Serializable {
private static final long SerialVersionUID = 1L;
// @JsonFormat可以帮我们完成格式转换。例如对于Date类型字段,如果不适用JsonFormat默认在rest返回的是long
// ,如果我们使用@JsonFormat(timezone = “GMT+8”, pattern = “yyyy-MM-dd HH:mm:ss”)
// ,就返回"2020-07-13 22:58:15"
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="id")
private Long id; //成绩表ID
@Column(name = "cname")
private String cname; //课程名称
@Column(name = "fraction")
private Integer fraction; //课程分数
//mappedBy = "Studeent" 用来级联
@JsonIgnoreProperties(value={"scores"})
@ManyToOne(targetEntity = Student.class)
@JoinColumn(name="sid",referencedColumnName = "id") //设置外键
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
public Integer getFraction() {
return fraction;
}
public void setFraction(Integer fraction) {
this.fraction = fraction;
}
}
给出StudentController层的代码
@RestController
@RequestMapping("student/")
@CrossOrigin
public class StudentController {
@Autowired
private StudentService studentService;
/**
* @Author: M
* @Description: 根据id查询,此方法调用mybatis实现
* @DateTime: 2020/7/14
* @Params: [id]
* @Return cn.grade.service.entity.Student
*/
@GetMapping("getByid/{id}")
public Student queryById(@PathVariable("id") Long id) {
return this.studentService.queryById(id);
}
/**
* @Author: M
* @Description: 根据ID查询,此方法为JPA实现
* @DateTime: 2020/7/14
* @Params: [id]
* @Return cn.grade.service.entity.R
*/
@GetMapping("findByid/{id}")
public R findById(@PathVariable("id") Long id){
return studentService.findById(id);
}
/**
* @Author: M
* @Description: 带条件分页查询
* @DateTime: 2020/7/14
* @Params: [current, limit, req]
* @Return cn.grade.service.entity.R
*/
@PostMapping("list/{current}/{limit}")
public R list(@PathVariable int current, @PathVariable int limit,@RequestBody PageStudentREQ req){
req.setPageNow(current);
req.setPageSize(limit);
System.out.println("开始页:"+req.getPageNow());
return studentService.findAllBySnameContainingAndSnoContaining(req);
}
/**
* @Author: M
* @Description: 添加或者更新操作
* @DateTime: 2020/7/14
* @Params: [stu]
* @Return cn.grade.service.entity.R
*/
@PostMapping("addOrUpdate")
public R addOrUpdate(@RequestBody Student stu){
return studentService.add(stu);
}
/**
* @Author: M
* @Description: 根据ID删除学生
* @DateTime: 2020/7/14
* @Params: [id]
* @Return cn.grade.service.entity.R
*/
@DeleteMapping("delete/{id}")
public R deleteById(@PathVariable("id") Long id){
if(studentService.del(id)){
return R.ok();
}else {
return R.error();
}
}
/**
* @Author: M
* @Description: 验证学号是否存在
* @DateTime: 2020/7/14
* @Params: [sno]
* @Return cn.grade.service.entity.R
*/
@GetMapping("stuNoValid/{sno}")
public R stuNoValid(@PathVariable String sno){
//如果不存在,则为真,返回R.ok
if(studentService.stuNoValid(sno)) {
return R.ok().data("info","没有该用户");
}else {
//否则返回R.error()
return R.error();
}
}
}
6、前端实现
前端采用vue+element-ui实现
官方网站:https://element.eleme.cn/#/zh-CN
里面有许多的组件,对于开发是非常方便快速的
页面实现效果
学生成绩管理页面
前端开发工具,采用VS code,运用element-ui提供的模板可以进行快速开发
实现功能
1、带添加分页查询
2、动态显示科目
3、删除
添加页面,可以实现动态添加
也可以对刚刚添加的进行修改操作