一、视频展示
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)分页插件的依赖包
com.github.pagehelper
pagehelper
4.1.6
(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 studentList = new ArrayList<>();
//如果班级号为空,则表明是查询所有学生信息的请求,反之则是根据班级号查询该班所有学生信息
if(classNo != null){
studentList = studentService.selectByCondition("", "", classNo);
}else{
studentList = studentService.selectAll();
}
//根据学号得到每个学生的选课数
for (Student student : studentList){
List 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)页面显示
头像
学号
姓名
性别
班级
手机号
选课数量
操作
[[${student.classCount}]]
2、用户头像的上传与存储
需求:学生的个人信息中包含了头像,上传的头像存储在本地目录。同样,在页面也需显示出来。
(1)页面上传头像
(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)页面逻辑
//获取验证码
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)页面
(2)后台处理请求
@RequestMapping("/exportTeacherData")
public void exportTeacherData(HttpServletResponse response) throws UnsupportedEncodingException {
//从数据库中查询到数据
List 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
org.webjars
bootstrap
3.3.5
org.webjars
jquery
3.6.0
org.webjars.bower
echarts
4.7.0
页面中使用:
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参数
教师页面:点击按钮,跳转至添加教师页面。
添加
顶部菜单栏的退出功能:
7、session的使用
需求:用户登陆成功之后,在顶部菜单栏显示欢迎XXX登录。
登录逻辑中的处理:
session.setAttribute("user", user);
页面中使用session:
欢迎[[${session.user.name}]]登录
8、echarts与后台的交互
需求:对信息以图表化形式显示在页面上。
(1)页面
课程分析
(2)后台处理
/**
* 课程等级
* @return
*/
@RequestMapping("/curriculumGrade")
public Map curriculumGrade(){
List 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 map = new HashMap<>();
map.put("names", names);
map.put("grades", grades);
return map;
}
/**
* 课程课时
* @return
*/
@RequestMapping("/curriculumTime")
public Map curriculumTime(){
List curriculumList = curriculumService.selectAll();
Map 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 entry : myMap.entrySet()) {
hours[i] = entry.getKey();
counts[i] = entry.getValue();
i++;
}
Map map = new HashMap<>();
map.put("hours", hours);
map.put("counts", counts);
return map;
}
9、省市区三级联动
需求:添加学生的时候,选择学生的家庭住址
页面实现:
10、日期控件
需求:学生生日的选择通过日历控件。
页面中的定义:
$("#date").datetime({
type: "date",
value: [2019, 9, 31],
success: function(res) {
console.log(res)
}
})
注:在省市区三级联动和日历控件中用了自定义的css以及一些js文件,可在源码中查看,这里不再罗列。
11、thymeleaf提取公共页面
需求:每个页面都有顶部菜单栏,因此提取作为公共部分,然后在所需引入即可。
topbar.html:
Title
footer.html:
$Title$
这两个文件都在templates的commons文件下。
引入公共部分:
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
欢迎大家指点!!!