第十章、项目开发
实现一个登录注册,增删改查功能的系统
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: 8989
servlet:
context-path: /ems-jsp
jsp:
init-parameters:
development: true # 开启jsp模板开发模式
# 配置jsp展示
spring:
mvc:
view:
prefix: /
suffix: .jsp
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ems?characterEncoding=UTF-8
username: root
password: 123456
# 配置mybatis
mybatis:
mapper-locations: classpath:com.baizhi/mapper/*.xml
type-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;// 定义图片的width private int height = 40;// 定义图片的height private int codeCount = 4;// 定义图片上显示验证码的个数 private int lineCount = 20;// 定义图片上显示干扰线的条数 private String code = null;// 定义用于保存验证码的字符串 private BufferedImage buffImg = null;// 定义图片Buffer private 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; // 图像buffer buffImg = 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 { @Autowired private UserDao userDao; @Override public 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; @Autowired public EmployeeServiceImpl(EmployeeDao employeeDao) { this.employeeDao = employeeDao; } @Override public List<Employee> list() { return employeeDao.list(); } }
-
EmployeeController
@Controller @RequestMapping("employee") public class EmployeeController { private EmployeeService employeeService; @Autowired public 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>