1、基本技术

   通过之前的微服务入门,这次不再mybatis上开发,改为基于spring jpa开发一个简单的学生成绩管理系统,基本SpringBoot + SpringCloud + JPA + VUE框架开发

2、需求说明

使用微服务架构完成《学生成绩管理系统》,它包含学生表和成绩表(科目是动态保存和动态展示)。

具体思路是:

  1. 使用Eureka搭建注册中心,并实现高可用。
  2. 开发学生成绩管理业务服务,并向注册中心注册。
  3. 使用ZUUL或Spring Cloud Gateway开发网关服务。
  4. 使用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: '@profiles.active@'
  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、删除

添加页面,可以实现动态添加

 

动态显示

 

也可以对刚刚添加的进行修改操作

 

 

修改成功

 

注:此次前端采用分页带条件模糊查询,因为是动态显示科目

所以每次调用分页方法的时候,都需要初始化一下存放科目的数组

例如,用postman测得后台返回的数据,以第一页为例

{
    "success": true,
    "code": 20000,
    "message": "成功",
    "data": {
        "map": {
            "count": 7,
            "courseNames": [
                "生物",
                "恋爱学",
                "佛系教学",
                "数学",
                "化学",
                "语文",
                "英语",
                "五号恋爱学"
            ],
            "list": [
                {
                    "id": 1,
                    "sname": "张无忌",
                    "sno": "001",
                    "score": 627,
                    "scores": {
                        "恋爱学": 100,
                        "佛系教学": 120,
                        "数学": 150,
                        "语文": 124,
                        "英语": 133
                    }
                },
                {
                    "id": 2,
                    "sname": "李亦非",
                    "sno": "002",
                    "score": 227,
                    "scores": {
                        "化学": 77,
                        "数学": 150
                    }
                },
                {
                    "id": 3,
                    "sname": "小古",
                    "sno": "003",
                    "score": 270,
                    "scores": {
                        "生物": 80,
                        "化学": 90,
                        "五号恋爱学": 100
                    }
                }
            ]
        }
    }
}

 data定义数据

 

调用列表方法

因为此次是动态显示,所以每次分页调用,都需要初始化一下数组对象

分页代码

<!-- 分页 -->
      <el-pagination
        :current-page="page"
        :page-size="limit"
        :total="total"
        style="padding: 30px 0; text-align: center;"
        layout="total, prev, pager, next, jumper"
        @current-change="init"
        @size-change="findSize"
      />

其中,@current-change="init"就是每次改变,调用的方法

init是自定义的方法

 

Logo

前往低代码交流专区

更多推荐