mmall用户模块
- user数据表设计
- 用户模块接口文档
- 服务端响应对象(ServerResponse< T>)
- 响应对象封装以下3个属性
- 判断响应是否成功
- 私有化构造函数,对外暴露静态方法返回所需要的响应对象,例如:
- 响应成功
- 响应失败
- ResponseCode
- 本地缓存TokenCache
- Token 验证的优势
- 无状态,可扩展和解耦
- 登录、注册、修改信息、查看、检验、退出登录
- UserController
- IUserService实现类UserServiceImpl
- UserMapper
- UserMapper.xml
- 测试门户_用户接口
user数据表设计
CREATE TABLE `mmall_user` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户表id',`username` varchar(50) NOT NULL COMMENT '用户名',`password` varchar(50) NOT NULL COMMENT '用户密码,MD5加密',`email` varchar(50) DEFAULT NULL,`phone` varchar(20) DEFAULT NULL,`question` varchar(100) DEFAULT NULL COMMENT '找回密码问题',`answer` varchar(100) DEFAULT NULL COMMENT '找回密码答案',`role` int(4) NOT NULL COMMENT '角色0-管理员,1-普通用户',`create_time` datetime NOT NULL COMMENT '创建时间',`update_time` datetime NOT NULL COMMENT '最后一次更新时间',PRIMARY KEY (`id`),UNIQUE KEY `user_name_unique` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8
用户模块接口文档
门户_用户接口
服务端响应对象(ServerResponse< T>)
响应对象封装以下3个属性
private int status;private String msg;private T date;
判断响应是否成功
@JsonIgnore//添加此注解,isSuccess()序列化之后不会显示在Json中public boolean isSuccess(){return this.status == ResponseCode.SUCCESS.getCode();}
私有化构造函数,对外暴露静态方法返回所需要的响应对象,例如:
private ServerResponse(int status, T date) {this.status = status;this.date = date;}private ServerResponse(int status, String msg) {this.status = status;this.msg = msg;}
响应成功
public static <T> ServerResponse<T> createBySuccess(T data){return new ServerResponse<T>(ResponseCode.SUCCESS.getCode(), data);}public static <T> ServerResponse<T> createBySuccessMessage(String msg){return new ServerResponse<T>(ResponseCode.SUCCESS.getCode(), msg);}
响应失败
public static <T> ServerResponse<T> createByErrorMessage(String errorMessage){return new ServerResponse<T>(ResponseCode.ERROR.getCode(), errorMessage);}
ResponseCode
public enum ResponseCode {SUCCESS(0, "SUCCESSS"),ERROR(1, "ERROR"),NEED_LOGIN(10, "NEED_LOGIN"),ILLEGAL_ARGUMENT(2, "ILLEGAL_ARGUMENT");private final int code;private final String desc;ResponseCode(int code, String desc) {this.code = code;this.desc = desc;}public int getCode() {return code;}public String getDesc() {return desc;}
}
本地缓存TokenCache
public class TokenCache {private static Logger logger = LoggerFactory.getLogger(TokenCache.class);public static final String TOKEN_PREFIX = "token_";//Guava缓存private static LoadingCache<String, String> localCache = CacheBuilder.newBuilder().initialCapacity(1000).maximumSize(10000).expireAfterAccess(12, TimeUnit.HOURS).build(new CacheLoader<String, String>() {//默认的数据加载实现,当调用get取值的时候,如果key没有对应的值,就调用这个方法进行加载@Overridepublic String load(String s) throws Exception {return "null";}});public static void setKey(String key, String value) {localCache.put(key, value);}public static String getKey(String key) {String value = null;try {value = localCache.get(key);if ("null".equals(value)) {return null;}return value;} catch (ExecutionException e) {logger.error("localCache get error", e);}return null;}
}
Token 验证的优势
无状态,可扩展和解耦
使用 token 而不是 cookie 的最大优点应该就是无状态,后端不需要保持对 token 的记录,每个 token 都是独立的
例如:用户忘记密码显示提示问题,在输入提示答案的时候校验正确,UUID随机生成的forgetToken(为了token复杂度可以+userID+时间戳),存入到TokenCache中
@Overridepublic ServerResponse<String> checkAnswer(String username, String question, String answer) {int resultCount = userMapper.checkAnswer(username, question, answer);if (resultCount > 0) {//说明问题及问题答案是这个用户的,并且是正确的String forgetToken = UUID.randomUUID().toString();TokenCache.setKey(TokenCache.TOKEN_PREFIX + username, forgetToken);return ServerResponse.createBySuccess(forgetToken);}return ServerResponse.createByErrorMessage("问题的答案错误");}
紧接着,忘记密码状态下重置密码,需要传递token
@Overridepublic ServerResponse<String> forgetResetPassword(String username, String passwordNew, String forgetToken) {if (StringUtils.isBlank(forgetToken)) {return ServerResponse.createByErrorMessage("参数错误,token需要传递");}ServerResponse validResponse = this.checkValid(username, Const.USERNAME);if(validResponse.isSuccess()) {//用户不存在return ServerResponse.createByErrorMessage("用户不存在");}String token = TokenCache.getKey(TokenCache.TOKEN_PREFIX + username);if (StringUtils.isBlank(token)) {return ServerResponse.createByErrorMessage("token无效或者过期");}if (StringUtils.equals(forgetToken,token)) {String md5Password = MD5Util.MD5EncodeUtf8(passwordNew);int rowCount = userMapper.updatePasswordByUsername(username, md5Password);if (rowCount > 0) {return ServerResponse.createBySuccessMessage("修改密码成功");}} else {return ServerResponse.createByErrorMessage("token错误,请重新获取重置密码的token");}return ServerResponse.createByErrorMessage("修改密码失败");}
总结:为了用户信息的安全性,忘记密码重置密码的情况下,需要客户端传递token进行验证
登录、注册、修改信息、查看、检验、退出登录
以更新用户信息为例:
UserController
@RequestMapping(value = "update_information.do", method = RequestMethod.POST)@ResponseBodypublic ServerResponse<User> updateInformation(HttpSession session, User user) {User currentUser = (User) session.getAttribute(Const.CURRENT_USER);if (currentUser == null) {return ServerResponse.createByErrorMessage("用户未登陆");}user.setId(currentUser.getId());user.setUsername(currentUser.getUsername());ServerResponse<User> response = iUserService.updateInformation(user);if (response.isSuccess()) {session.setAttribute(Const.CURRENT_USER, response.getDate());}return response;}
IUserService实现类UserServiceImpl
@Overridepublic ServerResponse<User> updateInformation(User user) {//username是不能被更新的//email校验:校验新的email是不是已经存在,如果存在的话,必须得是当前用户的int resultCount = userMapper.checkEmailByUserId(user.getEmail(), user.getId());if (resultCount > 0) {return ServerResponse.createByErrorMessage("email已经存在,请更换email再尝试更新");}User updateUser = new User();updateUser.setId(user.getId());updateUser.setEmail(user.getEmail());updateUser.setPhone(user.getPhone());updateUser.setQuestion(user.getQuestion());updateUser.setAnswer(user.getAnswer());int updateCount = userMapper.updateByPrimaryKeySelective(updateUser);if (updateCount > 0) {return ServerResponse.createBySuccess("更新个人信息成功", updateUser);}return ServerResponse.createByErrorMessage("更新个人信息失败");}
UserMapper
int updateByPrimaryKeySelective(User record);
UserMapper.xml
<update id="updateByPrimaryKeySelective" parameterType="com.mmall.pojo.User" >update mmall_user<set ><if test="username != null" >username = #{username,jdbcType=VARCHAR},</if><if test="password != null" >password = #{password,jdbcType=VARCHAR},</if><if test="email != null" >email = #{email,jdbcType=VARCHAR},</if><if test="phone != null" >phone = #{phone,jdbcType=VARCHAR},</if><if test="question != null" >question = #{question,jdbcType=VARCHAR},</if><if test="answer != null" >answer = #{answer,jdbcType=VARCHAR},</if><if test="role != null" >role = #{role,jdbcType=INTEGER},</if><if test="createTime != null" >create_time = #{createTime,jdbcType=TIMESTAMP},</if><if test="updateTime != null" >update_time = now(),</if></set>where id = #{id,jdbcType=INTEGER}</update>
测试门户_用户接口















