Java-springboot生鲜电商项目(二)用户模块
用户模块开发
涉及到的接口
- 用户注册登录
- 更新个性签名
- 退出登录
- 管理员登录
开发涉及到内容:
- 登录、注册
- 重名校验
- 密码加密存储
- session的使用
- 越权校验
- 统一响应
- 异常枚举
- Java异常体系
- postman实操
- 统一异常处理
- 更新个人信息
(一)用户注册
用户注册的开发思路:
- 在dao层创建用户注册需要对数据库操作的相关接口
- 在对应的mapper.xml下书写SQL语句
- 在service层创建业务相关的接口,并在Impl中对接口进行实现,书写主要的业务逻辑
- 在controller中定义好用户注册请求响应,相关参数,对前端的参数进行过滤,之后调用service接口实现前端的数据传输到数据库
- 在1到4步骤中,对于异常,工具等,进行重构统一处理,提高程序的健壮性
根据文档,创建common包下ApiResponse类来处理响应对象
#响应文档1
{
"status": 10000,
"msg": "SUCCESS",
"data": null
}
#响应文档2
{
"status": 10000,
"msg": "SUCCESS",
"data": {
"id": 9,
"username": "xiaomu2",
"password": null,
"personalizedSignature": "祝你今天好心情",
"role": 2,
"createTime": "2020-02-09T12:39:47.000+0000",
"updateTime": "2020-02-10T16:56:02.000+0000"
}
}
package com.hyb.mall.common;
import com.hyb.mall.exception.MallExceptionEnum;
public class ApiRestResponse<T> {
private Integer status;
private String msg;
private T data;
private static final int OK_CODE = 10000;
private static final String OK_MSG = "SUCCESS";
public ApiRestResponse(Integer status, String msg, T data) {
this.status = status;
this.msg = msg;
this.data = data;
}
public ApiRestResponse(Integer status, String msg) {
this.status = status;
this.msg = msg;
}
public ApiRestResponse() {
this(OK_CODE, OK_MSG);
}
//成功的时候调用的方法
public static <T> ApiRestResponse<T> success() {
return new ApiRestResponse<>();
}
//返回具有data的方法
public static <T> ApiRestResponse<T> success(T result) {
ApiRestResponse<T> response = new ApiRestResponse<>();
response.setData(result);
return response;
}
//失败时的方法
public static <T> ApiRestResponse<T> error(Integer code, String msg) {
return new ApiRestResponse<>(code, msg);
}
//失败时的方法
public static <T> ApiRestResponse<T> error(MallExceptionEnum ex) {
return new ApiRestResponse<>(ex.getCode(),ex.getMsg());
}
@Override
public String toString() {
return "ApiRestResponse{" +
"status=" + status +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static int getOkCode() {
return OK_CODE;
}
public static String getOkMsg() {
return OK_MSG;
}
}
定义controller返回的异常枚举
package com.hyb.mall.exception;
/**
* 定义异常枚举
*/
public enum MallExceptionEnum {
//业务异常 10000
NEED_USER_NAME(10001,"用户名不能为空"),
NEED_PASSWORD(10002,"密码不能为空"),
PASSWORD_TO_SHORT(10003,"密码长度不小于8位"),
NAME_EXISTED(10004,"不予许重名"),
INSERT_FAILED(10005,"插入失败,请重试"),
WRONG_PASSWORD(10006,"密码错误"),
NEED_LOGIN(10007,"用户未登录"),
UPDATE_FAILED(10008,"更新失败"),
NEED_ADMIN(10009,"无管理员权限"),
//系统异常 20000
SYSTEM_ERROR(20000,"系统异常");
/**
* 异常码 code
* 异常信息 msg
*/
Integer code;
String msg;
MallExceptionEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
public void setCode(Integer code) {
this.code = code;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
在exception包中定义统一异常处理
package com.hyb.mall.exception;
/**
* 描述:统一异常
*/
public class MallException extends RuntimeException{
/**
* 异常码 code
* 异常信息 msg
*/
private final Integer code;
private final String message;
public MallException(Integer code, String message) {
this.code = code;
this.message = message;
}
public MallException(MallExceptionEnum exceptionEnum){
this(exceptionEnum.getCode(),exceptionEnum.getMsg());
}
public Integer getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}
1.在dao层先定义查询用户的接口,并且返回User查询对象
User selectByName(String userName);
2.在UserMapper映射中添加SQL查询语句
<select id="selectByName" parameterType="java.lang.String" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from imooc_mall_user
where username = #{userName,jdbcType=VARCHAR}
</select>
3.在UserService定义用户注册接口,并在Service实现接口
//用户注册
void register(String userName,String password);
public void register(String userName, String password) throws MallException {
//1.查询用户名是否存在,不允许重名
User result = userMapper.selectByName(userName);
if (result != null) {
throw new MallException(MallExceptionEnum.NAME_EXISTED);
}
//2.将用户名和密码写入数据库
User user = new User();
user.setUsername(userName);
user.setPassword(password);
int count = userMapper.insertSelective(user);
if (count == 0) {
throw new MallException(MallExceptionEnum.INSERT_FAILED);
}
}
4.在controller实现调用
@PostMapping("/register")
@ResponseBody
public ApiRestResponse register(@RequestParam("userName") String userName, @RequestParam("password") String password) throws MallException {
//1.校验:判断用户名是否为空
if (StringUtils.isEmpty(userName)) {
return ApiRestResponse.error(MallExceptionEnum.NEED_USER_NAME);
}
//2.校验:判断密码是否为空
if (StringUtils.isEmpty(password)) {
return ApiRestResponse.error(MallExceptionEnum.NEED_PASSWORD);
}
//3.密码长度不少于8位
if (password.length() < 8) {
return ApiRestResponse.error(MallExceptionEnum.PASSWORD_TO_SHORT);
}
//4.调用useService的注册方法,将用户名和密码写入数据库
userService.register(userName, password);
//5.写入成功后返回成功
return ApiRestResponse.success();
}
5.使用postman进行测试,效果如下:
在插入成功后,我将用户名,密码为空,密码小于8位在进行测试,结果成功返回预期效果。但是在测试完后,有一些错误的信息暴露出来,所以错误的处理还需要进行重构!!!
package com.hyb.mall.exception;
import com.hyb.mall.common.ApiRestResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
1. 描述:处理统一异常的handler
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(Exception.class)
@ResponseBody
public Object handleException(Exception e) {
log.error("Default Exception: ", e);
return ApiRestResponse.error(MallExceptionEnum.SYSTEM_ERROR);
}
@ExceptionHandler(MallException.class)
@ResponseBody
public Object handleImoocMallException(MallException e) {
log.error("MallException: ", e);
return ApiRestResponse.error(e.getCode(), e.getMessage());
}
}
实现密码加密
- 在common下创建一个加盐常量的工具包,避免MD5加密被反破解
- 在util下创建MD5加密的工具类,对password进行加密处理
- 在注册接口的实现类中,用MD5加密的方法代替原来直接输入密码在数据库的操作
在common下Constant,用于加盐
package com.hyb.mall.common;
/**
* 描述:常量
*/
public class Constant {
//自定义常量,越复杂越好,和MD5混合使用,避免MD5反破解
public static final String SALT = "82ps[d]][sffs.a";
public static final String HYB_MALL_USER = "hyb_mall_user";
}
创建工具包Util,创建MD5Utils
package com.hyb.mall.util;
import com.hyb.mall.common.Constant;
import org.apache.tomcat.util.codec.binary.Base64;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* 描述:MD5加密工具
*/
public class MD5Utils {
public static String getMD5Str(String strValue) throws NoSuchAlgorithmException {
//传入算法MD5
MessageDigest md5 = MessageDigest.getInstance("MD5");
//传入的参数还需要进行base64的转码,方便存储
return Base64.encodeBase64String(md5.digest((strValue + Constant.SALT).getBytes()));
}
public static void main(String[] args) throws NoSuchAlgorithmException {
//加密后的密码:q+z/brWEdxFpbgLJ2MTXng==
String md5 = getMD5Str("1234");
System.out.println(md5);
}
}
在UserServiceImpl中替换原来password的存储密码方法,使用MD5进行密码存储
@Override
public void register(String userName, String password) throws MallException {
//1.查询用户名是否存在,不允许重名
User result = userMapper.selectByName(userName);
if (result != null) {
throw new MallException(MallExceptionEnum.NAME_EXISTED);
}
//2.将用户名和密码写入数据库
User user = new User();
user.setUsername(userName);
//使用MD5加密对密码进行加密处理
try {
user.setPassword(MD5Utils.getMD5Str(password));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
//直接输入密码不进行处理,存在安全隐患
// user.setPassword(password);
int count = userMapper.insertSelective(user);
if (count == 0) {
throw new MallException(MallExceptionEnum.INSERT_FAILED);
}
}
(二)用户登录
1. 在dao中UserMapper中添加查询数据库用户接口
//传入多个参数,需要用@Param
User selectLogin(@Param("userName") String userName,@Param("password") String password);
2. 在resources/mappers中UserMapper.xml中查询语句
<select id="selectLogin" parameterType="map" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from imooc_mall_user
where username = #{userName,jdbcType=VARCHAR}
and password = #{password}
</select>
3. 在service中添加用户登录的业务接口
//用户登录
User login(String userName, String password) throws MallException;
4. 在service/Impl中实现用户登录的业务接口类,获取用户名,加密后的密码进行登录校验
@Override
public User login(String userName,String password) throws MallException {
String md5Password = null;
try {
md5Password = MD5Utils.getMD5Str(password);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
User user = userMapper.selectLogin(userName,md5Password);
if(user == null){
throw new MallException(MallExceptionEnum.WRONG_PASSWORD);
}
return user;
}
5. 在controller中主要是实现用户的登录,并将用户信息保存在session中。
@PostMapping("/login")
@ResponseBody
public ApiRestResponse login(@RequestParam("userName") String userName,
@RequestParam("password") String password,
HttpSession session) throws MallException {
//1.校验:判断用户名是否为空
if (StringUtils.isEmpty(userName)) {
return ApiRestResponse.error(MallExceptionEnum.NEED_USER_NAME);
}
//2.校验:判断密码是否为空
if (StringUtils.isEmpty(password)) {
return ApiRestResponse.error(MallExceptionEnum.NEED_PASSWORD);
}
User user = userService.login(userName, password);
//为了安全,保存用户信息时,不将密码进行保存,返回null
user.setPassword(null);
//将用户信息存在session中
session.setAttribute(Constant.HYB_MALL_USER,user);
return ApiRestResponse.success(user);
}
(三)更新用户个性签名
1.先在userService中创建相关接口
//更新签名
void updateInformation(User user) throws MallException;
2.实现个性签名中的接口
@Override
public void updateInformation(User user) throws MallException {
//更新个性签名
int updateCount = userMapper.updateByPrimaryKeySelective(user);
if (updateCount>1){
throw new MallException(MallExceptionEnum.UPDATE_FAILED);
}
}
3.在controller中获取到了登录后的session信息,利用session去更新用户的个性签名。
//验证session是否进行存储
@PostMapping("/user/update")
@ResponseBody
public ApiRestResponse updateUserInfo(HttpSession session,@RequestParam String signature) throws MallException {
User currentUser = (User) session.getAttribute(Constant.HYB_MALL_USER);
if(currentUser == null){
return ApiRestResponse.error(MallExceptionEnum.NEED_LOGIN);
}
User user = new User();
user.setId(currentUser.getId());
user.setPersonalizedSignature(signature);
userService.updateInformation(user);
return ApiRestResponse.success();
}
(四)退出用户登录
直接将用户信息从session中清除掉,以此来完成登出功能
@PostMapping("/user/logout")
@ResponseBody
public ApiRestResponse logout(HttpSession session){
session.removeAttribute(Constant.HYB_MALL_USER);
return ApiRestResponse.success();
}
(五)管理员登录
1.在service中定义管理员校验接口
//校验是否管理员
boolean checkAdminRole(User user);
2.在service/Impl实现类中判断用户是普通用户(1)还是管理员(2)
@Override
public boolean checkAdminRole(User user){
//1是普通用户 2是管理员
return user.getRole().equals(2);
}
3.在controller中进行管理员的登录校验,与之前普通用户的登录差不多,只是需要判断登录的用户的身份是否是管理员
@PostMapping("/adminlogin")
@ResponseBody
public ApiRestResponse adminlogin(@RequestParam("userName") String userName,
@RequestParam("password") String password,
HttpSession session) throws MallException {
//1.校验:判断用户名是否为空
if (StringUtils.isEmpty(userName)) {
return ApiRestResponse.error(MallExceptionEnum.NEED_USER_NAME);
}
//2.校验:判断密码是否为空
if (StringUtils.isEmpty(password)) {
return ApiRestResponse.error(MallExceptionEnum.NEED_PASSWORD);
}
User user = userService.login(userName, password);
//判断登录的用户的身份是否是管理员
if (userService.checkAdminRole(user)) {
//为了安全,保存用户信息时,不将密码进行保存,返回null
user.setPassword(null);
//将用户信息存在session中
session.setAttribute(Constant.HYB_MALL_USER, user);
return ApiRestResponse.success(user);
}else {
return ApiRestResponse.error(MallExceptionEnum.NEED_ADMIN);
}
(六)使用postman进行接口校验
校验步骤:
- 先进行更新个性签名的更新,判断是否session能正常工作(更新结果失败,无登录结果)
- 进行用户注册,再次进行个性签名更新(更新结果还是失败,同样是无登录)
- 用户登录后,检查数据库是否存在和密码是否加密(校验成功)
- 再次进行个性签名更新(更新成功)
- 登出用户,清除session(登出成功)
- 修改数据库用户身份,将普通用户修改成管理员用户,再次进行管理员登录校验(管理员登录成功)
流程如下图所示: