前言
本篇文章主要介绍一下这个项目中电商系统的用户模块开发。电商系统有很多模块,除了用户模块,还有分类模块,购物车模块,订单模块等等。这篇文章我们先介绍用户模块。
用户模块是一个后端功能模块,提供了用户注册、登录、获取用户信息和登出等核心功能,其中,我们的项目中,用户模块的功能主要是用户注册和登录这两个功能。下面是关于我设计这个模块的思路。
开发顺序
Dao层->Service层->Controller层。有人是反过来开发的,因为他们觉得API已经定好了,那不如就从API开始写呗!其实这不太对,为什么要先写service层再写controller层呢?因为service层放的是业务的逻辑,而controller层只是一些数据的组装,是要返回给前端的JSON格式的数据。以后会面临着这样的一个问题:service层业务逻辑不变的情况下,前端那边可能会返回不同的数据,比如PC端返回的数据和小程序端返回的数据就不一样,那这个时候service层的代码是不用改的,要改的只是controller层。或者你再换个角度想想,Dao层是跟数据库打交道的,service层是业务逻辑,业务逻辑肯定也是比较重要的,而controller层就比较表层,只是返回给前端的数据,所以你不要依照表层的东西去改核心的东西。还有一点,就是关于单元测试(Test文件夹),为什么要测service层,不测Dao层呢?因为Dao层就是数据库的增删改查,这些东西没啥好测的,而controller层是面向前端的层面了,API给前端用的,这部分的测试是给公司内部人员测的。作为开发人员的我们,测service层就已经OK了,因为这块是我们的主要逻辑。
设计Dao层
作为用户模块,Dao层其实无非就是写一些关于用户记录怎么样和数据库打交道,就像这样:
package com.imooc.mall.dao;
import com.imooc.mall.pojo.User;
public interface UserMapper {
int deleteByPrimaryKey(Integer id); //根据主键从数据库中删除用户记录
int insert(User record); //将新的用户记录插入数据库
int insertSelective(User record); //将新的用户记录插入数据库,但只插入"User"对象中非空的字段
User selectByPrimaryKey(Integer id); //根据主键从数据库中检索用户记录
int updateByPrimaryKeySelective(User record); //更新数据库中的用户记录,但只更新"User"对象中非空的字段
int updateByPrimaryKey(User record); //更新数据库中的用户记录,包括"User"对象的所有字段
int countByUsername(String username); //统计数据库中具有特定用户名的用户记录数
int countByEmail(String email); //统计数据库中具有特定电子邮件的用户记录数
User selectByUsername(String username); //根据用户名从数据库中检索用户记录
}
上面的代码代码是一个Java接口,名为UserMapper
,位于com.imooc.mall.dao
包中。它定义了一些用于与数据库中的"User"实体进行交互的方法。该接口很可能是一个更大应用程序的一部分,该应用程序使用数据访问层(DAO)模式与数据库进行交互。实际实现这些方法的代码会在另一个实现了该接口的类中提供。
那么是哪个类实现了这些方法呢?没错!你肯定想到了!接口方法的具体实现放在与接口同名的XML文件中,即UserMapper.xml。
UserMapper.xml
就是上面这幅图,里面会实现UserMapper
的所有方法。我们其实不太关心怎么实现,太复杂了,新手也搞不懂,但是我们知道确实每个方法都有特定的语句去完成对数据库的增删改查的功能,是有这回事儿就对了!
Dao层大概就是这么多,接来下看看service层是怎么写的
设计service层
首先写一个名为IUserService
的Java接口,位于com.imooc.mall.service
包中。它定义了两个方法用于用户操作相关的服务
package com.imooc.mall.service;
import com.imooc.mall.pojo.User;
import com.imooc.mall.vo.ResponseVo;
/**
* Created by 廖师兄
*/
public interface IUserService {
/**
* 注册:注册用户。接收一个User对象作为参数,返回一个ResponseVo<User>对象作为注册结果
*/
ResponseVo<User> register(User user);
/**
* 登录:用户登录。接收用户名和密码作为参数,返回一个ResponseVo<User>对象作为登录结果
*/
ResponseVo<User> login(String username, String password);
}
这个接口定义了用户服务的行为,具体的实现将在其他类中完成。实现类将根据这些方法的定义提供相应的功能逻辑和处理。接下来我们写实现类:
package com.imooc.mall.service.impl;
import com.imooc.mall.dao.UserMapper;
import com.imooc.mall.enums.ResponseEnum;
import com.imooc.mall.enums.RoleEnum;
import com.imooc.mall.pojo.User;
import com.imooc.mall.service.IUserService;
import com.imooc.mall.vo.ResponseVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import java.nio.charset.StandardCharsets;
import static com.imooc.mall.enums.ResponseEnum.*;
@Service
public class UserServiceImpl implements IUserService {
@Autowired
private UserMapper userMapper;
/**
* 注册
*
* @param user
*/
@Override
public ResponseVo<User> register(User user) {
// error();
//username不能重复
int countByUsername = userMapper.countByUsername(user.getUsername());
if (countByUsername > 0) {
return ResponseVo.error(USERNAME_EXIST);
}
//email不能重复
int countByEmail = userMapper.countByEmail(user.getEmail());
if (countByEmail > 0) {
return ResponseVo.error(EMAIL_EXIST);
}
user.setRole(RoleEnum.CUSTOMER.getCode());
//MD5摘要算法(Spring自带)
user.setPassword(DigestUtils.md5DigestAsHex(
user.getPassword().getBytes(StandardCharsets.UTF_8)
));
//写入数据库
int resultCount = userMapper.insertSelective(user);
if (resultCount == 0) {
return ResponseVo.error(ERROR);
}
return ResponseVo.success();
}
@Override
public ResponseVo<User> login(String username, String password) {
User user = userMapper.selectByUsername(username);
if (user == null) {
//用户不存在(返回:用户名或密码错误 )
return ResponseVo.error(ResponseEnum.USERNAME_OR_PASSWORD_ERROR);
}
if (!user.getPassword().equalsIgnoreCase(
DigestUtils.md5DigestAsHex(password.getBytes(StandardCharsets.UTF_8)))) {
//密码错误(返回:用户名或密码错误 )
return ResponseVo.error(ResponseEnum.USERNAME_OR_PASSWORD_ERROR);
}
user.setPassword("");
return ResponseVo.success(user);
}
private void error() {
throw new RuntimeException("意外错误");
}
}
不知道你看不看得懂,你看得懂当然最好,看不懂就看看我对代码的分析。
首先分析一下注册功能:在注册过程中,首先检查用户名和邮箱是否已存在于数据库中。如果存在重复的用户名或邮箱,将返回相应的错误响应。否则,将设置用户的角色为普通用户,对用户的密码进行MD5摘要处理,并将用户信息写入数据库。若写入数据库成功,返回成功的响应。
再来分析一下用户登录功能。在用户登录过程中,首先根据用户名从数据库中获取用户信息,如果用户不存在,则返回用户名或密码错误的响应。如果用户存在,则将输入的密码与数据库中存储的密码进行MD5摘要比对,如果不一致,则返回用户名或密码错误的响应。如果密码验证通过,将返回成功的响应,并清除用户对象中的密码字段。
service层写完后,我们来看看controller层怎么写。
设计controller层
package com.imooc.mall.controller;
import com.imooc.mall.consts.MallConst;
import com.imooc.mall.form.UserLoginForm;
import com.imooc.mall.form.UserRegisterForm;
import com.imooc.mall.pojo.User;
import com.imooc.mall.service.IUserService;
import com.imooc.mall.vo.ResponseVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
/**
* Created by 廖师兄
*/
@RestController //它是一个控制器,用于处理HTTP请求并返回响应
@Slf4j
public class UserController {
@Autowired
private IUserService userService;
@PostMapping("/user/register")
public ResponseVo<User> register(@Valid @RequestBody UserRegisterForm userForm) {
User user = new User();
BeanUtils.copyProperties(userForm, user);
//dto
return userService.register(user);
}
@PostMapping("/user/login")
public ResponseVo<User> login(@Valid @RequestBody UserLoginForm userLoginForm,
HttpSession session) {
ResponseVo<User> userResponseVo = userService.login(userLoginForm.getUsername(), userLoginForm.getPassword());
//设置Session
session.setAttribute(MallConst.CURRENT_USER, userResponseVo.getData());
log.info("/login sessionId={}", session.getId());
return userResponseVo;
}
//session保存在内存里,改进版:token+redis
@GetMapping("/user")
public ResponseVo<User> userInfo(HttpSession session) {
log.info("/user sessionId={}", session.getId());
User user = (User) session.getAttribute(MallConst.CURRENT_USER);
return ResponseVo.success(user);
}
/**
* {@link TomcatServletWebServerFactory} getSessionTimeoutInMinutes
*/
@PostMapping("/user/logout")
public ResponseVo logout(HttpSession session) {
log.info("/user/logout sessionId={}", session.getId());
session.removeAttribute(MallConst.CURRENT_USER);
return ResponseVo.success();
}
}
上面的代码太多了,如果你看不懂,可以看看我对于这些代码的理解。
其实这串代码核心就两点:关于用户注册和用户登录。
-
用户注册(Register):
- 使用
@PostMapping("/user/register")
注解来处理用户注册的POST请求。 - 请求体中包含
UserRegisterForm
对象,用于接收用户的注册信息。 - 调用
userService.register()
方法,将注册信息传递给该方法进行用户注册。 - 注册成功后,返回一个
ResponseVo
对象,表示注册成功。
- 使用
-
用户登录(Login):
- 使用
@PostMapping("/user/login")
注解来处理用户登录的POST请求。 - 请求体中包含
UserLoginForm
对象,用于接收用户的登录信息。 - 调用
userService.login()
方法,将登录信息传递给该方法进行用户登录验证。 - 登录成功后,将用户信息存储在
HttpSession
中,并返回一个ResponseVo
对象,表示登录成功。
- 使用
总结
这篇文章我讲了我是如何设计电商系统的用户模块的,当然,电商系统还有很多其他的模块,比如分类模块,购物车模块,订单模块等,我只是介绍了其中的一个模块,其他的模块等我下几篇文章会详细介绍!