前言
接下来就要正式开始往之前搭建的结构中填充代码了,这些代码也不是一次完成的,我要纠结一下怎么来把这个学习过程明白的描写出来。可能有些类增加了多少就把那一部分写出来吧, 我大部分的上课笔记还是会写在代码的注释中,这样方便了记笔记,却让读者的思路顺序有些乱了,尽量表达的清楚一点吧~
以这种形式把上课学习内容写出来还真是有些别扭,以后可能我就只能挑着有意义的部分写了。
(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层中有了些模模糊糊的认识,以后还要多看看别人的代码~