2019_07_01store修改密码 第三天

9. 用户-登录-业务层

(a) 规划异常

此时,应该穷举用户在“登录”过程中可能出现的任何“错误”,例如,可能出现“用户名不存在”,或“密码错误”,或“用户数据被标记为已删除”。

所以,需要为以上“错误”创建对应的异常类:

cn.tedu.store.service.ex.UserNotFoundException

cn.tedu.store.service.ex.PasswordNotMatchException

创建的异常类都应该继承自ServiceException

(b) 接口与抽象方法

IUserService接口中添加“登录”的抽象方法:

User login(String username, String password) throws UserNotFoundException, PasswordNotMatchException;

© 实现抽象方法

UserServiceImpl实现类中重写以上抽象方法:

public User login(String username, String password) throws UserNotFoundException, PasswordNotMatchException {
	// 根据参数username执行查询
	// 判断查询结果是否为null
	// 是:抛出UserNotFoundException

	// 判断查询结果中的isDelete是否为1
	// 是:抛出UserNotFoundException

	// 从查询结果中获取盐值
	// 基于参数password和盐值执行加密
	// 判断以上加密结果与查询结果中的password是否不匹配
	// 是:抛出PasswordNotMatchException

	// 将查询结果中的password设置为null
	// 将查询结果中的salt设置为null
	// 将查询结果中的isDelete设置为null
	// 返回查询结果
}

具体实现为:

@Override
public User login(String username, String password) throws UserNotFoundException, PasswordNotMatchException {
	// 根据参数username执行查询
	User result = userMapper.findByUsername(username);
	// 判断查询结果是否为null
	if (result == null) {
		// 是:抛出UserNotFoundException
		throw new UserNotFoundException(
			"登录失败!用户数据不存在!");
	}
	
	// 判断查询结果中的isDelete是否为1
	if (result.getIsDelete() == 1) {
		// 是:抛出UserNotFoundException
		throw new UserNotFoundException(
			"登录失败!用户数据不存在!");
	}

	// 从查询结果中获取盐值
	String salt = result.getSalt();
	// 基于参数password和盐值执行加密
	String md5Password = getMd5Password(password, salt);
	// 判断以上加密结果与查询结果中的password是否不匹配
	if (!md5Password.equals(result.getPassword())) {
		// 是:抛出PasswordNotMatchException
		throw new PasswordNotMatchException(
			"登录失败!密码错误!");
	}

	// 将查询结果中的password设置为null
	result.setPassword(null);
	// 将查询结果中的salt设置为null
	result.setSalt(null);
	// 将查询结果中的isDelete设置为null
	result.setIsDelete(null);
	// 返回查询结果
	return result;
}

最后,在UserServiceTests中编写并执行单元测试:

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

##################### 测试结果为1 #####################
控制台输出登录成功信息:
User [uid=11, username=root1, password=null, salt=null, gender=null, phone=null, email=null, avatar=null, isDelete=null]

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

(a) 统一处理异常

BaseController的处理异常的方法中,添加更多的分支,对以上新抛出的2种异常进行处理:

@ExceptionHandler(ServiceException.class)
	@ResponseBody
	public JsonResult<Void> handleException(Throwable e) {
		JsonResult<Void> jr = new JsonResult<>(e);
		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>

© 处理请求

在类中添加处理请求的方法:

@RequestMapping("login")
public JsonResult<User> login(String username, String password, HttpSession session) {
	// 调用业务层对象的“登录”方法,获取返回结果
	// 向Session中存入用户id和用户名
	// 返回
}

具体代码为:

@RequestMapping("login")
public JsonResult<User> login(String username, String password, HttpSession session) {
	// 调用业务层对象的“登录”方法,获取返回结果
	User data = userService.login(username, password);
	// 向Session中存入用户id和用户名
	session.setAttribute("uid", data.getUid());
	session.setAttribute("username", data.getUsername());
	// 返回
	return new JsonResult<>(SUCCESS, data);
}

JsonResult中添加state和data的构造方法,以便于传参数。

完成后,可以通过http://localhost:8080/users/login?username=rootx&password=1234x进行单元测试,完成后,将@RequestMapping改成@PostMapping

对于输出的JSON结果过于复杂,且将为null的结果都已经输出为JSON数据,这种做法并不合理,存在浪费资源的问题,并且还暴露项目的特征数据,要解决该问题,可以在application.properties中添加配置,使得为null的属性将不被输出到JSON结果中:

spring.jackson.default-property-inclusion=non_null

并且在jsonRequest类中加入以下注解:
@JsonInclude(JsonInclude.Include.NON_NULL)
public class JsonResult {
}

##################### 测试结果为2 #####################
登录成功:
{“state”:2000,“data”:{“uid”:10,“username”:“root”}}

用户名不存在:
{“state”:4001,“message”:“登录失败!用户数据不存在!”}

密码错误:
{“state”:4002,“message”:“登录失败!密码错误!”}

11. 用户-登录-前端界面

1.直接复制ajax
2.修改ajax中对应的参数
3.修改表单中的数据,以便于用户输入的值能传到服务端进行匹配。

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

(a) 规划SQL语句

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

update t_user set password=?, modified_user=?, modified_time=? where uid=?

在执行修改之前,还需要验证原密码是否正确,但是,不应该是:

update t_user set password=? where uid=? and password=?

之所以不将密码作为查询条件之一,是因为在SQL语句中不区分大小写,而密码应该匹分大小写,另外,“需要验证原密码”是软件开发者所设计的业务规则,应该在业务层中去体现,并不应该写在SQL语句中。

为了保证“验证原密码”的功能,还需要查询出原密码和盐值,则需要执行:

select password, salt from t_user where uid=?

另外,在查询时,还应该检查用户的is_delete是否正常,所以:

select password, salt, is_delete from t_user where uid=?

(b) 接口与抽象方法

UserMapper.java接口中添加抽象方法:

Integer updatePassword(
	@Param("uid") Integer uid, 
	@Param("password") String password, 
	@Param("modifiedUser") String modifiedUser, 
	@Param("modifiedTime") Date modifiedTime);

User findByUid(Integer uid);

© 配置映射

UserMapper.xml中配置以上2个抽象方法的映射,如果不希望每次查询时都需要自定义别名,可以事先配置好<resultMap>

<!-- 查询结果与用户数据实体的映射 -->
<resultMap id="UserEntityMap" 
	type="cn.tedu.store.entity.User">
	<id column="uid" property="uid"/>
	<result column="username" property="username"/>
	<result column="password" property="password"/>
	<result column="salt" property="salt"/>
	<result column="gender" property="gender"/>
	<result column="phone" property="phone"/>
	<result column="email" property="email"/>
	<result column="avatar" property="avatar"/>
	<result column="is_delete" property="isDelete"/>
	<result column="created_user" property="createdUser"/>
	<result column="created_time" property="createdTime"/>
	<result column="modified_user" property="modifiedUser"/>
	<result column="modified_time" property="modifiedTime"/>
</resultMap>

<!-- 更新密码 -->
<!-- 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"
	resultMap="UserEntityMap">
	SELECT 
		password, salt,
		is_delete
	FROM 
		t_user 
	WHERE 
		uid=#{uid}
</select>

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

@Test
public void updatePassword() {
	Integer uid = 8;
	String password ="1234";
	String modifiedUser = "超级管理员";
	Date modifiedTime = new Date();
	Integer rows = mapper.updatePassword(uid, password, modifiedUser, modifiedTime);
	System.err.println("rows=" + rows);
}

@Test
public void findByUid() {
	Integer uid = 8;
	User result = mapper.findByUid(uid);
	System.err.println(result);
}

##################### 测试结果为3 #####################
1.修改密码

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

(a) 规划异常

本次执行的主要是更新数据的操作,则可能出现UpdateException

在执行更新之前,还需要根据uid查询用户数据,检查数据是否存在,检查数据的is_delete,都可能出现UserNotFoundException

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

则需要创建cn.tedu.store.service.ex.UpdateException,并继承自ServiceException

(b) 接口与抽象方法

IUserService中添加“修改密码”的抽象方法:

void changePassword(Integer uid, String oldPassword, 
String newPassword, String modifiedUser) throws
 UserNotFoundException, PasswordNotMatchException, UpdateException;

关于抽象方法的参数的设计原则:要么是客户端提交的,要么是服务器端的控制器中提供的,并且,穷举所有参数后,足以调用持久层的相关功能!

© 实现抽象方法

UserServiceImpl实现类中添加新的抽象方法并实现:

public void changePassword(Integer uid, String oldPassword,
 String newPassword, String modifiedUser) throws
  UserNotFoundException, PasswordNotMatchException, UpdateException {
	// 根据参数uid查询用户数据
	// 判断查询结果是否为null:UserNotFoundException

	// 判断查询结果中的isDelete是否为1:UserNotFoundException

	// 从查询结果中获取盐值
	// 对参数oldPassword执行加密,得到oldMd5Password
	// 判断查询结果中的密码与oldMd5Password是否不匹配:PasswordNotMatchException

	// 对参数newPassword执行加密,得到newMd5Passowrd
	// 执行更新,获取返回值(受影响的行数)
	// 判断受影响的行数是否不为1:UpdateException
}

具体实现为:

@Override
public void changePassword(Integer uid, String oldPassword, String newPassword, String modifiedUser)
		throws UserNotFoundException, PasswordNotMatchException, UpdateException {
	// 根据参数uid查询用户数据
	User result = userMapper.findByUid(uid);
	// 判断查询结果是否为null:UserNotFoundException
	if (result == null) {
		// 是:抛出UserNotFoundException
		throw new UserNotFoundException(
			"修改密码失败!用户数据不存在!");
	}

	// 判断查询结果中的isDelete是否为1:UserNotFoundException
	if (result.getIsDelete() == 1) {
		// 是:抛出UserNotFoundException
		throw new UserNotFoundException(
			"修改密码失败!用户数据不存在!");
	}
	
	// 从查询结果中获取盐值
	String salt = result.getSalt();
	// 对参数oldPassword执行加密,得到oldMd5Password
	String oldMd5Password = getMd5Password(oldPassword, salt);
	// 判断查询结果中的密码与oldMd5Password是否不匹配:PasswordNotMatchException
	if (!result.getPassword().equals(oldMd5Password)) {
		// 是:抛出PasswordNotMatchException
		throw new PasswordNotMatchException(
			"修改密码失败!原密码错误!");
	}

	// 对参数newPassword执行加密,得到newMd5Passowrd
	String newMd5Password = getMd5Password(newPassword, salt);
	// 执行更新,获取返回值(受影响的行数)
	Integer rows = userMapper.updatePassword(uid, newMd5Password, modifiedUser, new Date());
	// 判断受影响的行数是否不为1:UpdateException
	if (rows != 1) {
		throw new UpdateException(
			"修改密码失败!更新数据时出现未知错误!");
	}
}

最后,在UserServiceTests中编写并执行单元测试:

@Test
public void changePassword() {
	try {
		Integer uid = 9;
		String oldPassword = "8888";
		String newPassword = "1234";
		String modifiedUser = "系统管理员";
		service.changePassword(uid, oldPassword, newPassword, modifiedUser);
		System.err.println("OK");
	} catch (ServiceException e) {
		System.err.println(e.getClass().getName());
		System.err.println(e.getMessage());
	}
}

##################### 测试结果为4 #####################

控制台输出对应修改数据

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

(a) 统一处理异常

需要在BaseController中添加对UpdateException的处理。

(b) 设计请求

设计“修改密码”的请求方式:

请求路径:/users/change_password
请求参数:@RequestParam("old_password") String oldPassword, 
@RequestParam("new_password") String newPassword, HttpSession session
请求方式:POST
响应数据:JsonResult<Void>

© 处理请求

BaseController中添加各子级控制器都可能需要执行的方法,例如获取uid的方法:

protected final Integer getUidFromSession(HttpSession session) {
	return Integer.valueOf(session.getAttribute("uid").toString());
}

protected final String getUsernameFromSession(HttpSession session) {
	return session.getAttribute("username").toString();
}

UserController中添加处理请求的方法:

@RequestMapping("change_password")
public JsonResult<Void> changePassword(
	@RequestParam("old_password") String oldPassword,
	@RequestParam("new_password") String newPassword, 
	HttpSession session) {
	// 从session中获取uid
	Integer uid = getUidFromSession(session);
	// 从session中获取username
	String username = getUsernameFromSession(session);
	// 调用service对象执行修改密码
	userService.changePassword(uid, oldPassword, newPassword, username);
	// 响应成功
	return new JsonResult<>(SUCCESS);
}

完成后,打开浏览器,先登录,然后通过http://localhost:8080/users/change_password?old_password=1234&new_password=8888进行测试。

##################### 测试结果为5 #####################

切忌,在没有登录的情况下测试,否则会出现空指针异常。

15. 添加拦截器

由于后续的越来越多的操作都是需要事先登录的,不登录则不允许执行相关操作,例如修改密码、修改资料、上传头像、创建收货地址、操作购物车、生成订单等……所以,可以在项目中添加登录拦截器,对用户是否登录进行验证,如果没有登录,则不执行后续的请求处理,而是直接重定向到登录页,避免发生错误!

首先,创建cn.tedu.store.interceptor.LoginInterceptor拦截器类,并定义拦截处理方式:

public class LoginInterceptor implements HandlerInterceptor {
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		// 获取HttpSession对象
		HttpSession session = request.getSession();
		// 判断session中是否有登录信息
		if (session.getAttribute("uid") == null) {
			// 没有登录信息,则重定向到登录页
			response.sendRedirect("/web/login.html");
			// 执行拦截
			return false;
		}
		// 放行
		return true;
	}

}

然后,需要对拦截器进行配置,在SpringBoot中,需要自定义配置类,以对拦截器进行配置:所以需要新创建一个cn.tedu.blogs.config.interceptor

@Configuration
public class InterceptorConfigurer implements WebMvcConfigurer {

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		// 创建拦截器对象
		HandlerInterceptor interceptor = new LoginInterceptor();
		
		// 白名单
		List<String> patterns = new ArrayList<>();
		patterns.add("/bootstrap3/**");
		patterns.add("/css/**");
		patterns.add("/js/**");
		patterns.add("/images/**");
		
		patterns.add("/web/register.html");
		patterns.add("/web/login.html");
		
		patterns.add("/users/reg");
		patterns.add("/users/login");
		
		// 注册拦截器
		registry.addInterceptor(interceptor)
			.addPathPatterns("/**")
			.excludePathPatterns(patterns);
	}
	
}

##################### 测试结果为5 #####################

浏览器在没有登录的情况下,不能进行其他操作。
只有列入白名单的网页才能访问
比如以下就是没有登录,就不能访问的页面:
http://localhost:8080/web/404.html
http://localhost:8080/web/address.html

注册,登录可以

15. 用户-修改密码-前端界面

-----------------------------------------

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

(a) 规划SQL语句

(b) 接口与抽象方法

© 配置映射

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

(a) 规划异常

(b) 接口与抽象方法

© 实现抽象方法

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

(a) 统一处理异常

(b) 设计请求

设计“xxx”的请求方式:

请求路径:/users/reg
请求参数:User user
请求方式:POST
响应数据:JsonResult<Void>

© 处理请求

15. 用户-修改密码-前端界面

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员西柚柚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值