本文共 12473 字,大约阅读时间需要 41 分钟。
当需要开发某个项目时,首先,应该分析这个项目中,需要处理哪些种类的数据!例如:用户、商品、商品类别、收藏、订单、购物车、收货地址…
然后,将以上这些种类的数据的处理排个顺序,即先处理哪种数据,后处理哪种数据!通常,应该先处理基础数据,再处理所相关的数据,例如需要先处理商品数据,才可以处理订单数据,如果多种数据之间没有明显的关联,则应该先处理简单的,再处理较难的!
则以上这些数据的处理顺序应该是:用户 > 收货地址 > 商品类别 > 商品 > 收藏 > 购物车 > 订单
当确定了数据处理顺序后,就应该分析某个用户对应的功能有哪些,以“用户”数据为例,相关功能有:注册、登录、修改密码、修改资料、上传头像…
然后,还是需要确定以上功能的开发顺序,通常,遵循“增 > 查 > 删 > 改”的顺序,则以上功能的开发顺序应该是:注册 > 登录 > 修改密码 > 修改资料 > 上传头像。
每个功能的开发都应该遵循 创建数据表 > 创建实体类 > 持久层 > 业务层 > 控制器层 > 前端页面
一次只解决一个问题
大问题拆成小问题a.规划SQL语句
b.接口与抽象方法
c.配置映射
业务层的基本定位
a.规划异常
b.接口与抽象方法
c.实现类与重写方法
a.处理异常
b.设计请求
c.处理请求
a.规划SQL语句
登录验证的做法应该是:根据用户名查询数据是否存在,如果存在,则取出必要的数据,例如密码,然后,在Java程序中验证密码即可。
如果用户名匹配的数据是存在的,需要取出的数据有:密码,盐,是否标记为删除,uid,用户名。对应的SQL语句大致是:
SELECT uid,username, password,salt, is_deleteFROM t_user WHERE username=?
b.接口与抽象方法
在接口中已经存在findByUsername()
方法,则无须重复添加。
c.配置映射
只需在原有的findByUsername()
方法映射的SQL语句中,添加查询更多的字段即可!
然后执行单元测试
a.规划异常
规划异常,应该是列举此次操作中可能存在的操作失败,包括用户提交不合理甚至错误的数据,或不符合逻辑的数据,都是失败的!
在“登录”时,用户提交的用户名可能是未被注册的,即不存在的,对于这种情况,应该抛出对应的异常:UserNotFoundException
;
也可能查询到了用户名匹配的数据,但是,是被标记为删除的,这种用户数据也是不允许登录的,也应该抛出异常:UserNotFoundException
;
在验证密码时,还可能出现密码不匹配的问题,也是不允许登录的,则抛出异常:PasswordNotMatchException
。
则需要创建cn.tedu.store.service.ex.UserNotFoundException
和PasswordNotMatchException
,它们都是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()); } }
a.处理异常
此次业务层抛出了新的异常,则需在BaseController
的处理异常的方法中,添加更多的分支,对这些新的异常进行处理。
/** * 控制器类的基类,实现统一处理异常 */public abstract class BaseController { /** * 操作结果的“成功”状态 */ public static final Integer SUCCESS = 2000; // 只处理ServiceException及其子孙类异常,避免异常过度处理 @ExceptionHandler(ServiceException.class) public JsonResulthandleException(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 JsonResultlogin(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
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); }
注:用于测试的数据,在测试完成之后,需要将这些数据删除。
a.规划异常
此次更新密码之前,需要检查用户数据是否正常,如:用户数据是否存在、用户数据是否被标记为已删除,则可能抛出:UserNotFoundException
;
在执行更新之前还需验证原密码是否正确,则可能抛出:PasswordNotMatchException
;
在执行更新过程中,也可能出现更新失败,返回的受影响行数不符合预期值,则可能抛出:UpdateException
。
所以需要创建:cn.tedu.store.service.ex.UpdateException
:
/** * 更新数据异常 * @author DELL * */ public class UpdateException extends ServiceException { // 序列化接口 // 五个构造方法}
b.接口与抽象方法
在IUserService
中添加抽象方法:
业务层抽象方法设计原则:
/** * 修改密码 * @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()); } }
a.处理异常
此次业务层抛出了新的异常:UpdateException
,则需要在BaseController
中进行处理!
b.设计请求
请求路径:/users/change_password请求参数:String oldPassword, String newPassword, HttpSession session请求方式:POST响应数据:JsonResult//绝大部分情况下,JsonResult泛型的类型与业务层的抽象方法的返回值类型一致
c.处理请求
// 设计为RequestMapping是为了方便在地址栏中进行测试 @RequestMapping("change_password") public JsonResultchangePassword( @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
进行测试
转载地址:http://arzs.baihongyu.com/