【实战】5-2 用户登录相关功能开发

前言

接下来就要正式开始往之前搭建的结构中填充代码了,这些代码也不是一次完成的,我要纠结一下怎么来把这个学习过程明白的描写出来。可能有些类增加了多少就把那一部分写出来吧, 我大部分的上课笔记还是会写在代码的注释中,这样方便了记笔记,却让读者的思路顺序有些乱了,尽量表达的清楚一点吧~

以这种形式把上课学习内容写出来还真是有些别扭,以后可能我就只能挑着有意义的部分写了。

(20171025结束了不少校招面试,回来补项目经历,哈哈)最后我是把用户相关操作都做完了才来把这篇博客补全,没有更多的精力来把增量内容一点一点写出来了。这里不得不说一下,这里的MD5加密部分不严密,大家看看就行,不要学这里,我以后自己也要修改这一部分。

高可用服务响应对象

还是先贴代码,在common包下建立,同时还新建了ResponseCode枚举类,把一些状态码的列举放进去。
这样不管是底层service在数据库中查询出什么结果,都可以通过这个通用的对象返回,包括状态码status,一些提示消息msg,还有数据对象。这个对象返回给controller层后,也会修改或不修改的返回前端(浏览器)。

package top.winxblast.happymall.common;

import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.map.annotate.JsonSerialize;

import java.io.Serializable;

/**
 * 高可用服务响应对象
 * JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)意思是
 * 值为null时不要序列化,因为在生成错误对象时没有data,这时候就不要把data
 * 序列化进json了(有时候msg也为null)(保证序列化json的时候,如果是null的
 * 对象,key也会消失)
 *
 * @author winxblast
 * @create 2017/10/19
 **/
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
public class ServerResponse<T> implements Serializable {

    //这些都是在5-1接口设计中返回内容提到过的
    /**
     * 状态码
     */
    private int status;
    /**
     * 返回的消息
     */
    private String msg;
    /**
     * 返回数据,可以是一些类的json格式
     * 泛型的好处:这样在返回的时候可以指定泛型里面的内容,也可以不指定泛型里面的强制类型
     */
    private T data;

    //私有构造器,这里老师提到一种问题,我的理解就是泛型T万一是String类型
    //那么构造方法会选择2,3中的哪一个?这个问题需要在后面public方法中考虑
    //所以下面公开的方法要区分有message和data,不过调用构造方法是不会错的了
    private ServerResponse(int status) {
        this.status = status;
    }
    private ServerResponse(int status, T date) {
        this.status = status;
        this.data = date;
    }
    private ServerResponse(int status, String msg) {
        this.status = status;
        this.msg = msg;
    }
    private ServerResponse(int status, String msg, T data) {
        this.status = status;
        this.msg = msg;
        this.data = data;
    }


    /**
     * 根据状态码返回是否成功
     * JsonIgnore使被注解内容不在json序列化结果当中
     * 大概看了一下,JsonSerialize是通过getter方法来获取要序列化的内容的
     * 所以哪个内容不想被包含,就加上jasonignore注解
     * @return
     */
    @JsonIgnore
    public boolean isSuccess() {
        return this.status == ResponseCode.SUCCESS.getCode();
    }

    public int getStatus() {
        return status;
    }
    public String getMsg() {
        return msg;
    }
    public T getData() {
        return data;
    }


    /**
     * 通过该公开方法直接获取一个成功状态码的本对象
     * @param <T>
     * @return
     */
    public static <T> ServerResponse<T> createBySuccess() {
        return new ServerResponse<T>(ResponseCode.SUCCESS.getCode());
    }

    public static <T> ServerResponse<T> createBySuccessMessage(String msg) {
        return new ServerResponse<T>(ResponseCode.SUCCESS.getCode(), msg);
    }

    public static <T> ServerResponse<T> createBySuccess(T data) {
        return new ServerResponse<T>(ResponseCode.SUCCESS.getCode(), data);
    }

    public static <T> ServerResponse<T> createBySuccess(String msg, T data) {
        return new ServerResponse<T>(ResponseCode.SUCCESS.getCode(), msg, data);
    }


    /**
     * 获取错误对象
     * @param <T>
     * @return
     */
    public static <T> ServerResponse<T> createByError() {
        return new ServerResponse<T>(ResponseCode.ERROR.getCode(), ResponseCode.ERROR.getDesc());
    }

    public static <T> ServerResponse<T> createByErrorMessage(String errorMessage) {
        return new ServerResponse<T>(ResponseCode.ERROR.getCode(), errorMessage);
    }

    public static <T> ServerResponse<T> createByErrorCodeMessage(int errorCode, String errorMessage) {
        return new ServerResponse<T>(errorCode, errorMessage);
    }
}

响应编码的枚举类,与ServerResponse类配合使用

package top.winxblast.happymall.common;

/**
 * 响应编码的枚举类
 *
 * @author winxblast
 * @create 2017/10/19
 **/
public enum ResponseCode {

    //以后想要扩展的时候修改下面这一部分就行了
    SUCCESS(0,"SUCCESS"),
    ERROR(1,"ERROR"),
    NEED_LOGIN(10,"NEED_LOGIN"),
    ILLEGAL_ARGUMENT(2,"ILLEGAL_ARGUMENT");

    private final int code;
    private final String desc;

    //这里使用default的修饰,只允许类内部及本包调用,我不能很好理解这样的好处
    ResponseCode(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public int getCode() {
        return code;
    }
    public String getDesc() {
        return desc;
    }
}

用户登录

在controller包下创建一个portal包,这个是门户的意思,是给前端用的,这里新建UserController类。可以看到这里的@RequestMapping注解是和上一节定义的用户接口相对应的。

package top.winxblast.happymall.controller.portal;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import top.winxblast.happymall.common.Const;
import top.winxblast.happymall.common.ResponseCode;
import top.winxblast.happymall.common.ServerResponse;
import top.winxblast.happymall.pojo.User;
import top.winxblast.happymall.service.IUserService;

import javax.servlet.http.HttpSession;

/**
 * 前台用户接口设计的控制层
 *
 * @author winxblast
 * @create 2017/10/19
 **/
@Controller
//因为都要给到这个地址下,所以放在类前面,方法前面放更细的地址
@RequestMapping("/user/")
public class UserController {

    @Autowired
    private IUserService iUserService;

    /**
     * 用户登录
     * @param username
     * @param password
     * @param session
     * @return
     */
    //这里login.do的设计要与之前的用户接口定义相同,method指定请求的方式
    @RequestMapping(value = "login.do",method = RequestMethod.POST)
    //responsebody注解表示返回时自动使用SpringMVC Jackson插件将返回值序列化为json
    //它的配置在dispatcher-servlet.xml中
    @ResponseBody
    public ServerResponse<User> login(String username, String password, HttpSession session) {
        //service-->mybatis-->dao
        ServerResponse<User> response = iUserService.login(username,password);
        if(response.isSuccess()) {
            session.setAttribute(Const.CURRENT_USER, response.getData());
        }
        return response;
    }

    /**
     * 用户登出
     * @param session
     * @return
     */
    //这里method也就只使用了GET方法,登出比较简单
    @RequestMapping(value = "logout.do", method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<String> logout(HttpSession session) {
        //登出就直接在session中把当前用户删除即可
        session.removeAttribute(Const.CURRENT_USER);
        return ServerResponse.createBySuccess();
    }

    /**
     * 用户注册
     * @param user
     * @return
     */
    @RequestMapping(value = "register.do", method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<String> register(User user) {
        return iUserService.register(user);
    }

    /**
     * 检查用户名,email是否存在
     * 虽然注册时已经含有这个检查了(注册中的检查是为了防止恶意调用注册接口)
     * 这里的检查是为了返回前端一个检查结果,这样好实时显示
     * @param str
     * @param type 通过type是email还是username,去判断str
     * @return
     */
    @RequestMapping(value = "check_valid.do", method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<String> checkValid(String str, String type) {
        return iUserService.checkValid(str, type);
    }

    /**
     * 获取用户信息
     * @param session
     * @return
     */
    @RequestMapping(value = "get_user_info.do", method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<User> getUserInfo(HttpSession session) {
        User user = (User)session.getAttribute(Const.CURRENT_USER);
        if(user != null) {
            return ServerResponse.createBySuccess(user);
        }
        return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户信息");
    }

    /**
     * 忘记密码,获得密码提示问题
     * @param username
     * @return
     */
    @RequestMapping(value = "forget_get_question.do", method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<String> forgetGetQuestion(String username) {
        return iUserService.selectQuestion(username);
    }

    /**
     * 验证密码提示问题的答案,token要放到ServerResponse的泛型T中
     * 在写service时就会用到guava,先用本地的guava缓存来做token,利用
     * 缓存的有效期来搞定token的有效期
     * @param username
     * @param question
     * @param answer
     * @return
     */
    @RequestMapping(value = "forget_check_answer.do", method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<String> forgetCheckAnswer(String username, String question, String answer) {
        return iUserService.checkAnswer(username,question,answer);
    }


    /**
     * 重置密码
     * 现在更加能理解为什么要使用token的理由了,原来一直天真的以为验证答案正确
     * 直接进行修改不是一连串的动作么,现在发现不是这样,修改密码是通过另外一个
     * 接口的,那么破坏者就能利用那个没有验证答案的接口直接修改密码了
     * @param username
     * @param passwordNew
     * @param forgetToken
     * @return
     */
    @RequestMapping(value = "forget_reset_password.do", method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<String> forgetResetPassword(String username, String passwordNew, String forgetToken) {
        return iUserService.forgetResetPassword(username, passwordNew, forgetToken);
    }

    /**
     * 登录状态下重置密码
     * @param session
     * @param passwordOld
     * @param passwordNew
     * @return
     */
    @RequestMapping(value = "reset_password.do", method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<String> resetPassword(HttpSession session, String passwordOld, String passwordNew) {
        User user = (User)session.getAttribute(Const.CURRENT_USER);
        if(user == null) {
            return ServerResponse.createByErrorMessage("用户未登录");
        }

        return iUserService.resetPassword(user, passwordOld, passwordNew);
    }

    /**
     * 更新用户信息
     * @param session
     * @param user
     * @return 这里消息的泛型选择User,这样把新的用户信息更新到session中,返回前端后,前端也要把新信息直接更新
     */
    @RequestMapping(value = "update_information.do", method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<User> updateInformation(HttpSession session, User user) {
        User currentUser = (User) session.getAttribute(Const.CURRENT_USER);
        if(currentUser == null) {
            return ServerResponse.createByErrorMessage("用户未登录");
        }
        //因为user放的更新信息,里面没有用户的id,所以要先放进去,这里也是为了防止越权
        user.setId(currentUser.getId());
        user.setUsername(currentUser.getUsername());

        ServerResponse<User> response = iUserService.updateInformation(user);
        if(response.isSuccess()) {
            //成功则要更新session
            session.setAttribute(Const.CURRENT_USER, response.getData());
        }
        //最后不管成功失败,直接返回就行
        return response;
    }

    /**
     * 获取用户信息
     * 调用这个方法如果发现没有登录要强制登录
     * @param session
     * @return
     */
    @RequestMapping(value = "get_information.do", method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<User> getInformation(HttpSession session) {
        User currentUser = (User) session.getAttribute(Const.CURRENT_USER);
        if(currentUser == null) {
            return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), "未登录,需要强制登录status=10");
        }
        return iUserService.getInformation(currentUser.getId());
    }

}

同时也在common中新建了一个常量类,这里暂时只有一点内容,以后慢慢扩展。

package top.winxblast.happymall.common;

/**
 * 常量类
 *
 * @author winxblast
 * @create 2017/10/20
 **/
public class Const {

    public static final String CURRENT_USER = "currentUser";

    public static final String EMAIL = "email";
    public static final String USERNAME = "username";

     /**
     * 这个分组如果使用枚举类可能显得比较重,这里用一个接口
     * 接口不能包含实例域或静态方法,但可以包含常量
     * 接口中的域将被自动设为 public static final
     */
    public interface Role{
        int ROLE_CUSTOMER = 0; //普通用户
        int ROLE_ADMIN = 1; //管理员
    }
}

用户service接口

对于上一小节注入controller的IUserService,这里给出接口的定义和实现类。controller调用service的接口,service接口实现类里面调用dao的接口进行数据库操作。

这里就是一个典型的先定义接口再实现类的实践,当然我自己还没能体会到这种方式的好处···

在service包下创建IUserService.java,这里接口名字还要添加大写“I”的习惯可能有些过时了,先按照老师的习惯写吧···

package top.winxblast.happymall.service;

import top.winxblast.happymall.common.ServerResponse;
import top.winxblast.happymall.pojo.User;

/**
 * 前台用户接口设计
 * 使用接口及接口实现,为以后的AOP做准备;无论在用静态代理还是动态代理包括后续发展
 * 成的AOP,我们都用接口代理。类的代理扩展性没有接口强
 *
 * @author winxblast
 * @create 2017/10/19
 **/
public interface IUserService {

    ServerResponse<User> login(String username, String password);

    ServerResponse<String> register(User user);

    ServerResponse<String> checkValid(String str, String type);

    ServerResponse<String> selectQuestion(String username);

    ServerResponse<String> checkAnswer(String username, String question, String answer);

    ServerResponse<String> forgetResetPassword(String username, String passwordNew, String forgetToken);

    ServerResponse<String> resetPassword(User user, String passwordOld, String passwordNew);

    ServerResponse<User> updateInformation(User user);

    ServerResponse<User> getInformation(Integer userId);

}

在service包下创建impl包,再创建UserServiceImpl.java

package top.winxblast.happymall.service.impl;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import top.winxblast.happymall.common.Const;
import top.winxblast.happymall.common.ServerResponse;
import top.winxblast.happymall.common.TokenCache;
import top.winxblast.happymall.dao.UserMapper;
import top.winxblast.happymall.pojo.User;
import top.winxblast.happymall.service.IUserService;
import top.winxblast.happymall.util.MD5Util;

import java.util.UUID;

/**
 * 前台用户接口实现
 *
 * @author winxblast
 * @create 2017/10/19
 **/
//注意这个service的注解,名字改为接口的首字母小写,后续注入controller也是用这个名字
@Service("iUserService")
public class UserServiceImpl implements IUserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public ServerResponse<User> login(String username, String password) {
        int resultCount = userMapper.checkUsername(username);
        if(resultCount == 0) {
            return ServerResponse.createByErrorMessage("用户名不存在");
        }

        //密码登录MD5
        String md5Password = MD5Util.MD5EncodeUtf8(password);
        User user = userMapper.selectLogin(username, md5Password);
        if(user == null) {
            //因为上面已经判断过有没有用户名了,所以这里就是密码错误
            return ServerResponse.createByErrorMessage("密码错误");
        }

        //如果到这都没有return,则就是登录成功
        user.setPassword(StringUtils.EMPTY);
        return ServerResponse.createBySuccess("登录成功", user);

    }


    @Override
    public ServerResponse<String> register(User user) {
//        int resultCount = userMapper.checkUsername(user.getUsername());
//        if(resultCount > 0) {
//            return ServerResponse.createByErrorMessage("用户名已存在");
//        }
//
//        resultCount = userMapper.checkEmail(user.getEmail());
//        if(resultCount > 0) {
//            return ServerResponse.createByErrorMessage("email已存在");
//        }
        //根据下面的checkValid方法改造这段内容
        ServerResponse validResponse = this.checkValid(user.getUsername(), Const.USERNAME);
        if(!validResponse.isSuccess()) {
            return validResponse;
        }
        validResponse = this.checkValid(user.getEmail(), Const.EMAIL);
        if(!validResponse.isSuccess()) {
            return validResponse;
        }

        user.setRole(Const.Role.ROLE_CUSTOMER);

        //MD5加密,数据库中不能存储明文,这里的安全知识还涉及重放攻击等,这里是比较简单的设计
        user.setPassword(MD5Util.MD5EncodeUtf8(user.getPassword()));

        //mybatis的insert、delete、update默认返回操作的条数
        int resultCount = userMapper.insert(user);
        if(resultCount == 0) {
            return ServerResponse.createByErrorMessage("注册失败");
        }

        return ServerResponse.createBySuccessMessage("注册成功");
    }

    /**
     * 通过type是email还是username去调用不同的sql判断
     * @param str
     * @param type 同样也把type的值设置常量
     * @return
     */
    @Override
    public ServerResponse<String> checkValid(String str, String type) {
        //注意isNotBlank和isNotEmpty的区别,请看源码
        if(StringUtils.isNotBlank(type)) {
            //type不空才开始校验
            if(Const.USERNAME.equals(type)) {
                int resultCount = userMapper.checkUsername(str);
                if(resultCount > 0) {
                    return ServerResponse.createByErrorMessage("用户名已存在");
                }
            }
            if(Const.EMAIL.equals(type)) {
                int resultCount = userMapper.checkEmail(str);
                if(resultCount > 0) {
                    return ServerResponse.createByErrorMessage("email已存在");
                }
            }

        } else {
            return ServerResponse.createByErrorMessage("参数错误");
        }
        return ServerResponse.createBySuccessMessage("校验成功");
    }

    /**
     * 通过用户名获取密码找回问题
     * @param username
     * @return
     */
    @Override
    public ServerResponse<String> selectQuestion(String username) {
        ServerResponse validResponse = this.checkValid(username, Const.USERNAME);
        if(validResponse.isSuccess()) {
            //用户不存在
            return ServerResponse.createByErrorMessage("用户不存在");
        }
        String question = userMapper.selectQuestionByUsername(username);
        if(StringUtils.isNotBlank(question)) {
            return ServerResponse.createBySuccess(question);
        }
        return ServerResponse.createByErrorMessage("找回密码的问题是空的");
    }

    /**
     * 检查密码提示问题的答案是否正确
     * @param username
     * @param question
     * @param answer
     * @return
     */
    @Override
    public ServerResponse<String> checkAnswer(String username, String question, String answer) {
        int resultCount = userMapper.checkAnswer(username,question,answer);
        if(resultCount > 0) {
            //说明问题及问题答案是这个用户的,并且是正确的
            //这个UUID类是java.util自带的类,可以生成一个重复概率非常非常低的字符串
            String forgeToken = UUID.randomUUID().toString();
            //然后需要把这个forgetToken放到cache中并设置有效期
            TokenCache.setKey(TokenCache.TOKEN_PREFIX + username, forgeToken);
            return ServerResponse.createBySuccess(forgeToken);
        }
        return ServerResponse.createByErrorMessage("问题的答案错误");
    }

    @Override
    public 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)) {
            //看了一些安全方面资料,这里的password应该已经是前端把密码加了固定盐值再MD5之后的内容了
            //所以这里可能是有漏洞的,不应该在后端加密
            //todo 修改加密的模式
            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("修改密码失败");
    }

    @Override
    public ServerResponse<String> resetPassword(User user, String passwordOld, String passwordNew) {
        //防止横向越权,要校验一下这个用户的旧密码,且一定要指定是不是这个用户的,因为我们会查询一个count(1),所以还是要全部核对一遍再修改
        int resultCount = userMapper.checkPassword(user.getId(), MD5Util.MD5EncodeUtf8(passwordOld));
        if(resultCount == 0) {
            return ServerResponse.createByErrorMessage("旧密码错误");
        }

        user.setPassword(MD5Util.MD5EncodeUtf8(passwordNew));
        int updateCount = userMapper.updateByPrimaryKeySelective(user);
        if(updateCount > 0) {
            return ServerResponse.createBySuccessMessage("密码更新成功");
        }
        return ServerResponse.createByErrorMessage("密码更新失败");
    }

    @Override
    public ServerResponse<User> updateInformation(User user) {
        //username是不能被更新的
        //email也要进行一个校验,校验新的email是不是已经存在,并且存在的emai如果相同的话,不能是我们当前这个用户的
        int resultCount = userMapper.checkEmailByUserId(user.getId(), user.getEmail());
        if(resultCount > 0) {
            return ServerResponse.createByErrorMessage("email已存在,请更换email再尝试更新");
        }

        //todo 这个项目的实例还是很简单的,包括手机号的验证等都没有写,等待扩展
        User updateUser = new User();
        updateUser.setId(user.getId());
        updateUser.setUsername(user.getUsername());
        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("更新个人信息失败");
    }

    @Override
    public ServerResponse<User> getInformation(Integer userId) {
        User user = userMapper.selectByPrimaryKey(userId);
        if(user == null) {
            return ServerResponse.createByErrorMessage("找不到当前用户");
        }
        //不能把密码传出去
        user.setPassword(StringUtils.EMPTY);
        return ServerResponse.createBySuccess(user);
    }

}

DAO部分

这里给出用户表相关接口,以及xml的配置,UserMapper.xml中没有mybatis generator注解的就是我后来自己加的

UserMapper.java

package top.winxblast.happymall.dao;

import org.apache.ibatis.annotations.Param;
import top.winxblast.happymall.pojo.User;

public interface UserMapper {
    /**
     * This method was generated by MyBatis Generator.
     * This method corresponds to the database table happymall_user
     *
     * @mbggenerated Sun Oct 08 14:03:47 CST 2017
     */
    int deleteByPrimaryKey(Integer id);

    /**
     * This method was generated by MyBatis Generator.
     * This method corresponds to the database table happymall_user
     *
     * @mbggenerated Sun Oct 08 14:03:47 CST 2017
     */
    int insert(User record);

    /**
     * This method was generated by MyBatis Generator.
     * This method corresponds to the database table happymall_user
     *
     * @mbggenerated Sun Oct 08 14:03:47 CST 2017
     */
    int insertSelective(User record);

    /**
     * This method was generated by MyBatis Generator.
     * This method corresponds to the database table happymall_user
     *
     * @mbggenerated Sun Oct 08 14:03:47 CST 2017
     */
    User selectByPrimaryKey(Integer id);

    /**
     * This method was generated by MyBatis Generator.
     * This method corresponds to the database table happymall_user
     *
     * @mbggenerated Sun Oct 08 14:03:47 CST 2017
     */
    int updateByPrimaryKeySelective(User record);

    /**
     * This method was generated by MyBatis Generator.
     * This method corresponds to the database table happymall_user
     *
     * @mbggenerated Sun Oct 08 14:03:47 CST 2017
     */
    int updateByPrimaryKey(User record);


    /**
     * 通过用户名来查询数据库中是否存在用户
     * @param username
     * @return 用户数量
     */
    int checkUsername(String username);

    /**
     * 查看email是否已经被注册
     * @param email
     * @return
     */
    int checkEmail(String email);


    /**
     * 通过用户名和密码查看是否有这个用户
     * mybatis在传递多个参数的时候需要用到param注解,写sql时就对应注解里的string
     * @param username
     * @param password
     * @return
     */
    User selectLogin(@Param("username") String username, @Param("password") String password);

    /**
     * 通过用户名来获取密码提示问题
     * @param username
     * @return
     */
    String selectQuestionByUsername(String username);

    /**
     * 检查密码找回问题的答案是否正确
     * 多个传入参数还是老规矩要使用@Param
     * @param username
     * @param question
     * @param answer
     * @return
     */
    int checkAnswer(@Param("username")String username, @Param("question")String question, @Param("answer")String answer);

    /**
     * 通过用户名修改密码
     * @param username
     * @param passwordNew
     * @return
     */
    int updatePasswordByUsername(@Param("username")String username, @Param("passwordNew")String passwordNew);

    /**
     * 通过用户id验证密码是否正确,这个感觉跟selectLogin有点重复,只不过一个返回用户,
     * 一个返回查询到的数量,可能取用户的多个字段性能会差一点,具体差多少有待试验
     * @param userId
     * @param password
     * @return
     */
    int checkPassword(@Param("userId")Integer userId, @Param("password")String password);

    /**
     * 检查email地址是否有其他用户使用
     * @param userId
     * @param email
     * @return 返回1,则email地址已被使用,返回0,则email未被使用
     */
    int checkEmailByUserId(@Param("userId")Integer userId, @Param("email")String email);

}

UserMapper.xml
这里特别要注意一点就是sql函数count后面不要接空格···不然SQL会执行错误

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="top.winxblast.happymall.dao.UserMapper" >
  <resultMap id="BaseResultMap" type="top.winxblast.happymall.pojo.User" >
    <!--
      WARNING - @mbggenerated
      This element is automatically generated by MyBatis Generator, do not modify.
      This element was generated on Sun Oct 08 14:03:47 CST 2017.
    -->
    <constructor >
      <idArg column="id" jdbcType="INTEGER" javaType="java.lang.Integer" />
      <arg column="username" jdbcType="VARCHAR" javaType="java.lang.String" />
      <arg column="password" jdbcType="VARCHAR" javaType="java.lang.String" />
      <arg column="email" jdbcType="VARCHAR" javaType="java.lang.String" />
      <arg column="phone" jdbcType="VARCHAR" javaType="java.lang.String" />
      <arg column="question" jdbcType="VARCHAR" javaType="java.lang.String" />
      <arg column="answer" jdbcType="VARCHAR" javaType="java.lang.String" />
      <arg column="role" jdbcType="INTEGER" javaType="java.lang.Integer" />
      <arg column="create_time" jdbcType="TIMESTAMP" javaType="java.util.Date" />
      <arg column="update_time" jdbcType="TIMESTAMP" javaType="java.util.Date" />
    </constructor>
  </resultMap>
  <sql id="Base_Column_List" >
    <!--
      WARNING - @mbggenerated
      This element is automatically generated by MyBatis Generator, do not modify.
      This element was generated on Sun Oct 08 14:03:47 CST 2017.
    -->
    id, username, password, email, phone, question, answer, role, create_time, update_time
  </sql>
  <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
    <!--
      WARNING - @mbggenerated
      This element is automatically generated by MyBatis Generator, do not modify.
      This element was generated on Sun Oct 08 14:03:47 CST 2017.
    -->
    select 
    <include refid="Base_Column_List" />
    from happymall_user
    where id = #{id,jdbcType=INTEGER}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" >
    <!--
      WARNING - @mbggenerated
      This element is automatically generated by MyBatis Generator, do not modify.
      This element was generated on Sun Oct 08 14:03:47 CST 2017.
    -->
    delete from happymall_user
    where id = #{id,jdbcType=INTEGER}
  </delete>
  <insert id="insert" parameterType="top.winxblast.happymall.pojo.User" >
    <!--
      WARNING - @mbggenerated
      This element is automatically generated by MyBatis Generator, do not modify.
      This element was generated on Sun Oct 08 14:03:47 CST 2017.
    -->
    insert into happymall_user (id, username, password, 
      email, phone, question, 
      answer, role, create_time, 
      update_time)
    values (#{id,jdbcType=INTEGER}, #{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, 
      #{email,jdbcType=VARCHAR}, #{phone,jdbcType=VARCHAR}, #{question,jdbcType=VARCHAR}, 
      #{answer,jdbcType=VARCHAR}, #{role,jdbcType=INTEGER}, now(),
      now())
  </insert>
  <insert id="insertSelective" parameterType="top.winxblast.happymall.pojo.User" >
    <!--
      WARNING - @mbggenerated
      This element is automatically generated by MyBatis Generator, do not modify.
      This element was generated on Sun Oct 08 14:03:47 CST 2017.
    -->
    insert into happymall_user
    <trim prefix="(" suffix=")" suffixOverrides="," >
      <if test="id != null" >
        id,
      </if>
      <if test="username != null" >
        username,
      </if>
      <if test="password != null" >
        password,
      </if>
      <if test="email != null" >
        email,
      </if>
      <if test="phone != null" >
        phone,
      </if>
      <if test="question != null" >
        question,
      </if>
      <if test="answer != null" >
        answer,
      </if>
      <if test="role != null" >
        role,
      </if>
      <if test="createTime != null" >
        create_time,
      </if>
      <if test="updateTime != null" >
        update_time,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides="," >
      <if test="id != null" >
        #{id,jdbcType=INTEGER},
      </if>
      <if test="username != null" >
        #{username,jdbcType=VARCHAR},
      </if>
      <if test="password != null" >
        #{password,jdbcType=VARCHAR},
      </if>
      <if test="email != null" >
        #{email,jdbcType=VARCHAR},
      </if>
      <if test="phone != null" >
        #{phone,jdbcType=VARCHAR},
      </if>
      <if test="question != null" >
        #{question,jdbcType=VARCHAR},
      </if>
      <if test="answer != null" >
        #{answer,jdbcType=VARCHAR},
      </if>
      <if test="role != null" >
        #{role,jdbcType=INTEGER},
      </if>
      <if test="createTime != null" >
        now(),
      </if>
      <if test="updateTime != null" >
        now(),
      </if>
    </trim>
  </insert>
  <update id="updateByPrimaryKeySelective" parameterType="top.winxblast.happymall.pojo.User" >
    <!--
      WARNING - @mbggenerated
      This element is automatically generated by MyBatis Generator, do not modify.
      This element was generated on Sun Oct 08 14:03:47 CST 2017.
    -->
    update happymall_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>
  <update id="updateByPrimaryKey" parameterType="top.winxblast.happymall.pojo.User" >
    <!--
      WARNING - @mbggenerated
      This element is automatically generated by MyBatis Generator, do not modify.
      This element was generated on Sun Oct 08 14:03:47 CST 2017.
    -->
    update happymall_user
    set username = #{username,jdbcType=VARCHAR},
      password = #{password,jdbcType=VARCHAR},
      email = #{email,jdbcType=VARCHAR},
      phone = #{phone,jdbcType=VARCHAR},
      question = #{question,jdbcType=VARCHAR},
      answer = #{answer,jdbcType=VARCHAR},
      role = #{role,jdbcType=INTEGER},
      create_time = #{createTime,jdbcType=TIMESTAMP},
      update_time = now()
    where id = #{id,jdbcType=INTEGER}
  </update>

  <select id="checkUsername" resultType="java.lang.Integer" parameterType="java.lang.String">
    <!--#号是预编译,防止sql注入-->
    SELECT COUNT(1) FROM happymall_user
    WHERE username = #{username}
  </select>

  <select id="checkEmail" resultType="int" parameterType="string">
    SELECT COUNT(1) FROM happymall_user
    WHERE email = #{email}
  </select>

  <select id="selectLogin" resultMap="BaseResultMap" parameterType="map">
    SELECT
    <include refid="Base_Column_List"/>
    FROM happymall_user
    WHERE username = #{username}
    AND password = #{password}
  </select>

  <select id="selectQuestionByUsername" resultType="string" parameterType="string">
    SELECT
    question
    FROM happymall_user
    WHERE username = #{username}
  </select>

  <select id="checkAnswer" resultType="int" parameterType="map">
    SELECT
    COUNT(1)
    FROM happymall_user
    WHERE username = #{username}
    AND question = #{question}
    AND answer = #{answer}
  </select>

  <update id="updatePasswordByUsername" parameterType="map">
    UPDATE happymall_user
    SET password = #{passwordNew}, update_time = now()
    WHERE username = #{username}
  </update>

  <select id="checkPassword" resultType="int" parameterType="map">
    SELECT COUNT(1)
    FROM happymall_user
    WHERE id = #{userId}
    AND password = #{password}
  </select>

  <select id="checkEmailByUserId" resultType="int" parameterType="map">
    <!--其实就是看看这个新的email是不是属于其他用户的-->
    SELECT COUNT(1)
    FROM happymall_user
    WHERE email = #{email}
    AND id != #{userId}
  </select>
</mapper>

后台管理员用户

后台管理员在这里本质是和普通用户一样的,都在同一张user表中,只不过role字段不同而已,所以这里针对后台管理员用户只需要写controller层就行了,service层和dao层在功能够用的情况下完全可以不用重写。
血泪教训:RequestMapping不要用manager,会跟tomcat冲突,改用manage

package top.winxblast.happymall.controller.backend;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import top.winxblast.happymall.common.Const;
import top.winxblast.happymall.common.ServerResponse;
import top.winxblast.happymall.pojo.User;
import top.winxblast.happymall.service.IUserService;

import javax.servlet.http.HttpSession;

/**
 * 后台管理员相关功能类,控制层
 *
 * @author winxblast
 * @create 2017/10/22
 **/
@Controller
//这里有个问题RequestMapping这里最好不要用manager,因为可能汇合tomcat自己的manager界面冲突,所以改为manage
@RequestMapping("/manage/user")
public class UserManageController {

    @Autowired
    private IUserService iUserService;

    @RequestMapping(value = "/login.do", method = RequestMethod.POST)
    @ResponseBody
    public ServerResponse<User> login(String username, String password, HttpSession session) {
        ServerResponse<User> response = iUserService.login(username, password);
        if(response.isSuccess()) {
            User user = response.getData();
            if(user.getRole() == Const.Role.ROLE_ADMIN) {
                //说明登录的是管理员
                session.setAttribute(Const.CURRENT_USER, user);
                return response;
            } else {
                return ServerResponse.createByErrorMessage("不是管理员,无法登录");
            }
        }
        return response;
    }
}

小结(一些感想,想到啥说啥)

其实自己以前也没有接触过正规的开发内容,现在看来自己的习惯还是有很多要修改的,从上一篇的5-1先设计用户接口,再到这一篇的实现,就是一个比较规范的过程了。

这些规范就是代码质量,工程化的例子吧···(请原谅我简单的认识 ̄□ ̄||)

对于哪些代码要放在controller层中,哪些放在service层中有了些模模糊糊的认识,以后还要多看看别人的代码~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值