技术训练营- - 任务 G :用户管理
用户注册、登录,注册时需要填写姓名、性别、手机号、身份证号、年龄、现住址、工作单位等个人信息;
任务内容
功能要求包括:
- 用户注册、登录,注册时需要填写姓名、性别、手机号、身份证号、年龄、现住址、工作单位等个人信息;
- 可以通过手机号注册,也可以通过验证码注册(验证码由单独的页面显示,注册前就需要生成并,然后访问该页面获得验证码并用于注册);
- 在首页可以查看全部用户列表(带分页),可以点击某位用户的名字查看其详情,可以冻结用户帐户,被冻结后的用户无法登录(系统给出提示或返回账户被冻结页面);
- 可编辑用户详情,上传用户头像并保存;
- 用户还包括积分、余额、卡券等附属资产信息,这些数据要通过单独的页面访问,并且也可以编辑后保存;
- admin 用户为管理员,该用户信息不可编辑、不可禁用,也不显示在用户列表中。
技术要求包括:
- 通过 Redis 保存用户注册验证码,失效时间为 15 分钟;
- 用户注册完毕后,需要登录才能进入首页;
- 登录时需要图片验证码,通过 Redis 保存,失效时间为 5 分钟,图片验证码示例:
工作日志
- 创建
User
类, 类中包含用户的 姓名、性别、手机号、身份证号、年龄、现住址、工作单位等个人信息的字段。 - 创建
UserDAO
类,类中包含各种对数据库的的方法,包括:初始化(测试用)、与数据库连接、添加用户、编辑用户、展示用户列表的方法。 - 创建
UserEditServlet
类、UserListPage.jsp
列表页面、UserEditPage.jsp
编辑页面以及相应的结果:Edit_Fail.jsp
、Edit_Success.jsp
。 - 遇到了神秘的 404 错误,花了很久时间来解决。
- 给
User
类、UserDAO
类中相应方法、添加了 积分、余额、卡券 三个字段。 - 创建了
UserDetailEditServlet
类、UserDetailList
详情展示页面、UserDetailEditPage
详情编辑页面以及相应的结果:EditDetail_Success.jsp
、EditDetail_Fail.jsp
,在UserDAO
类中添加详情展示的方法userDetailEdit
、详情编辑的方法userDetailEdit
。 - 在初始化语句中,添加了 admin 账户,该用户不会在展示页面出现。
- 在
User
类、UserDAO
类中的响应方法、添加了 密码 字段。 - 添加密码字段后,出现 sql 语句报错。
- 创建
UserRegServlet
类,UserReg.html
注册页面,以及相应的结果页面:Reg_Fail.jsp
、Reg_Success.jsp
,实现了注册功能。 - 发现用户列表页面不能点击用户名跳转至编辑界面,修复成功,实现了用户基础信息编辑的功能。
- 更正了编辑用户信息页面的一个显示错误。
- 修复了编辑用户信息页面的输入栏没有预设值的问题。
- 修复了编辑用户信息页面中身份证预设值为 null 的问题。
- 完成了
UserDAO
里的冻结方法。 - 完善了
UserList
中的冻结按钮。 - 给 SQL的密码字段添加了默认值。
- 修复了性别栏没有勾选选项的问题。
- 添加了一个
UserInitServlet
类,并且在主页添加了一个调用这个 Servlet 的按钮。效果为点击之后会初始化数据库,方便测试。 - 修改了
UserDAO
类中的注册、列表方法,(跟 uid 有关)。 - 解决了基本信息编辑页面的一个无法读取到 uid 的错误。
- 解决了 UserDAO 类中,编辑用户的方法的 sql 语法问题。
- 创建了
UserLoginServlet
类、UserLogin.jsp
,在UserDAO
类中添加了 login 方法。添加了两个相应的登录结果Login_Success.jsp
、Login_Fail.jsp
- 在主页添加了一个按钮,点击之后跳转到登录界面。
- 修复了在登录界面点击登录按钮没有反应的问题。
- 在登录结果页面添加了返回登录界面的按钮。
- 调整了整个登录逻辑的页面显示问题(例如换行)。
- 修复了登录处理逻辑上的一个漏洞(sql 语句问题)。
- 给登录结果页面增加了一个显示登录用户用户名的欢迎页面。
- 修复了登录结果页面无法获取到用户名的问题,现在登录页面可以正常显示登录用户的用户名了。
- 修改了
UserDAO
中的userLogin
方法,添加冻结判定。 - 修复了冻结用户时会报错的问题。
- 修复了登录已冻结用户时仍可正常登录的问题。
- 修复了用户基本信息页面的跳转到详情页按钮无法正常工作的问题。
- 优化了详情展示页的处理逻辑。点击用户详情按钮后会先展示详情列表,点击列表下方的按钮会跳转至详情编辑列表。
- 在详情编辑列表里添加了提交按钮。
- 在详情编辑列表里添加了用户名,使编辑过程更加直观。
- 在
UserDAO
中添加了userDetailList
方法,用于从数据库中获取用户的详情(积分、余额、卡券)。 - 详情编辑页面添加一个按钮,点击后跳转至该用户的详情编辑页面。
- 修复了详情编辑编辑页面无法获取到用户信息的问题。
- 修复了详情页面的个人信息错位的问题。
- 修复了详情编辑页面提交按钮跳转地址有误的问题。
- 修复了详情编辑页面无法正确获取到用户名的问题。
- 给详情编辑页面的 uid 和用户名添加了只读属性。
- 修改了详情编辑的结果页面中,返回按钮的跳转地址。至此,详情编辑功能成功实现。
- 在
pom.xml
文件中添加了commons-fileupload
依赖,用于上传图像。 - 更新了 servlet 的依赖(原来的 servlet 版本不对)。
- 在项目模块中添加了 jsmart 的配置。
- 修改项目模块中的 jsmart 配置文件,并且在 tomcat 的
lib
目录下 穆现在上传图片时不会再出现classnotfound
的错误了。 - 找到了上传后图片的储存位置。
- 调整了代码,现在图片上传界面不会再有一个用户
uid
的输入框,已经将它设置为隐藏。虽然已经将它设为不可编辑,但是没必要显示出来。 - 添加了
code.jsp
文件,用于展示验证码。 - 修复了无法正常显示验证码的问题,原因为某些函数未定义。
- 在
code.jsp
添加了refresh
函数,用于刷新验证码。 - 在
code.jsp
中添加了判断输入的账号密码是否为空的函数。 - 删除了
code.jsp
的一行代码,解决了一个验证码显示的 bug ,但还是无法正常显示验证码。 - 修改了
code.jsp
中的一个方法,解决了验证码显示的 bug ,现在已经可以正常显示验证码了。 - 把
UserReg.html
删掉了,用UserReg.jsp
代替,跟其他页面统一。 - 在
UserReg.jsp
中添加了关于验证码的判定。 - 修复了验证码错误时不会弹窗报错的漏洞。
- 修复验证码错误弹窗时无法正常显示中文的问题。
项目代码
DAO
UserDAO
提供数据库的相关操作方法
package com.xxm;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author: xxm
* @Date: 2021/12/1 19:49
* UserDAO 类,类中包含各种对数据库操作的方法
*/
public class UserDAO {
/**
* 方法 0:initTable
* 方法用作测试,完成前记得删掉!
* 作用为:如果表 userTable 存在,则将其删掉,并重新创建
*/
public void initTable() {
//连接数据库、执行 sql 语句
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//初始化的 sql 语句,如果表存在则 DROP ,不存在则创建
String sql1 = "DROP TABLE IF EXISTS userTable;";
//创建表的 sql 语句
String sql2 =
"CREATE TABLE userTable ( \n" +
"uid INT(32) NOT NULL AUTO_INCREMENT,\n" +
"username VARCHAR(32) NOT NULL,\n" +
"password VARCHAR(32) NOT NULL DEFAULT '123456',\n" +
"gender CHAR(4) NOT NULL,\n" +
"id CHAR(18) NOT NULL,\n" +
"phone CHAR(11) NOT NULL,\n" +
"age INT(4) NOT NULL,\n" +
"address VARCHAR(32) NOT NULL,\n" +
"company VARCHAR(32) NOT NULL,\n" +
"freeze BOOLEAN,\n" +
"points INT(16),\n" +
"balance INT(32),\n" +
"coupon VARCHAR(32),\n" +
"PRIMARY KEY(uid)\n)" +
";";
//插入几个初始数据的 sql 语句
String sql3 = "INSERT INTO userTable(uid,username,gender,id,phone,age,address,company)\n" +
"VALUES\n" +
"(1,'admin','管理员','123456199645451088','13512345678',0,'管理','管理'),\n" +
"(2,'张飞','男','123456199645451088','13512345678',34,'燕','蜀'),\n" +
"(3,'刘备','男','123456199254510882','13512325478',36,'成都','蜀'),\n" +
"(4,'张辽','男','123436199254854828','18823254738',35,'逍遥津','魏'),\n" +
"(5,'貂蝉','女','123456788932135545','13512342234',24,'洛阳','东汉')\n" +
";\n";
try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/study?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai", "root", "root");
PreparedStatement pstmt = conn.prepareStatement(sql1)) {
pstmt.execute(sql1);
pstmt.execute(sql2);
pstmt.execute(sql3);
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 方法 1:
* getConnection 方法,作用为与数据库取得连接。可以起到简化代码的作用
* 与数据库连接的方法,返回 Connection 对象
*/
public Connection getConnection() throws SQLException {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return DriverManager.getConnection("jdbc:mysql://localhost:3306/study?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai", "root", "root");
}
/**
* 方法 2:
* addUser 方法,其作用为将 User 对象的数据(姓名、性别、手机号、身份证号、年龄、现住址、工作单位)保存至数据库
* 在数据前面还加了一个 uid ,作为用户的标识符。
* 根据添加结果,返回一个布尔值,servlet 再根据这个布尔值决定跳转至相应的页面。
*/
public boolean addUser(User user) throws Exception {
//定义 sql 语句
String sql = "INSERT INTO userTable (uid,username,gender,phone,id,age,address,company) VALUES (0,?,?,?,?,?,?,?)";
//连接数据库、执行 sql 语句
Class.forName("com.mysql.cj.jdbc.Driver");
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, user.getUsername());
pstmt.setString(2, user.getGender());
pstmt.setString(3, user.getPhone());
pstmt.setString(4, user.getId());
pstmt.setInt(5, user.getAge());
pstmt.setString(6, user.getAddress());
pstmt.setString(7, user.getCompany());
int num = pstmt.executeUpdate();
if (num != 0) {
System.out.println("注册成功");
return true;
} else {
return false;
}
} catch (SQLException e) {
System.out.println("注册失败,账号已存在");
return false;
}
}
/**
* 方法 3:
* userEdit 方法,作用为,编辑用户信息
* uid 是要编辑的用户的 uid,
* 其他传入参数是用户输入的信息。
*/
public int editUser(int uid, String username, String gender, String phone, String id, int age, String address, String company) {
//定义 sql 语句,用占位符标记,方便接收要修改的数据
int number = 0;
String sql = "UPDATE userTable SET username = ?, gender = ?, phone = ?, id = ?, age = ?, address = ?, company = ? WHERE uid = ?";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, username);
pstmt.setString(2, gender);
pstmt.setString(3, phone);
pstmt.setString(4, id);
pstmt.setInt(5, age);
pstmt.setString(6, address);
pstmt.setString(7, company);
pstmt.setInt(8, uid);
number = pstmt.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return number;
}
/**
* 方法 4:
* userList 方法,作用为:获取一个 List,里面有数据库里的所有用户的数据的所有的 User 类对象的基本信息。
*/
public List<User> userList() {
List<User> users = new ArrayList<>();
//定义 sql 语句, uid 为 1 的用户是管理员用户 admin,因此将其排除在展示列表外
String sql = "SELECT username,gender,phone,id,age,address,company,uid FROM userTable WHERE uid != 1";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
User user = new User();
user.setUsername(rs.getString(1));
user.setGender(rs.getString(2));
user.setPhone(rs.getString(3));
user.setId(rs.getString(4));
user.setAge(rs.getInt(5));
user.setAddress(rs.getString(6));
user.setCompany(rs