Bootstrap

Project(4)——用户修改个人资料

Project(4)

1、分析项目

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

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

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

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

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

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

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

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

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

4、用户 - 注册 - 持久层

a.规划SQL语句

b.接口与抽象方法

c.配置映射

5、用户 - 注册 - 业务层

业务层的基本定位

a.规划异常

b.接口与抽象方法

c.实现类与重写方法

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

a.处理异常

b.设计请求

c.处理请求

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

8、用户 - 登录 - 持久层

a.规划SQL语句

b.接口与抽象方法

c.配置映射

9、用户 - 登录 - 业务层

a.规划异常

b.接口与抽象方法

c.实现类与重写方法

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

a.处理异常

b.设计请求

c.处理请求

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

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

a.规划SQL语句

b.接口与抽象方法

c.配置映射

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

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

a.规划异常

b.接口与抽象方法

c.实现类与重写方法

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

a.处理异常

b.设计请求

c.处理请求

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

16、用户 - 登陆拦截器

后续会有越来越多的操作需要先登录才能进行,为了保证统一处理则应该在项目中创建cn.tedu.store.interceptor.LoginInterceptor拦截器类,实现HandlerInterceptor接口并重写preHandle()抽象方法:

/**
 * 登录拦截器
 */
public class LoginInterceptor implements HandlerInterceptor {

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		// 验证用户是否登录?登录--放行;未登录 -- 重定向到登录界面
		// 获取session对象
		HttpSession session = request.getSession();
		// 根据uid判断是否登录,因为在登录成功时,会在session中存入uid
		if(session.getAttribute("uid") == null) {
			// 没有uid,重定向
			response.sendRedirect("/web/login.html");
			// 拦截
			return false;
		}
		return true;
	}
	
}

然后,还需要配置拦截器,在SpringBoot项目中,需要使用自定义类进行配置,则需要创建cn.tedu.store.conf.LoginInterceptorConfigurer类,实现WebMvcConfigurer接口并重写addInterceptors(InterceptorRegistry registry)抽象方法,且在类前添加@Configuration注解:

/**
 * 登录拦截器的配置类
 * @author DELL
 *
 */
@Configuration
public class LoginInterceptorConfigurer implements WebMvcConfigurer {

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

17、用户 - 修改个人资料 - 持久层

a.规划SQL语句

修改个人资料的SQL语句大致是:

UPDATE 
    t_user 
SET 
    phone=?, email=?, gender=?,
    modified_user=?, modified_time=? 
WHERE 
    uid=?

在执行修改之前,还应该检查用户的数据是否正确,例如:用户数据是否存在、是否被标记为已删除。检查所需的功能已经完成,无需重复开发。

另外,“修改个人资料”和“修改密码”不同,首先需要完成界面的显示!即用户打开界面时,应该显示当前登录的用户的相关信息!则需要:


SELECT
    username, phone,
    email, gender 
 FROM
     t_user
 WHERE
     uid=?

根据uid查询数据的持久层功能已经存在,后续在开发时,只需要补充查询的字段列表即可。

b.接口与抽象方法

UserMapper接口添加新的抽象方法:

    /**
	 * 修改个人信息
	 * @param user 用户提交的信息
	 * @return 受影响的行数
	 */
    Integer updateInfo(User user);

c.配置映射

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

    <!-- 修改个人信息 -->
    <!-- Integer updateInfo(User user); -->
    <update id="updateInfo">
        UPDATE
            t_user
        SET
            <if test="phone != null">
            phone=#{phone},
            </if>
            <if test="email != null">
            email=#{email},
            </if>
            <if test="gender != null">
            gender=#{gender}, 
            </if>
            modified_user=#{modifiedUser}, 
            modified_time=#{modifiedTime}
        WHERE
            uid=#{uid}
    </update>

修改原来的findByUid的配制,查询更多的字段:

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

完成后,编写并执行单元测试:

    @Test
	public void updateInfo() {
		Date now = new Date();
		Date modifiedTime = now;
		User user = new User();
		user.setUid(16);
		user.setPhone("15934460492");
		user.setEmail("[email protected]");
		user.setGender(0);
		user.setModifiedUser("管理员");
		user.setModifiedTime(modifiedTime);
		Integer rows = userMapper.updateInfo(user);
		System.err.println("rows = " + rows);
	}

18、用户 - 修改个人资料 - 业务层

a.规划异常

此次客户端涉及的操作有两个:查询当前登录的用户信息,执行修改用户的基本资料。

首先,由于是登录后才能进行修改个人资料操作,所以查询功能是不存在错误的。

则关于修改功能,可能涉及的异常是:UserNotFoundExceptionUpdateException

b.接口与抽象方法

IUserService中添加两个抽象方法:

    /**
	 * 根据用户id查询用户信息
	 * @param uid 用户id
	 * @return 返回匹配的用户数据,如果没有则返回null
	 */
	User getByUid(Integer uid);
    /**
	 * 修改用户的基本资料
	 * @param user 封装了用户基本资料的对象,至少需要封装用户的id和用户名
	 *             可选择性的封装用户的手机号码、电子邮箱、年龄等。
	 * @throws UserNotFoundException 尝试访问的用户数据不存在,或被标记为已删除
	 * @throws UpdateException 更新失败异常
	 */
    void changeInfo(User user) throws UserNotFoundException, UpdateException;

c.实现类与重写方法

UserServiceImpl中重写以上2个抽象方法:

public User getByUid(Integer uid){
    // 根据uid查询用户数据
    // 如果查询到数据,则需要将查询结果中的password、salt、is_delete设置为null
    // 将查询结果返回
}

代码实现:

    @Override
	public User getByUid(Integer uid) {
		// 根据uid查询用户数据
		// 如果查询到数据,则需要将查询结果中的password、salt、is_delete设置为null
		// 将查询结果返回
		User result = userMapper.findByUid(uid);
		if (result != null) {
			result.setPassword(null);
			result.setSalt(null);
			result.setIsDelete(null);
		}
		return result;
	}
public Integer changeInfo(User user) throws UserNotFoundException, UpdateException{
    // 根据参数中的uid,即user.getUid()查询数据
    // 判断查询结果是否存在,查询结果是否被标记为已删除
    
    // 创建当前时间对象
    // 将时间对象和修改人封装到user中
    // 执行修改操作:userMapper.updateInfo() > UPDATE t_user SET phone=?, email=?, gender=?, modified_user=?, modified_time=? WHERE uid=?
    // 判断修改操作的返回值是否不为1
    // 抛出UpdateException
}

代码实现:

    @Override
	public void changeInfo(User user) throws UserNotFoundException, UpdateException {
		// 根据参数的uid,即user.getUid()查询数据
		// 判断查询结果是否存在,是否被标记为已删除
		User result = userMapper.findByUid(user.getUid());
		if (result == null) {
			throw new UserNotFoundException("更新用户数据失败!该用户不存在!");
		}
		if (result.getIsDelete() == 1) {
			throw new UserNotFoundException("更新用户数据失败!该用户不存在!");
		}

		// 创建当前时间对象
		Date now = new Date();
		// 将时间封装到user中
		user.setModifiedTime(now);
		// 将user中的用户名封装到modifiedUser中
		user.setModifiedUser(user.getUsername());
		// 执行修改操作:userMapper.updateInfo() > UPDATE t_user SET
		// phone=?,email=?,gender=?,modified_user=?,modified_time=? WHERE uid=?
		Integer rows = userMapper.updateInfo(user);
		// 判断以上修改操作的返回值是否不为1
		// 是则:抛出异常:UpdateException
		if (rows != 1) {
			throw new UpdateException("更新用户数据失败!出现未知错误!请联系系统管理员!");
		}
	}

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

    @Test
	public void testGetByUid() {
		User user = service.getByUid(17);
		System.err.println(user);
	}
	
	@Test
	public void testChangerInfo() {
		try {
			User user = new User();
			user.setUid(17);
			user.setUsername("Gin");
			user.setPhone("1593446049Gin");
			user.setEmail("[email protected]");
			user.setGender(1);
			service.changeInfo(user);
			System.err.println("OK");
		}catch(ServiceException e) {
			System.err.println(e.getClass().getName());
			System.err.println(e.getMessage());
		}
	}

19、用户 - 修改个人资料 - 控制器层

a.处理异常

b.设计请求

关于获取当前登录的用户的信息的请求:

请求路径:/users/get_info
请求参数:HttpSession session
请求方式:GET
响应数据:JsonResult<User>



关于执行修改个人信息的请求:

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

c.处理请求


//modified_ser、uid的来源应该是:session中向参数user封装

    @GetMapping("get_info")
	public JsonResult<User> getInfo(HttpSession session) {
		// 从session中获取uid
		Integer uid = Integer.valueOf(session.getAttribute("uid").toString());
		// 查询匹配的数据
		User data = userService.getByUid(uid);
		// 响应
		return new JsonResult<User>(SUCCESS, data);
	}

	//使用RequestMapping是为了方便在地址栏进行测试
	@RequestMapping("change_info")
	public JsonResult<Void> changeInfo(User user, HttpSession session) {
		// 从session中获取uid和username
		Integer uid = Integer.valueOf(session.getAttribute("uid").toString());
		String username = session.getAttribute("username").toString();
		// 将uid和username封装到user中
		user.setUid(uid);
		user.setUsername(username);
		// 执行修改
		userService.changeInfo(user);
		// 响应
		return new JsonResult<Void>(SUCCESS);
	}

优化:

以上代码中,需要频繁从session中获取uid,username,则可以在父类BaseController中添加新方法:
BaseController

    /**
	 * 从session中获取uid
	 * @param session
	 * @return session中的uid
	 * 添加 final 表示该方法不可以被重写
	 */
	protected final Integer getUidFromSession(HttpSession session) {
		return Integer.valueOf(session.getAttribute("uid").toString());
	}
	
	/**
	 * 从session中获取username
	 * @param session
	 * @return session中的username
	 */
	protected final String getUsernameFromSession(HttpSession session) {
		return session.getAttribute("username").toString();
	}

UserController

        // 从session中获取uid和username
		Integer uid = getUidFromSession(session);
		String username = getUsernameFromSession(session);

打开浏览器,先登录,分别通过http://localhost:8080/users/get_infohttp://localhost:8080/users/[email protected]&phone=13100131007&gender=1进行测试。

20、用户 - 修改个人资料 - 前端页面

<script type="text/javascript">
		$(document).ready(function() {
			showUserInfo();
			$("#btn-change-info").click(function() {
				$.ajax({
					// url:          请求交到哪里去
					// data:        请求提交的参数,以上的form表单中,在需要提交的参数的控件中添加name属性;
					//            form表单上添加id属性
					// type:        请求方式
					// dataType: 服务器端响应的结果的类型
					// success:   响应成功时的处理函数
					"url" : "/users/change_info",
					"data" : $("#form-change-info").serialize(),
					"type" : "post",
					"dataType" : "json",
					"success" : function(json) {
						if (json.state == 2000) {
							alert("修改成功!");
							//跳转到某个页面
						} else {
							alert(json.message);
						}
					},
					"error" : function() {
						alert("您的登录已过期!请重新登录!");
						// location.href = "/web/login.html";
					}
				});
			});
			function showUserInfo() {
				$.ajax({
					// url:          请求交到哪里去
					// data:        请求提交的参数,以上的form表单中,在需要提交的参数的控件中添加name属性;
					//            form表单上添加id属性
					// type:        请求方式
					// dataType: 服务器端响应的结果的类型
					// success:   响应成功时的处理函数
					"url" : "/users/get_info",
					"type" : "get",
					"dataType" : "json",
					"success" : function(json) {
						if (json.state == 2000) {
						    // 给用户名、电话、邮箱输入框添加id属性
							$("#inp-username").val(json.data.username);
							$("#inp-phone").val(json.data.phone);
							$("#inp-email").val(json.data.email);
							
							// 给性别输入框添加id属性
							// if (json.data.gender == 1) {
							//	 $("#gender-male").attr("checked", "checked");
							// } else {
							//	 $("#gender-female").attr("checked", "checked");
							// }
							
							// 修改性别输入框中的value值为1、0
							var radio = json.data.gender == 1 ? $("#gender-male") : $("#gender-female");
							radio.attr("checked", "checked");
							
						} else {
							alert(json.message);
						}
					}
				});
			}
		});
	</script>
;