学习视频:【编程不良人】2021年SpringBoot最新最全教程
第十章、项目开发
实现一个登录注册,增删改查功能的系统
10.1 项目开发流程
-
需求分析
分析用户主要需求 提取项目核心功能,根据核心功能构建页面原型
-
库表设计:
- 分析系统有哪些表
- 分析表之间关联关系
- 确定字段
-
详细设计(流程图、伪代码):
验证库表准确性
-
功能实现(编码)
环境搭建,具体功能实现
-
功能测试,部署,上线,运维,维护
全栈式开发:前端+后端+运维
10.2 需求分析
- 系统有哪些模块?
- 每个模块功能有哪些?
- 用户模块:登录、注册、验证码生成
- 员工模块:查询、删除、更新、添加
10.3 库表设计
用户表:user
员工表:employee
表与表关系:user,employee 独立两张表
创建库表
create database ems;
use ems;
create TABLE user(
id int auto_increment ,username VARCHAR(40) COMMENT '用户名' ,
realname VARCHAR(40) COMMENT '真实姓名' ,
`password` VARCHAR(50) COMMENT '密码',gender TINYINT(3) COMMENT '性别',PRIMARY KEY (`id`)
);create TABLE employee(
id int auto_increment,
name VARCHAR(40) COMMENT '姓名',
birthday datetime COMMENT '生日',
salary DOUBLE COMMENT '薪资',
gender TINYINT(3) COMMENT '性别',
PRIMARY KEY(id)
)
10.5 编码环节
技术选型:SpringBoot + MyBatis + JSP + MySQL
环境搭建:Spring Boot + JSP + MyBatis
创建名为ems-jsp的项目,并引入web支持依赖,创建完成
环境搭建
pom.xml依赖导入
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--jsp解析依赖--><dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-jasper</artifactId></dependency><!--引入和MyBatis整合相关的依赖--><!--druid--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.19</version></dependency><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--mybatis-spring-boot-stater--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.0</version></dependency><!--开启热部署--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId></dependency>
</dependencies>
application.yml:
# 应用服务 WEB 访问端口
server:port: 8989servlet:context-path: /ems-jspjsp:init-parameters:development: true # 开启jsp模板开发模式# 配置jsp展示
spring:mvc:view:prefix: /suffix: .jspdatasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/ems?characterEncoding=UTF-8username: rootpassword: 123456# 配置mybatis
mybatis:mapper-locations: classpath:com.baizhi/mapper/*.xmltype-aliases-package: com.baizhi.entity# 配置日志
logging:level:com.baizhi: debug
添加dao包扫描
@SpringBootApplication
@MapperScan("com.baizhi.dao")
public class EmsJspApplication {public static void main(String[] args) {SpringApplication.run(EmsJspApplication.class, args);}}
10.6 验证码实现
-
业务逻辑
- 生成随机字符(依靠工具类VerifyCode)
- 放入session(与前端传过来的验证码进行比对),放进map(传给前端)
- 生成图片响应
-
register.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head><meta charset="UTF-8"><title>Registration Page</title><link rel="stylesheet" type="text/css" href="css/register.css"> </head> <body><h2>用户注册</h2> <div class="container"><h3 style="color: red">${param.msg}</h3><form action="${pageContext.request.contextPath}/user/register" method="post">用户名: <input type="text" name="username"><br>真实姓名: <input type="text" name="realname" ><br>密码: <input type="password" name="password" ><br> <%-- 确认密码: <input type="password" name="confirmPassword"><br>--%>性别:<select name="gender" ><option value="1">男</option><option value="0">女</option></select><br><!-- 验证码展示 --><label for="verifyCode">验证码:</label><br><img id="verifyCode" src="" alt="Captcha Image"><a href="javascript:" id="refresh">换一张</a><input type="text" name="code"><br><input type="submit" value="注册"></form> </div><script src="js/jquery.js"></script> <script>// jQuery代码$(document).ready(function() {$('#registerForm').submit(function() {return validateRegisterForm();});});// 表单验证function validateRegisterForm() {var username = $('#username').val(),password = $('#password').val(),// confirmPassword = $('#confirmPassword').val(),realname = $('#realname').val(),code = $('#code').val();console.log(backCode,code)if (username === "" || password === "" || realname === "" || code === "") {alert("请填写所有字段和验证码");return false;}if (backCode.toLowerCase() === code.toLowerCase()) {alert("验证码填写不正确")refreshVerifyCode();// 刷新验证码return false;}// if (password !== confirmPassword) {// alert("密码不匹配");// return false;// }alert("注册成功,请登录")return true;}var backCode = "";// 验证码刷新function refreshVerifyCode() {$.ajax({url: '${pageContext.request.contextPath}/user/verifyCode',method: 'GET',dataType: 'json',success: function(data) {console.log(data)backCode = data.code;$('#verifyCode').attr('src', data.image);},error: function(error) {console.error('error:', error);}});}// 初始化页面加载时显示验证码refreshVerifyCode();// 点击“换一张”按钮时刷新验证码$('#refresh').click(function() {refreshVerifyCode();}); </script></body> </html>
-
css
body {font-family: 'Arial', sans-serif;margin: auto;justify-content: center;align-items: center;width: 500px;height: 800px;/*border: 1px solid red;*/ }.container {padding: 30px;background-color: #ffffff;border-radius: 8px;box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);/*border: 1px solid red;*/ }h2 {text-align: center; }input[type="text"], input[type="password"], input[type="submit"], select {width: calc(100% - 20px);margin-bottom: 15px;padding: 10px;border: 1px solid #dddddd;border-radius: 5px;transition: border-color 0.3s ease-in-out; }input[type="text"]:focus, input[type="password"]:focus, select:focus {outline: none;border-color: #66afe9; }input[type="submit"] {background-color: #4CAF50;color: white;border: none;cursor: pointer;transition: background-color 0.3s ease-in-out; }input[type="submit"]:hover {background-color: seagreen; }
-
验证码 实现类
package com.baizhi.utils; import javax.imageio.ImageIO; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Random;public class VerifyCode {private int width = 100;// 定义图片的widthprivate int height = 40;// 定义图片的heightprivate int codeCount = 4;// 定义图片上显示验证码的个数private int lineCount = 20;// 定义图片上显示干扰线的条数private String code = null;// 定义用于保存验证码的字符串private BufferedImage buffImg = null;// 定义图片Bufferprivate char[] codeSequence = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R','S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9' };public VerifyCode() {this.createCode();}/*** @param width* 图片宽* @param height* 图片高*/public VerifyCode(int width, int height) {this.width = width;this.height = height;this.createCode();}/*** @param width* 图片宽* @param height* 图片高* @param codeCount* 字符个数* @param lineCount* 干扰线条数*/public VerifyCode(int width, int height, int codeCount, int lineCount) {this.width = width;this.height = height;this.codeCount = codeCount;this.lineCount = lineCount;this.createCode();}public void createCode() {int x = 0, fontHeight = 0, codeY = 0;int red = 0, green = 0, blue = 0;x = width / (codeCount + 2);// 每个字符的宽度fontHeight = height - 2;// 字体的高度codeY = height - 4;// 图像bufferbuffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);Graphics g = buffImg.createGraphics();// 创建一个随机数生成器类Random random = new Random();// 将图像填充为白色g.setColor(Color.WHITE);g.fillRect(0, 0, width, height);// 创建字体,字体的大小应该根据图片的高度来定。Font font = new Font("Fixedsys", Font.PLAIN, fontHeight);// 设置字体。g.setFont(font);for (int i = 0; i < lineCount; i++) {// 设置随机开始和结束坐标int xs = random.nextInt(width);// x坐标开始int ys = random.nextInt(height);// y坐标开始int xe = xs + random.nextInt(width / 8);// x坐标结束int ye = ys + random.nextInt(height / 8);// y坐标结束// 生成随机颜色red = random.nextInt(255);green = random.nextInt(255);blue = random.nextInt(255);g.setColor(new Color(red, green, blue));g.drawLine(xs, ys, xe, ye);}// randomCode记录随机产生的验证码StringBuffer randomCode = new StringBuffer();// 随机产生codeCount个字符的验证码。for (int i = 0; i < codeCount; i++) {// 得到随机产生的验证码数字。String strRand = String.valueOf(codeSequence[random.nextInt(codeSequence.length)]);// 用随机产生的颜色将验证码绘制到图像中。red = random.nextInt(255);green = random.nextInt(255);blue = random.nextInt(255);g.setColor(new Color(red, green, blue));g.drawString(strRand, (i + 1) * x, codeY);// 将产生的四个随机数组合在一起。randomCode.append(strRand);}// 将四位数字的验证码保存到Session中。code = randomCode.toString();}public void write(String path) throws IOException {OutputStream sos = new FileOutputStream(path);this.write(sos);}public void write(OutputStream sos) throws IOException {ImageIO.write(buffImg, "png", sos);sos.close();}public BufferedImage getBuffImg() {return buffImg;}public String getCode() {return code;} }
-
验证码生成 请求
@Controller @RequestMapping("user") public class UserController {/*** 生成验证码*/@ResponseBody@RequestMapping("verifyCode")public Map<String, String> verifyCode(HttpServletRequest request) throws IOException {Map<String,String> map = new HashMap<>();// 1.使用工具类生成验证码VerifyCode vc = new VerifyCode(120, 40, 4, 100);String code = vc.getCode();map.put("code", code);// 2. 获取验证码的BufferedImage对象BufferedImage captchaImage = vc.getBuffImg();//4.将图片转为base64 [放入src,可以直接显示图片]ByteArrayOutputStream outputStream = new ByteArrayOutputStream();ImageIO.write(captchaImage, "png", outputStream);byte[] imageBytes = outputStream.toByteArray();String image = "data:image/png;base64," + Base64Utils.encodeToString(imageBytes);map.put("image", image);return map;} }
10.7 注册实现
-
业务逻辑
-
Service
@Service @Transactional public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Overridepublic void register(User user) {User userDB = userDao.findByUserName(user.getUsername());if (!ObjectUtils.isEmpty(userDB)) {throw new RuntimeException("用户名已存在");}// 注册之前给密码进行加密String passwordSecret = DigestUtils.md5DigestAsHex(user.getPassword().getBytes(StandardCharsets.UTF_8));user.setPassword(passwordSecret);userDao.save(user);} }
-
mapper语句
<select id="findByUserName" resultType="com.baizhi.entity.User">select id,username,realname,password,gender from `user`where username = #{username} </select>
-
api
@Autowired private UserService userService;@RequestMapping("register") public String register(User user, String code, HttpSession session) {log.debug("接受的验证码:{}", code);log.debug("User:{}", user);// 比较验证try {String sessionCode = session.getAttribute("code").toString();if (!sessionCode.equalsIgnoreCase(code)) {throw new RuntimeException("验证码输入错误!!!");}userService.register(user);} catch (RuntimeException e) {e.printStackTrace();return "redirect:/register.jsp?msg=" + UriEncoder.encode(e.getMessage());}return "redirect:/login.jsp"; }
10.8用户登录
-
login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head><meta charset="UTF-8"><title>Login Page</title><link rel="stylesheet" href="css/register.css"> </head> <body> <h2>用户登录</h2> <h3 style="color: red">${param.msg}</h3> <form action="${pageContext.request.contextPath}/employee/list" method="post"><label for="username">用户名:</label><br><input type="text" id="username" name="username"><br><label for="password">密码:</label><br><input type="password" id="password" name="password"><br><input type="submit" value="登录"><input type="button" onclick="window.location.href='register.jsp'" value="注册"> </form> </body> </html>
-
ServiceImpl
@Override public User login(String username, String password) {//1. 根据用户名查询数据库是否存在User user = userDao.findByUserName(username);//2.判断对象是否存在if (ObjectUtils.isEmpty(user)) {throw new RuntimeException("用户名输入错误!");}//3.判断密码正确性String digestPassword = DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8));if (!user.getPassword().equals(digestPassword)) {throw new RuntimeException("密码错误!");}return user; }
-
UserController
@RequestMapping("login") public String login(String username, String password,HttpSession session) throws UnsupportedEncodingException {log.debug("接受到的用户名:{},接收到的密码:{}", username, password);try {// 1.执行登录业务逻辑User user = userService.login(username, password);// 2.登录成功,保存用户信息session.setAttribute("user", user);} catch (Exception e) {e.printStackTrace();return "redirect:/login.jsp?msg=" + UriEncoder.encode(e.getMessage());}return "redirect:/emplist.jsp"; }
10.9 员工列表展示
- 在数据库查询所有员工信息
- 在页面中进行展示
-
emplist.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8" %> <!DOCTYPE html> <html> <head><title>用户列表</title><link rel="stylesheet" href="${pageContext.request.contextPath}/css/emplist.css"> </head> <body><div id="container"><h2>用户列表</h2><table><tr><th>ID</th><th>姓名</th><th>性别</th><th>薪水</th><th>生日</th><th>操作</th></tr><c:forEach var="employee" items="${requestScope.employees}"><tr><td>${employee.id}</td><td>${employee.name}</td><td>${employee.gender?'男':'女'}</td><td>${employee.salary}</td><td><fmt:formatDate value="${employee.birthday}" pattern="yyyy-MM-dd"/></td><td><a href="javascript:;">删除</a><a href="javascript:;">修改</a></td></tr></c:forEach></table><a href="javascript:;">添加员工信息</a></div></body> </html>
-
emplist.css
body {font-family: Arial, sans-serif;margin: 0;padding: 0; }#container {max-width: 800px;margin: 0 auto;padding: 20px; }h2 {text-align: center; } table {width: 100%;border-collapse: collapse;margin-bottom: 20px; }th, td {padding: 10px;text-align: left;border: 1px solid #ccc; }thead {background-color: #f2f2f2; }div > button {margin: 5px;padding: 5px 10px;border: none;background-color: #007bff;color: #fff;cursor: pointer; }div > button:hover {background-color: #0056b3; }select, button {padding: 5px; }div > span {margin: 0 10px;font-weight: bold; }label {font-weight: bold; }
-
Service
@Service @Transactional public class EmployeeServiceImpl implements EmployeeService {private final EmployeeDao employeeDao;@Autowiredpublic EmployeeServiceImpl(EmployeeDao employeeDao) {this.employeeDao = employeeDao;}@Overridepublic List<Employee> list() {return employeeDao.list();} }
-
EmployeeController
@Controller @RequestMapping("employee") public class EmployeeController {private EmployeeService employeeService;@Autowiredpublic EmployeeController(EmployeeService employeeService) {this.employeeService = employeeService;}/*** 员工列表** @return*/@RequestMapping("list")public String listEmployee(HttpServletRequest request, Model model) {//1. 获取员工列表List<Employee> employees = employeeService.list(); // request.setAttribute("employees", employees);model.addAttribute("employees", employees);return "emplist";} }
10.10 添加员工信息
- 在EmplyeeController开发一个添加方法
- 接收员工信息
- 将员工信息保存到数据库
- 跳转到员工列表展示数据
-
addEmp.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8" %> <!DOCTYPE html> <html> <head><title>添加员工</title><link rel="stylesheet" href="css/addEmp.css"> </head> <body><h2>添加员工</h2><div id="container"><form action="${pageContext.request.contextPath}/employee/add" method="post"><table><tr><td>姓名:</td><td><input type="text" id="name" name="name"></td></tr><tr><td>薪水:</td><td><input type="text" id="salary" name="salary"></td></tr><tr><td>生日:</td><td><input type="text" id="birthday" name="birthday"></td></tr><tr><td>性别:</td><td><select name="gender" ><option value="1">男</option><option value="0">女</option></select></td></tr><tr><td colspan="2"><input type="submit" value="添加"></td></tr></table></form></div></body> </html>
-
addEmp.css
body {font-family: Arial, sans-serif;margin: 0;padding: 0; } #container {max-width: 800px;margin: 0 auto;padding: 20px; }h2 {text-align: center; } table {width: 100%;border-collapse: collapse;margin-bottom: 20px;color: #212529; } td, th {vertical-align: top;padding: 10px;text-align: left;border: 1px solid #ccc; } input[type="text"], input[type="date"], select {width: 60%;padding: .375rem .75rem;border: 1px solid #ced4da;border-radius: .25rem; } input[type="submit"] {color: #fff;background-color: #007bff;border-color: #007bff;padding: .375rem .75rem;border-radius: .25rem; }
-
EmployeeDaomapper.xml
<insert id="add" parameterType="Employee" useGeneratedKeys="true" keyProperty="id">INSERT INTO `ems`.`employee`(`id`, `name`, `birthday`, `salary`, `gender`) VALUES (#{id},#{name},#{birthday},#{salary},#{gender}); </insert>
-
EmployeeServiceImpl
public void addEmployee(Employee employee) {employeeDao.add(employee); }
-
Controller
@RequestMapping("add") public String addEmployee(Employee employee) {log.debug("员工信息:{}", employee);//1. 保存员工信息employeeService.addEmployee(employee);return "redirect:/employee/list"; }
10.11 更新员工信息
-
显示员工信息
- 根据id查询员工信息
- 将对象放进作用域
- 跳转到更新页面
-
UpdateEmp.jsp
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8" %> <!DOCTYPE html> <html> <head><title>修改员工信息</title><link rel="stylesheet" href="${pageContext.request.contextPath}/css/updateEmp.css"> </head> <body><div id="container"><h2>修改员工信息</h2><form action="${pageContext.request.contextPath}/employee/update" method="post"><input type="text" id="id" name="id" value="${employee.id}" style="display: none"><label for="name">姓名:</label><br> <input type="text" id="name" name="name" value="${employee.name}"><br><label for="gender">性别:</label><br><select id="gender" name="gender"><option value="1" ${employee.gender?'selected':''}>男</option><option value="0" ${!employee.gender?'selected':''}>女</option></select><br><label for="salary">薪水:</label><br><input type="text" id="salary" name="salary" value="${employee.salary}"><br><label for="birthday">生日:</label><br><input type="text" id="birthday" name="birthday" value="<fmt:formatDate value='${employee.birthday}' pattern='yyyy/MM/dd'/>"/><br><input type="submit" value="提交"></form></div> </body> </html>
-
UpdateEmp.css
body {font-family: Arial, sans-serif; }#container {width: 300px;margin: 0 auto;padding: 20px;border: 1px solid #ccc;border-radius: 5px;background-color: #f8f8f8; }h2 {text-align: center;color: #333; }label {font-weight: bold;color: #555; }input[type="text"], input[type="date"],select {width: 70%;padding: 10px;margin: 5px 0 15px;border: 1px solid #ccc;border-radius: 3px; }input[type="submit"] {width: 100%;padding: 10px;color: white;background-color: #007BFF;border: none;border-radius: 3px;cursor: pointer; }input[type="submit"]:hover {background-color: #0056b3; }
-
Controller
@RequestMapping("detail") public String detailEmployee(Integer id, Model model) {log.debug("接收的id:{}",id);Employee employee = employeeService.idByEmployee(id);model.addAttribute("employee", employee);return "updateEmp"; }
-
更改员工信息
- 获取更新后的员工信息
- 更新数据库
-
Controller
@RequestMapping("update") public String updateEmployee(Employee employee) {log.debug("修改的员工信息:{}", employee);employeeService.updateEmployee(employee);return "redirect:/employee/list"; }
10.12 删除员工
- 传递id给后端进行数据库删除
- 返回employee/list重新查数据库刷新
-
emplist.jsp
<a href="javascript:;" onclick="deleteEmployee()">删除</a><script>function deleteEmployee(){if (window.confirm('确定删除这条记录吗')) {location.href= '${pageContext.request.contextPath}/employee/delete?id=${employee.id}'}}</script>
-
Controller
@RequestMapping("delete") public String deleteEmployee(Integer id) {log.debug("接收的id:{}", id);employeeService.deleteEmployee(id);return "redirect:/employee/list"; }
-
mapper
<delete id="deleteEmployee">delete from `employee`where id =#{id} </delete>