博客
关于我
Project(3)——用户登录&修改密码
阅读量:197 次
发布时间:2019-02-28

本文共 12473 字,大约阅读时间需要 41 分钟。

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_deleteFROM     t_user WHERE     username=?

b.接口与抽象方法

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

c.配置映射

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

然后执行单元测试

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				// 从查询结果中获取盐值		// 根据用户提交的参数password和盐值进行加密,得到加密后的密码		// 判断查询结果中的password和以上加密后的密码是否不一致		// 抛出PasswordNotMatchException				// 将查询结果中的password、salt、isDelete设置为null		// 返回查询结果		return null;	}

代码实现:

@Override	public User login(String username, String password) 			throws UserNotFoundException, PasswordNotMatchException {   		// 根据参数username查询用户数据		// 判断查询结果是否为null		// 抛出UserNotFoundException		User result = userMapper.findByUsername(username);		if(result == null) {   			throw new UserNotFoundException("登录失败!用户名不存在!");		}				// 判断查询结果中的isDelete是否为1		// 抛出UserNotFoundException		if(result.getIsDelete() == 1) {   			throw new UserNotFoundException("登录失败!用户名不存在!");		}				// 从查询结果中获取盐值		// 根据用户提交的参数password和盐值进行加密(调用getMd5Password方法加密),得到加密后的密码		// 判断查询结果中的password和以上加密后的密码是否不一致		// 抛出PasswordNotMatchException		String salt = result.getSalt();		String md5Password = getMd5Password(password, salt);		System.err.println("salt = " + salt);		System.err.println("md5Password = " + md5Password);		System.err.println("user.getPassword = " + result.getPassword());		if(! result.getPassword().equals(md5Password)) {   			throw new PasswordNotMatchException("登录失败!密码错误!");		}				// 将查询结果中的password、salt、isDelete设置为null		result.setPassword(null);		result.setSalt(null);		result.setIsDelete(null);				// 返回查询结果		System.err.println(result);		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
handleException(Throwable e) { JsonResult
jr = new JsonResult
(); 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

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

c.处理请求

UserController类中:

//使用@RequestMapping注解方便在地址栏上测试!    @RequestMapping("login")	public JsonResult
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
(SUCCESS, user); }

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

/** * 向客户端响应操作结果的数据类型 * @param 
向客户端响应的数据的类型 */public class JsonResult
{ .... .... .... 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 
向客户端响应的数据的类型 */@JsonInclude(Include.NON_NULL)public class JsonResult
{ .... ....}

新的返回值:

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

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

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

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

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中配置以上两个方法的映射:

UPDATE t_user SET password=#{ password}, modified_user=#{ modifiedUser}, modified_time=#{ modifiedTime} WHERE uid=#{ uid}

然后再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查询用户数据;    // 判断查询结果是否为null -- 为null则抛出:UserNotFoundException;    // 判断查询结果中isDelete是否为1 -- 是则抛出:UserNotFoundException;        // 从查询结果中获取盐值    // 根据参数oldPassword和盐值进行加密,获得加密后的密码    // 判断查询结果中的password和以上加密后的密码是否不一致    // -- 是则抛出:PasswordNotMatchException;        // 根据参数newPassword和盐值进行加密,得到加密后的密码    // 创建当前时间对象    // 执行更新密码,获取返回的受影响行数    // 判断受影响的行数是否不为1 -- 是则抛出:UpdateException}

代码实现:

@Override	public void changePassword(Integer uid, String username, String oldPassword, String newPassword)			throws UserNotFoundException, PasswordNotMatchException, UpdateException {   		// 根据uid查询用户数据		// 判断查询结果是否为null -- 为null则抛出:UserNotFoundException		// 判断查询结果中的isDelete是否为1 -- 为1则抛出:UserNotFoundException		User result = userMapper.findByUid(uid);		System.err.println(result);		if(result == null) {   			throw new UserNotFoundException("修改密码失败!用户不存在!");		}		if(result.getIsDelete() == 1) {   			throw new UserNotFoundException("修改密码失败!用户不存在!");		}				// 获取查询结果中的盐值		// 根据oldPassword和盐值进行加密,得到加密后的密码		// 判断查询结果中的password与以上加密后的密码是否不一致		// -- 不一致则抛出:PasswordNotMatchException		String salt = result.getSalt();		String md5Password = getMd5Password(oldPassword, salt);		System.err.println("盐值:"+salt);		System.err.println("用户输入的oldPassword:"+oldPassword);		System.err.println("数据库中的:"+result.getPassword());		System.err.println("用户输入的旧密码:"+md5Password);		if(!result.getPassword().equals(md5Password)) {   			throw new PasswordNotMatchException("修改密码失败!原密码错误!");		}				// 根据参数newPassword和盐值进行加密,得到加密后的密码		// 获取当前时间对象		// 执行更新密码,并返回受影响的行数		// 判断受影响的行数是否不为1 -- 不为1则抛出:UpdateException		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
//绝大部分情况下,JsonResult泛型的类型与业务层的抽象方法的返回值类型一致

c.处理请求

// 设计为RequestMapping是为了方便在地址栏中进行测试    @RequestMapping("change_password")	public JsonResult
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
(SUCCESS); }

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

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

转载地址:http://arzs.baihongyu.com/

你可能感兴趣的文章
MYSQL一直显示正在启动
查看>>
MySQL一站到底!华为首发MySQL进阶宝典,基础+优化+源码+架构+实战五飞
查看>>
MySQL万字总结!超详细!
查看>>
Mysql下载以及安装(新手入门,超详细)
查看>>
MySQL不会性能调优?看看这份清华架构师编写的MySQL性能优化手册吧
查看>>
MySQL不同字符集及排序规则详解:业务场景下的最佳选
查看>>
Mysql不同官方版本对比
查看>>
MySQL与Informix数据库中的同义表创建:深入解析与比较
查看>>
mysql与mem_细说 MySQL 之 MEM_ROOT
查看>>
MySQL与Oracle的数据迁移注意事项,另附转换工具链接
查看>>
mysql丢失更新问题
查看>>
MySQL两千万数据优化&迁移
查看>>
MySql中 delimiter 详解
查看>>
MYSQL中 find_in_set() 函数用法详解
查看>>
MySQL中auto_increment有什么作用?(IT枫斗者)
查看>>
MySQL中B+Tree索引原理
查看>>
mysql中cast() 和convert()的用法讲解
查看>>
mysql中datetime与timestamp类型有什么区别
查看>>
MySQL中DQL语言的执行顺序
查看>>
mysql中floor函数的作用是什么?
查看>>