Project(3)——用户登录&修改密码

Project(3)

1、分析项目

当需要开发某个项目时,首先,应该分析这个项目中,需要处理哪些种类的数据!例如:用户、商品、商品类别、收藏、订单、购物车、收货地址…

然后,将以上这些种类的数据的处理排个顺序,即先处理哪种数据,后处理哪种数据!通常,应该先处理基础数据,再处理所相关的数据,例如需要先处理商品数据,才可以处理订单数据,如果多种数据之间没有明显的关联,则应该先处理简单的,再处理较难的!

则以上这些数据的处理顺序应该是:用户 > 收货地址 > 商品类别 > 商品 > 收藏 > 购物车 > 订单

当确定了数据处理顺序后,就应该分析某个用户对应的功能有哪些,以“用户”数据为例,相关功能有:注册、登录、修改密码、修改资料、上传头像…

然后,还是需要确定以上功能的开发顺序,通常,遵循“增 > 查 > 删 > 改”的顺序,则以上功能的开发顺序应该是:注册 > 登录 > 修改密码 > 修改资料 > 上传头像。

每个功能的开发都应该遵循 创建数据表 > 创建实体类 > 持久层 > 业务层 > 控制器层 > 前端页面

一次只解决一个问题
大问题拆成小问题

2、用户 - 注册 - 创建数据表

3、用户 - 注册 - 创建实体类

4、用户 - 注册 - 持久层

a.规划SQL语句

b.接口与抽象方法

c.配置映射

5、用户 - 注册 - 业务层

业务层的基本定位

a.规划异常

b.接口与抽象方法

c.实现类与重写方法

6、用户 - 注册 - 控制器层

a.处理异常

b.设计请求

c.处理请求

7、用户 - 注册 - 前端页面

8、用户 - 登录 - 持久层

a.规划SQL语句

登录验证的做法应该是:根据用户名查询数据是否存在,如果存在,则取出必要的数据,例如密码,然后,在Java程序中验证密码即可。

如果用户名匹配的数据是存在的,需要取出的数据有:密码,盐,是否标记为删除,uid,用户名。对应的SQL语句大致是:

SELECT  
    uid,username,
    password,salt,
    is_delete
FROM 
    t_user 
WHERE 
    username=?

b.接口与抽象方法

在接口中已经存在findByUsername()方法,则无须重复添加。

c.配置映射

只需在原有的findByUsername()方法映射的SQL语句中,添加查询更多的字段即可!

    <!-- 根据用户名查询用户数据 -->
    <!-- User findByUsername(String username); -->
    <select id="findByUsername" resultType="cn.tedu.store.entity.User">
        SELECT
            uid,username,
            password,salt,
            is_delete AS isDelete
            <!-- is_delete要取别名,否则查询结果无法封装到user中 -->
        FROM
            t_user
        WHERE
            username=#{username}
    </select>

然后执行单元测试

9、用户 - 登录 - 业务层

a.规划异常

规划异常,应该是列举此次操作中可能存在的操作失败,包括用户提交不合理甚至错误的数据,或不符合逻辑的数据,都是失败的!

在“登录”时,用户提交的用户名可能是未被注册的,即不存在的,对于这种情况,应该抛出对应的异常:UserNotFoundException

也可能查询到了用户名匹配的数据,但是,是被标记为删除的,这种用户数据也是不允许登录的,也应该抛出异常:UserNotFoundException

在验证密码时,还可能出现密码不匹配的问题,也是不允许登录的,则抛出异常:PasswordNotMatchException

则需要创建cn.tedu.store.service.ex.UserNotFoundExceptionPasswordNotMatchException,它们都是ServiceException的子类。

b.接口与抽象方法

IUserService接口中添加新的抽象方法:

    /**
	 * 用户登录
	 * @param username 用户名
	 * @param password 密码
	 * @return 登录成功的用户的信息
	 * @throws UserNotFoundException 用户名不存在异常
	 * @throws PasswordNotMatchException 密码错误异常
	 */
    User login(String username, String password) 
                    throws UserNotFoundException, PasswordNotMatchException;

c.实现类与重写方法

UserServiceImpl中重写接口中的抽象方法:

    @Override
	public User login(String username, String password) 
			throws UserNotFoundException, PasswordNotMatchException {
		// 根据 username 查询数据是否为 null
		// 是 -- 抛出 UserNotFoundException
		// 判断 isDelete 是否为 1
		// 是 -- 抛出 UserNotFoundException
		
		
		// 获取数据库中的密码 mysqlPassword
		// 获取盐值 salt
		// 将用户提交的密码 password 与 salt 加密得到 md5Password
		// 比较 mysqlPassword 与 md5Password 是否不一致
		// 是-- 抛出 PasswordNotMatchException
		
		
		// 将查询结果中的 password、salt、isDelete 设为 null
		
		// 返回查询结果
		
	}

代码实现:

    @Override
	public User login(String username, String password) 
			throws UserNotFoundException, PasswordNotMatchException {
	    // 根据 username 查询数据是否为 null
		// 是 -- 抛出 UserNotFoundException
		// 判断 isDelete 是否为 1
		// 是 -- 抛出 UserNotFoundException
		User result = userMapper.findByUsername(username);
		if(result == null) {
			throw new UserNotFoundException("登陆失败!用户名不存在!");
		}
		if(result.getIsDelete() == 1) {
			throw new UserNotFoundException("登陆失败!用户名不存在!");
		}
		
		// 获取数据库中的密码 mysqlPassword
		// 获取盐值 salt
		// 将用户提交的密码 password 与 salt 加密得到 md5Password
		// 比较 mysqlPassword 与 md5Password 是否不一致
		// 是-- 抛出 PasswordNotMatchException
		String mysqlPassword = result.getPassword();
		String salt = result.getSalt();
		String md5Password = getMd5Password(password, salt);
		if(!mysqlPassword.equals(md5Password)) {
			throw new PasswordNotMatchException("登陆失败!密码错误!");
		}
		
		// 将查询结果中的 password、salt、isDelete 设为 null
		result.setPassword(null);
		result.setSalt(null);
		result.setIsDelete(null);
		
		// 返回查询结果
		return result;
	}

UserServiceTests中编写新的测试方法,以执行单元测试:

    @Test
	public void testLogin() {
		try {
			String username = "root";
			String password = "123";
			User user = service.login(username, password);
			System.err.println(user);
		}catch(ServiceException e) {
			System.err.println(e.getClass().getName());
			System.err.println(e.getMessage());
		}
	}

10、用户 - 登录 - 控制器层

a.处理异常

此次业务层抛出了新的异常,则需在BaseController的处理异常的方法中,添加更多的分支,对这些新的异常进行处理。

/**
 * 控制器类的基类,实现统一处理异常
 */
public abstract class BaseController {

	/**
	 * 操作结果的“成功”状态
	 */
	public static final Integer SUCCESS = 2000;

	// 只处理ServiceException及其子孙类异常,避免异常过度处理
	@ExceptionHandler(ServiceException.class)
	public JsonResult<Void> handleException(Throwable e) {
		JsonResult<Void> jr = new JsonResult<Void>();
		jr.setMessage(e.getMessage());

		if (e instanceof UsernameDuplicateException) {
			jr.setState(4000);
		} else if (e instanceof UserNotFoundException) {
			jr.setState(4001);
		} else if (e instanceof PasswordNotMatchException) {
			jr.setState(4002);
		} else if (e instanceof InsertException) {
			jr.setState(5000);
		}

		return jr;
	}

}

b.设计请求


请求路径:/users/login
请求参数:String username, String password, HttpSession session
请求方式:POST
响应数据:JsonResult<User>

通常,请求路径中,后半部分表示当前功能的名称,可以与业务层方法的名称保持一致!

c.处理请求

UserController类中:

    //使用@RequestMapping注解方便在地址栏上测试!
    @RequestMapping("login")
	public JsonResult<User> login(String username, String password, HttpSession session) {
		// 执行登录,获取登录返回结果
		User user = userService.login(username, password);
		// 向session中封装数据
		session.setAttribute("uid", user.getUid());
		session.setAttribute("username", user.getUsername());
		
		// 向客户端响应操作成功
		// 因为要返回响应结果,所以需要在JsonResult类中在添加一个新的构造方法
		return new JsonResult<User>(SUCCESS, user);
	}

JsonResult类中添加新的构造方法:

/**
 * 向客户端响应操作结果的数据类型
 * @param <T> 向客户端响应的数据的类型
 */
public class JsonResult<T> {
    ....
    ....
    ....
    public JsonResult(Integer state, T data) {
		super();
		this.state = state;
		this.data = data;
	}
}

注:

因为登录的返回的user对象中,有很多属性值为null,且这样的返回user对象会暴露我们所设计的数据结构:

在这里插入图片描述

所以可以再返回值类型的类之前添加@JsonInclude(Include.NON_NULL)注解:

/**
 * 用户数据的实体类
 * @author DELL
 *
 */
@JsonInclude(Include.NON_NULL)
public class User extends BaseEntity {
    ....
    ....
}
/**
 * 向客户端响应操作结果的数据类型
 * 
 * @param <T> 向客户端响应的数据的类型
 */
@JsonInclude(Include.NON_NULL)
public class JsonResult<T> {
    ....
    ....
}

新的返回值:
在这里插入图片描述
@JsonInclude(Include.NON_NULL)注解还可以加在类的属性前,使用更灵活。

也可在spring配置文件中统一配置:


spring.jackson.default-property-inclusion=non-null

11、用户 - 登录 - 前端页面

    <script type="text/javascript">
	    // 页面加载完成后执行的方法(函数)
		$(document).ready(function() {
			// 在button控件上添加id:btn-reg
			$("#btn-login").click(function() {
				$.ajax({
					// url:          请求交到哪里去
					// data:        请求提交的参数,以上的form表单中,在需要提交的参数的控件中添加name属性;
					//            form表单上添加id属性
					// type:        请求方式
					// dataType: 服务器端响应的结果的类型
					// success:   响应成功时的处理函数
					"url" : "/users/login",
					"data" : $("#form-login").serialize(),
					"type" : "post",
					"dataType" : "json",
					"success" : function(json) {
						if (json.state == 2000) {
							alert("登录成功!");
							//跳转到某个页面
						} else {
							alert(json.message);
						}
					}
				});
			});
		});
	</script>

12、用户 - 修改密码 - 持久层

a.规划SQL语句

修改密码的SQL语句大致是:


UPDATE t_user SET password=?, modified_user=?, modified_time=? WHERE uid=?

在执行更新之前,还应检查用户数据是否正常,并验证其密码是否正确,则需要执行:


SELECT is_delete, password, salt FROM t_user WHERE uid=?

b.接口与抽象方法

需要在UserMapper接口中添加新的抽象方法:

    /**
	 * 修改密码
	 * @param uid 用户id
	 * @param password 用户提交的新密码
	 * @param modifiedUser 修改人
	 * @param modifiedTime 修改时间
	 * @return 受影响的行数
	 */
	Integer updatePassword(
			@Param("uid") Integer uid, 
			@Param("password") String password, 
			@Param("modifiedUser") String modifiedUser, 
			@Param("modifiedTime") Date modifiedTime);
    /**
	 * 根据用户id查询用户数据
	 * @param uid 要查询的用户的id
	 * @return 匹配的用户数据,如果没有匹配的数据,则返回null
	 */
	User findByUid(Integer uid);

注:在接口中,应将各方法按照某种顺序进行排列。

c.配置映射

UserMapper.xml中配置以上两个方法的映射:

    <!-- 修改密码 -->
    <!-- Integer updatePassword(
			@Param("uid") Integer uid, 
			@Param("password") String password, 
			@Param("modifiedUser") String modifiedUser, 
			@Param("modifiedTime") Date modifiedTime);
	 -->
    <update id="updatePassword">
        UPDATE
            t_user
        SET
            password=#{password}, modified_user=#{modifiedUser}, 
            modified_time=#{modifiedTime}
        WHERE
            uid=#{uid}
    </update>
    



    <!-- 根据用户id查询用户数据 -->
    <!-- User findByUid(Integer uid); -->
    <select id="findByUid" resultType="cn.tedu.store.entity.User">
        SELECT 
            is_delete  AS isDelete, password,
            salt
        FROM
            t_user
        WHERE
            uid=#{uid}
    </select>

然后再UserMapperTests中编写并执行单元测试:

    @Test
	public void testUpdatePassword() {
		String password = "000";
		Integer uid = 22;
		Date now = new Date();
		String modifiedUser = "Tom3";
		Date modifiedTime = now;
		Integer rows = userMapper.updatePassword(uid, password, modifiedUser, modifiedTime);
		System.err.println(rows);
	}
	

	@Test
	public void testFindByUid() {
		Integer uid = 22;
		User user = userMapper.findByUid(uid);
		System.err.println(user);
	}

注:用于测试的数据,在测试完成之后,需要将这些数据删除。

13、用户 - 修改密码 - 业务层

a.规划异常

此次更新密码之前,需要检查用户数据是否正常,如:用户数据是否存在、用户数据是否被标记为已删除,则可能抛出:UserNotFoundException

在执行更新之前还需验证原密码是否正确,则可能抛出:PasswordNotMatchException

在执行更新过程中,也可能出现更新失败,返回的受影响行数不符合预期值,则可能抛出:UpdateException

所以需要创建:cn.tedu.store.service.ex.UpdateException

/**
 * 更新数据异常
 * @author DELL
 *
 */
 public class UpdateException extends ServiceException {
    // 序列化接口
    // 五个构造方法
}	

b.接口与抽象方法

IUserService中添加抽象方法:

业务层抽象方法设计原则:

  1. 返回值:仅以操作成功为前提来设计返回值(因为通常失败会以异常的方式返回给用户,不必考虑失败的返回值);
  2. 方法名:尽量体现业务例如使用reg或regist或register表示注册,使用login表示登录;
  3. 参数列表:一定是客户端可以提供的数据,或来自于Session中的数据,且足够调用持久层的各方法;
  4. 异常:把用户操作失败的可能,都设计成各种异常,并把这些异常都添加到方法的声明中。
    /**
	 * 修改密码
	 * @param uid 用户id
	 * @param username 用户名
	 * @param oldPassword 旧密码
	 * @param newPassword 新密码
	 * @throws UserNotFoundException 用户不存在异常
	 * @throws PasswordNotMatchException 密码错误异常
	 * @throws UpdateException 更新失败异常
	 */
    void changePassword(Integer uid, String username, String oldPassword, String newPassword) 
    throws UserNotFoundException, PasswordNotMatchException, UpdateException;

c.实现类与重写方法

UserServiceImpl中重写修改密码的方法:

void changePassword(Integer uid, String username, String oldPassword, String newPassword) 
    throws UserNotFoundException, PasswordNotMatchException, UpdateException{
        // 根据 uid 查询用户数据 result 是否存在
		// 是 -- 抛出 UserNotFoundException
		// 判断 isDelete 是否为 1
		// 是 -- 抛出 UserNotFoundException
		
		// 根据 result 获取数据库中的密码 mysqlPassword
		// 根据 result 获取盐值 salt
		// 将 oldPassword 和 salt 加密获得 oldMd5Password
		// 比较 mysqlPassword 与 oldMd5Password 是否不一致
		// 是-- 抛出 PasswordNotMatchException
		
		// 将 newPassword 和 salt 加密获得 newMd5Password
		// uid、username、newMd5Password,获取当前时间
		// 执行修改密码
		
}

代码实现:

    
    @Override
	public void changePassword(Integer uid, String username, String oldPassword, String newPassword)
			throws UserNotFoundException, PasswordNotMatchException, UpdateException {
		// 根据 uid 查询用户数据 result 是否存在
		// 是 -- 抛出 UserNotFoundException
		// 判断 isDelete 是否为 1
		// 是 -- 抛出 UserNotFoundException
		User result = userMapper.findByUid(uid);
		if(result == null) {
			throw new UserNotFoundException("修改密码失败!用户不存在!");
		}
		if(result.getIsDelete() == 1) {
			throw new UserNotFoundException("修改密码失败!用户不存在!");
		}
		
		// 根据 result 获取数据库中的密码 mysqlPassword
		// 根据 result 获取盐值 salt
		// 将 oldPassword 和 salt 加密获得 oldMd5Password
		// 比较 mysqlPassword 与 oldMd5Password 是否不一致
		// 是-- 抛出 PasswordNotMatchException
		String mysqlPassword = result.getPassword();
		String salt = result.getSalt();
		String oldMd5Password = getMd5Password(oldPassword, salt);
		
		// System.err.println("salt           = " + salt);
		// System.err.println("oldPassword    = " + oldPassword);
		// System.err.println("mysqlPassword  = " + mysqlPassword);
		// System.err.println("oldMd5Password = " + oldMd5Password);
		
		if(!mysqlPassword.equals(oldMd5Password)) {
			throw new PasswordNotMatchException("修改密码失败!原密码错误!");
		}
		
		// 将 newPassword 和 salt 加密获得 newMd5Password
		// uid、username、newMd5Password,获取当前时间
		// 执行修改密码
		String newMd5Password = getMd5Password(newPassword, salt);
		Date now = new Date();
		Integer rows = userMapper.updatePassword(uid, newMd5Password, username, now);
		if(rows != 1) {
			throw new UpdateException("修改密码失败!出现未知错误!请联系系统管理员!");
		}
		
	}


    /**
	 * 对密码进行加密
	 * 
	 * @param password 原始密码
	 * @param salt     盐值
	 * @return 加密后的密码
	 */
	String getMd5Password(String password, String salt) {
		// 规则:对 原始密码+盐值 3重加密
		String str = password + salt;
		for (int i = 0; i < 3; i++) {
			str = DigestUtils.md5Hex(str.getBytes());
		}
		return str;
	}
	

UserServiceTests中编写并执行测试代码:

    @Test
	public void testUpdatePassword() {
		try {
			Integer uid = 20;
			String username = "Tom0";
			String oldPassword = "123";
			String newPassword = "000";
			service.changePassword(uid, username, oldPassword, newPassword);
			System.err.println("OK");
		}catch(ServiceException e) {
			System.err.println(e.getClass().getName());
			System.err.println(e.getMessage());
		}
	}

14、用户 - 修改密码 - 控制器层

a.处理异常

此次业务层抛出了新的异常:UpdateException,则需要在BaseController中进行处理!

b.设计请求


请求路径:/users/change_password
请求参数:String oldPassword, String newPassword, HttpSession session
请求方式:POST
响应数据:JsonResult<Void> //绝大部分情况下,JsonResult泛型的类型与业务层的抽象方法的返回值类型一致

c.处理请求

    
    // 设计为RequestMapping是为了方便在地址栏中进行测试
    @RequestMapping("change_password")
	public JsonResult<Void> changePassword(
			@RequestParam("old_password") String oldPassword, 
			@RequestParam("new_password") String newPassword, 
			HttpSession session){
		// 从session中获取uid和username
		Integer uid = Integer.valueOf(session.getAttribute("uid").toString());
		String username = session.getAttribute("username").toString();
		System.err.println("uid+username:"+uid+"--"+username);
		// 执行修改密码
		userService.changePassword(uid, username, oldPassword, newPassword);
		// 响应修改成功
		return new JsonResult<Void>(SUCCESS);
	}
	

完成后,启动项目,在浏览器中,先登录,然后通过http://localhost:8080/users/change_password?old_password=123&new_password=000进行测试

15、用户 - 修改密码 - 前端页面

    <script type="text/javascript">
	    // 页面加载完成后执行的方法(函数)
		$(document).ready(function() {
			// 在button控件上添加id:btn-reg
			$("#btn-change-password").click(function() {
				$.ajax({
					// url:          请求交到哪里去
					// data:        请求提交的参数,以上的form表单中,在需要提交的参数的控件中添加name属性;
					//            form表单上添加id属性
					// type:        请求方式
					// dataType: 服务器端响应的结果的类型
					// success:   响应成功时的处理函数
					"url" : "/users/change_password",
					"data" : $("#form-change-password").serialize(),
					"type" : "post",
					"dataType" : "json",
					"success" : function(json) {
						if (json.state == 2000) {
							alert("修改成功!");
							//跳转到某个页面
						} else {
							alert(json.message);
						}
					}
				});
			});
		});
	</script>
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值