一、视频展示
https://www.bilibili.com/video/BV1pU4y1J7wc
二、项目介绍
1、技术与工具
开发工具 | IntelliJ IDEA |
数据库 | MySQL 5.7 |
前端技术 | Jquery 、 Bootstrap 、echarts、thymeleaf |
后台技术 | SpringBoot、MyBatis |
2、项目目录介绍
具体介绍如下:
(1)程序代码的存放目录——src下的java
component | 登录拦截器 |
config | 静态资源拦截 |
controller | 负责在页面和程序之间传输数据的,做页面的跳转 |
dao | 负责对数据向数据库增删改查的操作 |
entity | 实体类。一般与数据库中的属性值基本保持一致 |
service | 负责业务模块的应用逻辑设计 |
utils | 工具包。包括验证码、分页插件等 |
(2)资源文件的存放目录——resources
Mapper | 存放Mabtis的映射文件 |
static | 存放静态资源文件,包括图片、css文件、js文件 |
tempaltes | 存放前端所有的页面。 |
(3)测试类的目录——test
(4)maven的配置文件——pom.xml
三、关键技术
1、分页插件
需求:系统中的数据可能会有多条。当数据量较少的时候可以在一页上展示,但是如果数据量很大,则应该使用分页的方式来显示数据。
(1)分页插件的依赖包
<!--分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.1.6</version>
</dependency>
(2)分页插件的工具类
Page类
public class Page {
private int start; //开始页数
private int count; //每页显示个数
private int total; //总个数
private String param; //参数
private static final int defaultCount = 5; //默认每页显示5条
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Page (){
count = defaultCount;
}
public Page(int start, int count) {
this();
this.start = start;
this.count = count;
}
public boolean isHasPreviouse(){
if(start==0)
return false;
return true;
}
public boolean isHasNext(){
if(start==getLast())
return false;
return true;
}
public int getTotalPage(){
int totalPage;
// 假设总数是50,是能够被5整除的,那么就有10页
if (0 == total % count)
totalPage = total /count;
// 假设总数是51,不能够被5整除的,那么就有11页
else
totalPage = total / count + 1;
if(0==totalPage)
totalPage = 1;
return totalPage;
}
public int getLast(){
int last;
// 假设总数是50,是能够被5整除的,那么最后一页的开始就是45
if (0 == total % count)
last = total - count;
// 假设总数是51,不能够被5整除的,那么最后一页的开始就是50
else
last = total - total % count;
last = last<0?0:last;
return last;
}
@Override
public String toString() {
return "Page [start=" + start + ", count=" + count + ", total=" + total + ", getStart()=" + getStart()
+ ", getCount()=" + getCount() + ", isHasPreviouse()=" + isHasPreviouse() + ", isHasNext()="
+ isHasNext() + ", getTotalPage()=" + getTotalPage() + ", getLast()=" + getLast() + "]";
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public String getParam() {
return param;
}
public void setParam(String param) {
this.param = param;
}
}
PageHelperConfig类
@Configuration
public class PageHelperConfig {
@Bean
public PageHelper pageHelper() {
PageHelper pageHelper = new PageHelper();
Properties p = new Properties();
p.setProperty("offsetAsPageNum", "true");
p.setProperty("rowBoundsWithCount", "true");
p.setProperty("reasonable", "true");
pageHelper.setProperties(p);
return pageHelper;
}
}
(3)分页插件在业务逻辑中的使用方式
以查询所有学生信息为例演示分页插件的使用:
@RequestMapping("/studentList")
public String studentList(Model model, Page page, String classNo){
//分页插件
PageHelper.offsetPage(page.getStart(),page.getCount());
//结果集
List<Student> studentList = new ArrayList<>();
//如果班级号为空,则表明是查询所有学生信息的请求,反之则是根据班级号查询该班所有学生信息
if(classNo != null){
studentList = studentService.selectByCondition("", "", classNo);
}else{
studentList = studentService.selectAll();
}
//根据学号得到每个学生的选课数
for (Student student : studentList){
List<String> stringList = studentService.selectClassCount(student.getStuNo());
student.setClassCount(stringList.size());
}
//得到记录总数
int total = (int) new PageInfo<>(studentList).getTotal();
//设置页的总数
page.setTotal(total);
//向页面返回数据
model.addAttribute("page",page);
model.addAttribute("studentList", studentList);
return "student/student";
}
(4)页面显示
<table class="table table-striped table-bordered text-center" style="margin-top:80px" th:if="${studentList != null}">
<thead>
<tr>
<th class="text-center">头像</th>
<th class="text-center">学号</th>
<th class="text-center">姓名</th>
<th class="text-center">性别</th>
<th class="text-center">班级</th>
<th class="text-center">手机号</th>
<th class="text-center">选课数量</th>
<th class="text-center">操作</th>
</tr>
</thead>
<tbody>
<tr th:each="student : ${studentList}">
<td><img th:src="@{${student.icon}}" alt="点击查看头像" class="img-circle" style="width: 30px; height: 30px;"></td>
<td th:text="${student.stuNo}"></td>
<td th:text="${student.name}"></td>
<td th:text="${student.gender==0} ? '女' : '男'"></td>
<td th:text="${student.classNo}"></td>
<td th:text="${student.phone}"></td>
<td><a th:href="@{/curriculum/curriculumList(stuNo=${student.stuNo})}">[[${student.classCount}]]</span></a></td>
<td>
<a th:href="@{/student/studentDetail(id=${student.id})}"><span class="glyphicon glyphicon-pencil"
style="margin-right:10px"></span></a>
<a th:href="@{/student/deleteStudent(id=${student.id})}"><span class="glyphicon glyphicon-trash"></span></a>
</td>
</tr>
</tbody>
</table>
<div class="row text-center" th:if="${studentList != null}">
<div class="col-sm-6 col-sm-offset-2">
<ul class="pagination">
<li><a th:href="@{/student/studentList(start = 0)}" aria-label="Previous"><span aria-hidden="true">上一页</span></a></li>
<li th:each="number:${#numbers.sequence(1,page.totalPage)}"><a th:href="@{/student/studentList(start=${(number-1)*page.count})}" th:text="${number}"></a></li>
<li><a th:href="@{/student/studentList(start=${page.last})}" aria-label="Next"><span aria-hidden="true">下一页</span></a></li>
</ul>
</div>
</div>
2、用户头像的上传与存储
需求:学生的个人信息中包含了头像,上传的头像存储在本地目录。同样,在页面也需显示出来。
(1)页面上传头像
<form class="form-horizontal " th:action="@{/student/addStudent}" enctype="multipart/form-data" method="post">
<div class="form-group" style="margin-top: 10px;">
<label for="exampleInputFile" class="col-sm-3 control-label col-sm-offset-1">头像</label>
<div class="col-sm-8">
<div class="custom-file">
<input type="file" name="file" class="custom-file-input" id="exampleInputFile">
</div>
</div>
</div>
...
</form>
(2)存储头像
@RequestMapping("/addStudent")
public String addStudent(RedirectAttributes attributes, Student student, MultipartFile file, String province, String city, String district){
//学生头像
String filePath = "D:/Java/项目/MyDo/schoolmanagement/src/main/resources/static/userImg";
String fileName = file.getOriginalFilename();
File targetFile = new File(filePath, fileName);
targetFile.getParentFile().mkdirs();
try {
file.transferTo(targetFile);
} catch (IOException e) {
e.printStackTrace();
}
student.setIcon("/userImg/"+fileName);
//学生地址
String address = province + ":" + city + ":" + district;
student.setAddress(address);
//插入数据库
studentService.insertStudent(student);
attributes.addFlashAttribute("msg", "添加成功");
return "redirect:/student/studentList";
}
(3)更换头像
@RequestMapping("/modifyStudent")
public String modifyStudent(RedirectAttributes attributes, Student student, MultipartFile file, String province, String city, String district){
//学生头像。如果页面提交过来的不为空,则替换,如果为空,则仍然是源头像
if (!file.isEmpty()) {
/**
* 上传图片
*/
//图像存放路径
String filePath = "D:/Java/项目/MyDo/schoolmanagement/src/main/resources/static/userImg";
//图片名称
String fileName = file.getOriginalFilename();
File targetFile = new File(filePath, fileName);
//创建文件路径
targetFile.getParentFile().mkdirs();
targetFile.delete();
try {
file.transferTo(targetFile);
} catch (IOException e) {
e.printStackTrace();
}
student.setIcon("/userImg/"+fileName);
}else {
//设置源头像
Student searchStudent = studentService.selectById(student.getId());
student.setIcon(searchStudent.getIcon());
}
//学生地址
String address = province + ":" + city + ":" + district;
student.setAddress(address);
studentService.updateStudent(student);
attributes.addFlashAttribute("msg", "修改成功");
return "redirect:/student/studentList";
}
3、登录验证码
需求:在用户登陆系统时,需要输入验证码,并在后台完成验证。
(1)页面逻辑
<div class="input-group mb-3">
<img id="imgVerify" th:src="@{/getVerify}" alt="更换验证码" height="30" width="170"
onclick="getVerify(this);">
<input type="tel" class="form-control" placeholder="验证码" maxlength="4" style="float:right;width: 40%;left: 0;margin-top: 14px;"
name="verifyInput" id="verify" onfocus="modifyLabel()">
</div>
//获取验证码
function getVerify() {
$("#imgVerify").attr("src", '/getVerify?' + Math.random());//jquery方式
}
function modifyLabel(){
$(".tip-area").css("display", "none");
}
(2)工具类
RandomValidateCodeUtil类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
public class RandomValidateCodeUtil {
public static final String RANDOMCODEKEY= "RANDOMVALIDATECODEKEY";//放到session中的key
private String randString = "0123456789";//随机产生只有数字的字符串 private String
//private String randString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";//随机产生只有字母的字符串
//private String randString = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";//随机产生数字与字母组合的字符串
private int width = 95;// 图片宽
private int height = 25;// 图片高
private int lineSize = 40;// 干扰线数量
private int stringNum = 4;// 随机产生字符数量
private static final Logger logger = LoggerFactory.getLogger(RandomValidateCodeUtil.class);
private Random random = new Random();
/**
* 获得字体
*/
private Font getFont() {
return new Font("Fixedsys", Font.CENTER_BASELINE, 18);
}
/**
* 获得颜色
*/
private Color getRandColor(int fc, int bc) {
if (fc > 255)
fc = 255;
if (bc > 255)
bc = 255;
int r = fc + random.nextInt(bc - fc - 16);
int g = fc + random.nextInt(bc - fc - 14);
int b = fc + random.nextInt(bc - fc - 18);
return new Color(r, g, b);
}
/**
* 生成随机图片
*/
public void getRandcode(HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession();
// BufferedImage类是具有缓冲区的Image类,Image类是用于描述图像信息的类
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
Graphics g = image.getGraphics();// 产生Image对象的Graphics对象,改对象可以在图像上进行各种绘制操作
g.fillRect(0, 0, width, height);//图片大小
g.setFont(new Font("Times New Roman", Font.ROMAN_BASELINE, 18));//字体大小
g.setColor(getRandColor(110, 133));//字体颜色
// 绘制干扰线
for (int i = 0; i <= lineSize; i++) {
drowLine(g);
}
// 绘制随机字符
String randomString = "";
for (int i = 1; i <= stringNum; i++) {
randomString = drowString(g, randomString, i);
}
logger.info(randomString);
//将生成的随机字符串保存到session中
session.removeAttribute(RANDOMCODEKEY);
session.setAttribute(RANDOMCODEKEY, randomString);
g.dispose();
try {
// 将内存中的图片通过流动形式输出到客户端
ImageIO.write(image, "JPEG", response.getOutputStream());
} catch (Exception e) {
logger.error("将内存中的图片通过流动形式输出到客户端失败>>>> ", e);
}
}
/**
* 绘制字符串
*/
private String drowString(Graphics g, String randomString, int i) {
g.setFont(getFont());
g.setColor(new Color(random.nextInt(101), random.nextInt(111), random
.nextInt(121)));
String rand = String.valueOf(getRandomString(random.nextInt(randString
.length())));
randomString += rand;
g.translate(random.nextInt(3), random.nextInt(3));
g.drawString(rand, 13 * i, 16);
return randomString;
}
/**
* 绘制干扰线
*/
private void drowLine(Graphics g) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(13);
int yl = random.nextInt(15);
g.drawLine(x, y, x + xl, y + yl);
}
/**
* 获取随机的字符
*/
public String getRandomString(int num) {
return String.valueOf(randString.charAt(num));
}
}
Picverigyaction类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RestController
public class Picverifyaction {
private final static Logger logger = LoggerFactory.getLogger(Picverifyaction.class);
/**
* 生成验证码
*/
@RequestMapping(value = "/getVerify")
public void getVerify(HttpServletRequest request, HttpServletResponse response) {
try {
response.setContentType("image/jpeg");//设置相应类型,告诉浏览器输出的内容为图片
response.setHeader("Pragma", "No-cache");//设置响应头信息,告诉浏览器不要缓存此内容
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expire", 0);
RandomValidateCodeUtil randomValidateCode = new RandomValidateCodeUtil();
randomValidateCode.getRandcode(request, response);//输出验证码图片方法
} catch (Exception e) {
logger.error("获取验证码失败>>>> ", e);
}
}
}
(3)登录处理
@PostMapping("/login")
public ModelAndView login(User user, @RequestParam String verifyInput, HttpSession session, RedirectAttributes attributes){
//验证码
String random = (String) session.getAttribute("RANDOMVALIDATECODEKEY");
ModelAndView mv = new ModelAndView();
//验证码校验
if (random == null) {
System.out.println("验证码错误");
mv.addObject("msg", "验证码错误");
mv.setViewName("index");
return mv;
}
//判断验证码是否相等
if(random.equals(verifyInput)){
//根据用户名和密码查询用户
User loginUser = userService.selectByNameAndPwd(user);
if(loginUser != null){
//查询成功,判断身份是否相等
if(loginUser.getIdentity() != user.getIdentity()){
//不相等,则提示信息
mv.addObject("msg", "你不是"+(user.getIdentity() == 1 ? "管理员" : "用户"));
mv.setViewName("index");
}else{
//相等,登陆成功
// session.setAttribute("loginUsername", user.getName());
session.setAttribute("user", user);
mv.setViewName("main");
}
} else{
//查询失败,提示未注册
mv.addObject("msg", "当前用户未注册");
mv.setViewName("index");
}
}else{
//验证码错误。,返回登录页
System.out.println("验证码错误");
mv.addObject("msg", "验证码错误");
mv.setViewName("index");
}
// mv.setViewName("main");
return mv;
}
4、Hutool导出数据
需求:页面显示了信息列表,如果需要导出列表数据,需要点击导出按钮。
(1)页面
<div class="text-center" style="display:block;margin-bottom: 20px;">
<a class="btn btn-primary col-sm-1" style="margin-right:10px" th:href="@{/user/toPage(url='/teacher/addTeacher')}"><span class="glyphicon glyphicon-plus" style="margin-right: 10px;"></span>添加</a>
<a class="btn btn-warning col-sm-1" th:href="@{/teacher/exportTeacherData}"><span class="glyphicon glyphicon-upload" style="margin-right: 10px;"></span>导出</a>
<label class="pull-right" style="margin-right:20px" th:if="${teacherList != null}">共有数据[[${page.total}]]条</label>
</div>
(2)后台处理请求
@RequestMapping("/exportTeacherData")
public void exportTeacherData(HttpServletResponse response) throws UnsupportedEncodingException {
//从数据库中查询到数据
List<Teacher> teacherList = teacherService.selectAll();
for(Teacher teacher : teacherList){
teacher.setClassCount(curriculumService.selectByTeacherNo(teacher.getTeacherNo()).size());
}
// 通过工具类创建writer,默认创建xls格式
ExcelWriter writer = ExcelUtil.getWriter();
//自定义标题别名
writer.addHeaderAlias("teacherNo", "教工号");
writer.addHeaderAlias("name", "教师姓名");
writer.addHeaderAlias("age", "年龄");
writer.addHeaderAlias("gender", "性别(0 男 1 女)");
writer.addHeaderAlias("professor", "职称");
writer.addHeaderAlias("phone", "手机号");
writer.addHeaderAlias("email", "邮箱");
writer.addHeaderAlias("classCount", "带课数");
//只导出设置了别名的列
writer.setOnlyAlias(true);
// 合并单元格后的标题行,使用默认标题样式
writer.merge(7, "教师信息");
// 一次性写出内容,使用默认样式,强制输出标题
writer.write(teacherList, true);
//out为OutputStream,需要写出到的目标流
//response为HttpServletResponse对象
response.setContentType("application/vnd.ms-excel;charset=utf-8");
//dateString.xls是弹出下载对话框的文件名,用日期作为文件名称
Date currentTime = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = formatter.format(currentTime);
response.setHeader("Content-Disposition","attachment;filename="+dateString+".xls");
ServletOutputStream out= null;
try {
out = response.getOutputStream();
writer.flush(out, true);
}
catch (IOException e) {
e.printStackTrace();
}
finally {
// 关闭writer,释放内存
writer.close();
}
//此处记得关闭输出Servlet流
IoUtil.close(out);
}
5、webjars使用
(1)介绍
WebJars是将客户端(浏览器)资源(JavaScript,Css等)打成jar包文件,以对资源进行统一依赖管理。WebJars的jar包部署在Maven中央仓库上。
(2)目的
统一管理静态资源
(3)实现方式(以Bootstrap、jquery、echart为例)
pom.xml
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.5</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.6.0</version>
</dependency>
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>echarts</artifactId>
<version>4.7.0</version>
</dependency>
页面中使用:
<!-- Bootstrap core CSS -->
<link th:href="@{/webjars/bootstrap/3.3.5/css/bootstrap.css}" rel="stylesheet">
<script th:src="@{/webjars/jquery/3.6.0/jquery.min.js}"></script>
<script th:src="@{/webjars/bootstrap/3.3.5/js/bootstrap.js}"></script>
<script th:src="@{/webjars/echarts/4.7.0/dist/echarts.js}"></script>
6、公共请求的页面跳转
需求:在后台逻辑中有的请求只是为了跳转页面,通过设置公共请求对这些跳转请求进行统一处理。如下:
/**
* 跳转至学生分析页面
* @return
*/
@RequestMapping("/studentAnalysis")
public String studentScore(){
return "/student/studentAnalysis";
}
处理公共请求的映射方法:
/**
* 公共请求的处理
* @param request
* @return
*/
@RequestMapping(value = "/toPage",method = RequestMethod.GET)
public String toPage(HttpServletRequest request){
//获取请求参数
String url= request.getParameter("url");
return url;
}
使用方式:在请求页面时携带url参数
教师页面:点击按钮,跳转至添加教师页面。
<a class="btn btn-primary col-sm-1" style="margin-right:10px" th:href="@{/user/toPage(url='/teacher/addTeacher')}"><span class="glyphicon glyphicon-plus" style="margin-right: 10px;"></span>添加</a>
顶部菜单栏的退出功能:
<ul class="dropdown-menu text-center">
<li class="text-center" ><a href="#">修改密码</a></li>
<li role="separator" class="divider"></li>
<li class="text-center"><a th:href="@{/user/toPage(url='index')}">退出</a></li>
</ul>
7、session的使用
需求:用户登陆成功之后,在顶部菜单栏显示欢迎XXX登录。
登录逻辑中的处理:
session.setAttribute("user", user);
页面中使用session:
<li><a >欢迎[[${session.user.name}]]登录</a></li>
8、echarts与后台的交互
需求:对信息以图表化形式显示在页面上。
(1)页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<title>课程分析</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/webjars/bootstrap/3.3.5/css/bootstrap.css}" rel="stylesheet">
<script th:src="@{/webjars/jquery/3.6.0/jquery.min.js}"></script>
<script th:src="@{/webjars/bootstrap/3.3.5/js/bootstrap.js}"></script>
<script th:src="@{/webjars/echarts/4.7.0/dist/echarts.js}"></script>
</head>
<body>
<!-- 导航 -->
<div th:replace="commons/bar::topbar"></div>
<!-- 主题内容 -->
<div class="container-fluid">
<div class="page-main">
<ol class="breadcrumb">
<li>课程信息</li>
<li>课程分析</li>
</ol>
<div class="row">
<div class="">
<div id="leftGraph" style="height: 400px" class="col-sm-5 col-sm-offset-1"></div>
<div id="rightGraph" style="height: 400px" class="col-sm-5 col-sm-offset-1"></div>
</div>
</div>
</div>
</div>
</div>
<div th:replace="commons/footer::footer"></div>
</body>
`
<script type="text/javascript">
$(function () {
$.ajax({
type : "get", //请求方式
url : "http://localhost:8082/curriculumGrade", //后端controller层路径
contentType: 'application/json',
success:function(data){
var leftDom = document.getElementById("leftGraph");
var leftChart = echarts.init(leftDom);
var leftOption;
leftOption = {
title : {
show:true,//显示策略,默认值true,可选为:true(显示) | false(隐藏)
text: '课程等级',
},
xAxis: {
type: 'category',
data: data.names
},
yAxis: {
type: 'value'
},
series: [{
data: data.grades,
type: 'bar',
showBackground: true,
backgroundStyle: {
color: 'rgba(43, 114, 180, 0.2)'
}
}]
};
if (leftOption && typeof leftOption === 'object') {
leftChart.setOption(leftOption);
}
},
error:function(data){
console.log("error")
console.log(data)
}
})
$.ajax({
type : "get", //请求方式
url : "http://localhost:8082/curriculumTime", //后端controller层路径
contentType: 'application/json',
success:function(data){
console.log(data)
var arr = [];
for(var i=0; i < data.hours.length; i++){
arr.push({"value":data.counts[i], "name":data.hours[i]});
}
console.log(arr);
var rightDom = document.getElementById("rightGraph");
var rightChart = echarts.init(rightDom);
var rightOption;
rightOption = {
title: {
text: '课时数量',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left',
},
series: [
{
name: '访问来源',
type: 'pie',
radius: '50%',
data:arr,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
if (rightOption && typeof rightOption === 'object') {
rightChart.setOption(rightOption);
}
},
error:function(data){
console.log("error")
console.log(data)
}
})
});
</script>
</html>
(2)后台处理
/**
* 课程等级
* @return
*/
@RequestMapping("/curriculumGrade")
public Map<String, Object> curriculumGrade(){
List<Curriculum> curriculumList = curriculumService.selectAll();
String[] names = new String[curriculumList.size()];
int[] grades = new int[curriculumList.size()];
int i = 0;
for(Curriculum curriculum :curriculumList){
names[i] = curriculum.getClassName();
grades[i] = curriculum.getClassGrade();
i++;
}
Map<String, Object> map = new HashMap<>();
map.put("names", names);
map.put("grades", grades);
return map;
}
/**
* 课程课时
* @return
*/
@RequestMapping("/curriculumTime")
public Map<String, Object> curriculumTime(){
List<Curriculum> curriculumList = curriculumService.selectAll();
Map<Integer, Integer> myMap = new HashMap<>();
for(Curriculum curriculum : curriculumList){
if(myMap.containsKey(curriculum.getClassHour())){
myMap.put(curriculum.getClassHour(), myMap.get(curriculum.getClassHour())+1);
}else{
myMap.put(curriculum.getClassHour(), 1);
}
}
System.out.println(myMap);
int[] hours = new int[myMap.size()];
int[] counts = new int[myMap.size()];
int i = 0;
for (Map.Entry<Integer, Integer> entry : myMap.entrySet()) {
hours[i] = entry.getKey();
counts[i] = entry.getValue();
i++;
}
Map<String, Object> map = new HashMap<>();
map.put("hours", hours);
map.put("counts", counts);
return map;
}
9、省市区三级联动
需求:添加学生的时候,选择学生的家庭住址
页面实现:
<div class="form-group" style="margin-top: 10px;">
<label class="col-sm-3 control-label col-sm-offset-1">家庭住址</label>
<div class="col-sm-8 form-inline">
<div id="distpicker" >
<div class="form-group col-sm-offset-1 col-sm-3" style="margin-right: 10px;">
<label class="sr-only" for="province">Province</label>
<select class="form-control" id="province" name="province"></select>
</div>
<div class="form-group col-sm-offset-1 col-sm-3" >
<label class="sr-only" for="city">City</label>
<select class="form-control" id="city" name="city"></select>
</div>
<div class="form-group col-sm-offset-1 col-sm-3" style="margin-right: 10px;">
<label class="sr-only" for="district">District</label>
<select class="form-control" id="district" name="district"></select>
</div>
<div class="form-group">
<button class="btn btn-primary" id="reset" type="button">Reset</button>
</div>
</div>
</div>
</div>
<script src="/js/distpicker.data.js"></script>
<script src="/js/distpicker.js"></script>
<script>
var _distpicker = $('#distpicker');
_distpicker.distpicker({
province: '浙江省',
city: '杭州市',
district: '上城区'
});
$('#reset').click(function () {
_distpicker.distpicker('reset');
});
</script>
10、日期控件
需求:学生生日的选择通过日历控件。
页面中的定义:
<div class="form-group" style="margin-top: 10px;">
<label class="col-sm-3 control-label col-sm-offset-1">生日</label>
<div class="col-sm-8 username-area">
<input type="text" class="form-control" id="date" name="birth">
</div>
</div>
$("#date").datetime({
type: "date",
value: [2019, 9, 31],
success: function(res) {
console.log(res)
}
})
注:在省市区三级联动和日历控件中用了自定义的css以及一些js文件,可在源码中查看,这里不再罗列。
11、thymeleaf提取公共页面
需求:每个页面都有顶部菜单栏,因此提取作为公共部分,然后在所需引入即可。
topbar.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 导航 -->
<nav class="navbar navbar-default navbar-static-top navbar-inverse" th:fragment="topbar">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#slide-left">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<label class="navbar-brand">高校人员信息管理</label>
</div>
<ul class="nav navbar-nav nav-menu" style="margin-right: 25px;">
<li ><a th:href="@{/user/toPage/(url='/main')}">首页</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">学生培养
<span class="caret"></span></a>
<ul class="dropdown-menu text-center">
<li class="text-center"><a th:href="@{/student/studentList}">学生列表</a></li>
<li role="separator" class="divider"></li>
<li class="text-center"><a th:href="@{/student/studentAnalysis}">学生分析</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">教师风采
<span class="caret"></span></a>
<ul class="dropdown-menu text-center">
<li class="text-center"><a th:href="@{/teacher/teacherList}">教师列表</a></li>
<li role="separator" class="divider"></li>
<li class="text-center"><a th:href="@{/teacher/teacherAnalysis}">教师分析</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">课程信息
<span class="caret"></span></a>
<ul class="dropdown-menu text-center">
<li class="text-center"><a th:href="@{/curriculum/curriculumList}">课程列表</a></li>
<li role="separator" class="divider"></li>
<li class="text-center"><a th:href="@{/curriculum/curriculumAnalysis}">课程分析</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">班级信息
<span class="caret"></span></a>
<ul class="dropdown-menu text-center">
<li class="text-center"><a th:href="@{/class/classAllList}">班级列表</a></li>
<li role="separator" class="divider"></li>
<li class="text-center"><a th:href="@{/class/classAnalysis}">班级分析</a></li>
</ul>
</li>
<li><a href="">关于</a></li>
</ul>
<ul class="nav navbar-nav navbar-right pull-right" style="margin-right: 40px;">
<li><a >欢迎[[${session.user.name}]]登录</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">我的
<span class="caret"></span></a>
<ul class="dropdown-menu text-center">
<li class="text-center" ><a href="#">修改密码</a></li>
<li role="separator" class="divider"></li>
<li class="text-center"><a th:href="@{/user/toPage(url='index')}">退出</a></li>
</ul>
</li>
</ul>
</nav>
</body>
</html>
footer.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>$Title$</title>
</head>
<body>
<footer class="footer" style="background-color:#1C1C1C;color:white;height:80px;line-height:30px" th:fragment="footer">
<div class="container text-center">
<p>版权所有:YHT China 电话:029-12345678<br />地址:陕西省西安市 邮编:123456</p>
</div>
</footer>
</body>
</html>
这两个文件都在templates的commons文件下。
引入公共部分:
<div th:replace="commons/bar::topbar"></div>
<div th:replace="commons/footer::footer"></div>
12、MD5加密
需求:对用户的密码进行MD5加密,提高安全性。
(1)工具类MD5Utils
public class MD5Utils {
public static String stringToMD5(String plainText) {
byte[] secretBytes = null;
try {
secretBytes = MessageDigest.getInstance("md5").digest(
plainText.getBytes());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("没有这个md5算法!");
}
String md5code = new BigInteger(1, secretBytes).toString(16);
for (int i = 0; i < 32 - md5code.length(); i++) {
md5code = "0" + md5code;
}
return md5code;
}
}
(2)注册时MD5加密
user.setPassword(MD5Utils.stringToMD5(user.getPassword()));
13、RedirectAttributes
需求:在重定向之后需要携带参数跳转页面。
如下:在添加班级之后将提示信息传递到classList页面。
/**
* 添加班级信息
* @param classEntity
* @param attributes
* @return
*/
@RequestMapping("/addClass")
public String addCurriculum(ClassEntity classEntity, RedirectAttributes attributes){
//插入数据
classService.insertClass(classEntity);
attributes.addFlashAttribute("msg", "添加成功");
return "redirect:/class/classAllList";
}
四、总结
一定要仔细,特别是复制粘贴的时候。在这个项目中,其实出现的问题都是因为自己不仔细,所以才出现的。
五、代码地址
https://github.com/MyBestThought/schoolmanagement
欢迎大家指点!!!