SpringBoot-项目1-用户(注册,登录,修改密码,修改个人资料,上传头像)

1. 项目分析

在设计一款软件时,在编写代码之前,应该先分析这个项目中需要处理哪些类型的数据!例如,本项目中需要处理的数据种类有:收藏,购物车,用户,收货地址,订单,商品,商品类别。

当确定了需要处理的数据的种类之后,就应该确定这些数据的处理先后顺序:用户 > 收货地址 > 商品类别 > 商品 > 收藏 > 购物车 > 订单。

在具体开发某个数据的管理功能之前,还应该分析该数据需要开发哪些管理功能,以用户数据为例,需要开发的有:修改密码,上传头像,修改资料,登录,注册。

分析出功能之后,也需要确定这些功能的开发顺序,一般先开发简单的,也依据增、查、删、改的顺序,则以上功能的开发顺序应该是:注册 > 登录 > 修改密码 > 修改资料 > 上传头像。

在开发某个数据的任何功能之前,还应该先创建这种数据对应的数据表,然后,创建对应的实体类,再开发某个功能!

在开发某个功能时,还应该遵循顺序:持久层(数据库编程) > 业务层 > 控制器层 > 前端页面。

2. 用户-创建数据表

先创建数据库:

CREATE DATABASE db_store;
USE db_store;

然后,在数据库中创建数据表:

CREATE TABLE t_user (
  uid INT AUTO_INCREMENT COMMENT '用户id',
  username VARCHAR(20) UNIQUE NOT NULL COMMENT '用户名',
  password CHAR(32) NOT NULL COMMENT '密码',
  salt CHAR(36) COMMENT '盐值',
  gender INT(1) COMMENT '性别:0-女,1-男',
  phone VARCHAR(20) COMMENT '手机号码',
  email VARCHAR(50) COMMENT '电子邮箱',
  avatar VARCHAR(100) COMMENT '头像',
  is_delete INT(1) COMMENT '是否删除:0-否,1-是',
  created_user VARCHAR(20) COMMENT '创建人',
  created_time DATETIME COMMENT '创建时间',
  modified_user VARCHAR(20) COMMENT '最后修改人',
  modified_time DATETIME COMMENT '最后修改时间',
  PRIMARY KEY (uid)
) DEFAULT CHARSET=utf8mb4;

完成后,可以通过desc t_user;show create table t_user;进行查看。

3. 用户-创建实体类

创建SpringBoot项目,所以,先打开https://start.spring.io创建项目,创建时,使用的版本选择2.1.12,Group为cn.demo,Artifact为store,Packaging为war,添加Mybatis FrameworkMySQL Driver` 依赖,在网站生成项目后,将解压得到的项目文件夹剪切到Workspace中,并在Eclipse中导入该项目。

src/main/java下,在现有的cn.demo.store包中,创建子级entity包,用于存放实体类,先在entity包中创建所有实体类的基类:

/**
 * 实体类的基类
 */
abstract class BaseEntity implements Serializable {

  private static final long serialVersionUID = -3122958702938259476L;
  
  private String createdUser;
  private Date createdTime;
  private String modifiedUser;
  private Date modifiedTime;
  
  // 自行添加SET/GET方法,toString()
  
}

并在entity包中创建User类,继承自以上基类:

/**
 * 用户数据的实体类
 */
public class User extends BaseEntity {

  private static final long serialVersionUID = -3302907460554699349L;
  
  private Integer uid;
  private String username;
  private String password;
  private String salt;
  private Integer gender;
  private String phone;
  private String email;
  private String avatar;
  private Integer isDelete;
 
  // 自行添加SET/GET方法,基于uid的equals()和hashCode()方法,toString()方法
  
}

4. 用户-注册-持久层

持久层:持久化保存数据的层。

刚创建好的SpringBoot项目,由于添加了数据库相关的依赖,在没有配置数据库连接信息之前,将无法启动!所以,应该先在application.properties中添加配置:

server.port=8080

spring.datasource.url=jdbc:mysql://localhost:3306/db_store?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456

mybatis.mapper-locations=classpath:mappers/*.xml

然后,在cn.demo.store包中,创建mapper子级包,用于存放使用MyBatis编程时的接口文件,并在mapper包中创建UserMapper接口,在接口中添加抽象方法:

/**
 * 处理用户数据的持久层接口
 */
public interface UserMapper {
  
  /**
   * 插入用户数据
   * @param user 用户数据
   * @return 受影响的行数
   */
  Integer insert(User user);
  
  /**
   * 根据用户名查询用户数据
   * @param username 用户名
   * @return 匹配的用户数据,如果没有匹配的数据,则返回null
   */
  User findByUsername(String username);

}

然后,需要在启动类的声明之前补充@MapperScan注解,以配置接口文件的位置:

@SpringBootApplication
@MapperScan("cn.demo.store.mapper")
public class StoreApplication {
  public static void main(String[] args) {
    SpringApplication.run(StoreApplication.class, args);
  }
}

src/main/resources下创建mappers文件夹,该文件夹的名称应该与复制的配置信息中保持一致!并在该文件夹中创建UserMapper.xml文件,以配置2个抽象方法的SQL映射:

<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"      
 "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">

<mapper namespace="cn.demo.store.mapper.UserMapper">

  <resultMap type="cn.demo.store.entity.User" id="UserEntityMap">
    <id column="uid" property="uid"/>
    <result column="is_delete" property="isDelete"/>
    <result column="created_user" property="createdUser"/>
    <result column="created_time" property="createdTime"/>
    <result column="modified_user" property="modifiedUser"/>
    <result column="modified_time" property="modifiedTime"/>
  </resultMap>

  <!-- 插入用户数据 -->
  <!-- Integer insert(User user); -->
  <insert id="insert" useGeneratedKeys="true" keyProperty="uid">
    INSERT INTO t_user (
      username, password, salt, gender,
      phone, email, avatar, is_delete,
      created_user, created_time, modified_user, modified_time
    ) VALUES (
      #{username}, #{password}, #{salt}, #{gender},
      #{phone}, #{email}, #{avatar}, #{isDelete},
      #{createdUser}, #{createdTime}, #{modifiedUser}, #{modifiedTime}
    )
  </insert>
  
  <!-- 根据用户名查询用户数据 -->
  <!-- User findByUsername(String username) -->
  <select id="findByUsername" resultMap="UserEntityMap">
    SELECT * FROM t_user WHERE username=#{username}
  </select>

</mapper>

src/test/java中的cn.demo.store包中创建子级的mapper包,并在mapper包中创建UserMapperTests测试类,并在测试类的声明之前添加@RunWith(SpringRunner.class)@SpringBootTest注解:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTests {

}

如果使用的是SpringBoot 2.2.x系列的版本,只需要添加1个注解即可,具体使用什么样的注解,请参考默认就存在那个单元测试类。

然后,在单元测试类中编写并执行单元测试:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTests {

  @Autowired
  private UserMapper mapper;

  @Test
  public void insert() {
    User user = new User();
    user.setUsername("project");
    user.setPassword("1234");
    user.setSalt("salt");
    user.setGender(0);
    user.setPhone("13800138002");
    user.setEmail("project@163.com");
    user.setAvatar("avatar");
    user.setIsDelete(0);
    user.setCreatedUser("系统管理员");
    user.setCreatedTime(new Date());
    user.setModifiedUser("超级管理员");
    user.setModifiedTime(new Date());
    Integer rows = mapper.insert(user);
    System.err.println("rows=" + rows);
    System.err.println(user);
  }
  
  @Test
  public void findByUsername() {
    String username = "project";
    User result = mapper.findByUsername(username);
    System.err.println(result);
  }
}

5. 用户-注册-业务层

业务,在普通用户眼里就是“1个功能”,例如“注册”就是一个业务,在开发人员看来,它可能是由多个数据操作所组成的,例如“注册”就至少由“查询用户名对应的用户数据”和“插入用户数据”这2个数据操作组成,多个数据操作组成1个业务,在组织过程中,可能涉及一些相关的检查,及数据安全、数据完整性的保障,所以,业务层的代码主要是组织业务流程,设计业务逻辑,以保障数据的完整性和安全性

在开发领域中,数据安全指的是:数据是由开发人员所设定的规则而产生或发生变化的!

在业务层的开发中,应该先创建业务层的接口,因为,在实际项目开发中,强烈推荐“使用接口编程”的效果!

所以,先在cn.demo.store包中创建service子包,并在service包中创建UserService业务接口,并在接口中声明“注册”这个业务的抽象方法:

/**
 * 处理用户数据的业务接口
 */
public interface UserService {
  
  /**
   * 用户注册
   * @param user 客户端提交的用户数据
   */
  void reg(User user);

}

在设计抽象方法时,仅以操作成功(例如注册成功、登录成功等)为前提来设计抽象方法的返回值,涉及的操作失败将通过抛出异常来表示!

**创建异常处理:**在cn.demo.store下创建ex子包,并创建异常的父类(ServiceException)

由于需要使用异常来表示错误,所以,在实现抽象方法的功能之前,还应该先定义相关的异常,有哪些“错误”(导致操作失败的原因),就创建哪些异常类,例如,注册时,用户名可能已经被占用,则需要创建对应的异常,当用户名没有被占用,允许注册时,执行的INSERT操作也可能失败,导致相应的异常,为了便于统一管理这些异常,还应该创建自定义异常的基类,这个基类异常应该继承自RuntimeException

/**
 * 业务异常的基类
 */
public class ServiceException extends RuntimeException {

  private static final long serialVersionUID = 980104530291206274L;

  public ServiceException() {
    super();
  }

  public ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
    super(message, cause, enableSuppression, writableStackTrace);
  }

  public ServiceException(String message, Throwable cause) {
    super(message, cause);
  }

  public ServiceException(String message) {
    super(message);
  }

  public ServiceException(Throwable cause) {
    super(cause);
  }

}
-----------------------------------------------------------------------------

/**
 * 用户名冲突的异常
 */
public class UsernameDuplicateException extends ServiceException {

  private static final long serialVersionUID = -1224474172375139228L;

  public UsernameDuplicateException() {
    super();
  }

  public UsernameDuplicateException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
    super(message, cause, enableSuppression, writableStackTrace);
  }

  public UsernameDuplicateException(String message, Throwable cause) {
    super(message, cause);
  }

  public UsernameDuplicateException(String message) {
    super(message);
  }

  public UsernameDuplicateException(Throwable cause) {
    super(cause);
  }

}

-------------------------------------------------------------------------------
/**
 * 插入数据异常
 */
public class InsertException extends ServiceException {

  private static final long serialVersionUID = 7991875652328476596L;

  public InsertException() {
    super();
  }

  public InsertException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
    super(message, cause, enableSuppression, writableStackTrace);
  }

  public InsertException(String message, Throwable cause) {
    super(message, cause);
  }

  public InsertException(String message) {
    super(message);
  }

  public InsertException(Throwable cause) {
    super(cause);
  }

}

接下来,就需要编写接口的实现类,并实现接口中的抽象方法!所以,在cn.demo.store.service包创建子级的impl包,并在impl包中创建UserServiceImpl类,实现UserService接口,在类的声明之前添加@Service注解,使得Spring框架能够创建并管理这个类的对象!并且,由于在实现过程中,必然用到持久层开发的数据操作,所以,还应该声明UserMapper对象,该对象的值应该是自动装配的:

/**
 * 处理用户数据的业务层实现类
 */
@Service
public class UserServiceImpl implements UserService {

  @Autowired
  private UserMapper userMapper;
  
  @Override
  public void reg(User user) {
    
  }

}

接下来,分析实现过程:

public void reg(User user) {
  	// 通过参数user获取尝试注册的用户名
    String username = user.getUsername();
    // 调用userMapper.findByUsername()方法执行查询
    User result = userMapper.findByUsername(username);
    // 判断查询结果是否不为null
    if (result != null) {
      // 是:查询到了数据,表示用户名已经被占用,则抛出UsernameDuplicationException
      throw new UsernameDuplicateException();
    }

    // 如果代码能执行到这一行,则表示没有查到数据,表示用户名未被注册,则允许注册
    // 创建当前时间对象:
    Date now = new Date();
    // 向参数user中补全数据:salt, password,涉及加密处理,暂不处理
    // 向参数user中补全数据:is_delete(0)
    user.setIsDelete(0);
    // 向参数user中补全数据:4项日志(now, user.getUsername())
    user.setCreaser(username);
    user.setCreatedTime(now);
    user.setModifiedUser(username);
    user.setModifiedTime(now);
    // 调用userMapper.insert()执行插入数据,并获取返回的受影响行数
    Integer rows = userMapper.insert(user);
    // 判断受影响的行数是否不为1
    if (rows != 1) {
      // 是:插入数据失败,则抛出InsertException
      throw new InsertException();
    }
}

然后,应该在src/test/java下的cn.demo.store包中创建子级的service包,并在这个包中创建UserServiceTests测试类,专门用于测试UserService接口中定义的功能:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTests {
  
  @Autowired
  private UserService service;
  
  @Test
  public void reg() {
    try {
      User user = new User();
      user.setUsername("service");
      user.setPassword("1234");
      user.setGender(0);
      user.setPhone("13800138003");
      user.setEmail("service@163.com");
      user.setAvatar("avatar");
      service.reg(user);
      System.err.println("OK.");
    } catch (ServiceException e) {
      System.err.println(e.getClass().getName());
    }
  }

}

最后,还应该处理密码加密(添加commons-codec依赖),完整业务代码例如:

/**
 * 处理用户数据的业务层实现类
 */
@Service
public class UserServiceImpl implements UserService {

  @Autowired
  private UserMapper userMapper;

  @Override
  public void reg(User user) {
    // 日志
    System.err.println("UserServiceImpl.reg()");
    // 通过参数user获取尝试注册的用户名
    String username = user.getUsername();
    // 调用userMapper.findByUsername()方法执行查询
    User result = userMapper.findByUsername(username);
    // 判断查询结果是否不为null
    if (result != null) {
      // 是:查询到了数据,表示用户名已经被占用,则抛出UsernameDuplicationException
      throw new UsernameDuplicateException();
    }

    // 如果代码能执行到这一行,则表示没有查到数据,表示用户名未被注册,则允许注册
    // 创建当前时间对象:
    Date now = new Date();
    // 向参数user中补全数据:salt, password
    String salt = UUID.randomUUID().toString();
    user.setSalt(salt);
    String md5Password = getMd5Password(user.getPassword(), salt);
    user.setPassword(md5Password);
    // 向参数user中补全数据:is_delete(0)
    user.setIsDelete(0);
    // 向参数user中补全数据:4项日志(now, user.getUsername())
    user.setCr
  • 27
    点赞
  • 179
    收藏
    觉得还不错? 一键收藏
  • 74
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 74
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值