1. 项目分析
首先应该分析该项目中需要处理哪些种类的数据,在本项目中有:商品、商品类别、用户、收货地址、购物车、收藏、订单……
然后,确定以上这些数据的开发顺序,原则上应该先开发基础数据和简单的数据相关的功能,所以,以上数据的开发顺序应该是:用户 > 收货地址 > 商品类别 > 商品 > 收藏 > 购物车 > 订单。
接下来,分析第1种数据的相关功能,即“用户”数据的管理中,有哪些功能需要开发:注册,登录,修改密码,个人资料,上传头像。
分析完成后,再确定以上功能的开发顺序,通常开发顺序的基本原则是:增、查、删、改,所以,以上功能的开发顺序应该是:注册 > 登录 > 修改密码 > 个人资料 > 上传头像。
在具体的开发某个功能时,应该遵循的顺序是:创建数据表 > 创建实体类 > 持久层 > 业务层 > 控制器层 > 前端界面。
2. 用户-创建数据表
首先应该创建数据库:
CREATE DATABASE tedu_store;
然后使用该数据库:
USE tedu_store
再创建数据表:
CREATE TABLE t_user (
uid INT AUTO_INCREMENT COMMENT '用户id',
username VARCHAR(50) UNIQUE NOT NULL COMMENT '用户名',
password CHAR(32) NOT NULL COMMENT '密码',
salt CHAR(36) COMMENT '盐值',
gender INT COMMENT '性别,0-女,1-男',
phone VARCHAR(20) COMMENT '手机号码',
email VARCHAR(50) COMMENT '电子邮箱',
avatar VARCHAR(100) COMMENT '头像',
is_delete INT COMMENT '是否删除,0-未删除,1-已删除',
created_user VARCHAR(50) COMMENT '创建人',
created_time DATETIME COMMENT '创建时间',
modified_user VARCHAR(50) COMMENT '最后修改人',
modified_time DATETIME COMMENT '最后修改时间',
PRIMARY KEY (uid)
) DEFAULT CHARSET=utf8;
3. 用户-创建实体类
先下载共享的项目文件,解压得到项目文件夹,将其移动到Workspace中,通过Import中的Existing Maven Projects导入项目,然后,在此前的springboot项目中复制数据库连接的配置信息到新项目中,并修改需要连接到的数据库名称为tedu_store。
然后,在src/test/java下的测试类,先执行原有的空的测试方法,以检验环境是否正确,然后,在该测试类测试获取数据库连接对象,以检验连接配置信息是否正确:
@Autowired
DataSource dataSource;
@Test
public void getConnection() throws SQLException {
Connection conn = dataSource.getConnection();
System.err.println(conn);
}
然后,先创建cn.tedu.store.entity.BaseEntity
实体的父类,称之为实体类的“基类”,实现序列化接口,该类的作用就是用于被继承的,所以应该添加abstract
修饰符,并且,该类也只需要被子类访问,子类都与它在同一个包中,所以,使用默认的访问权限即可:
abstract class BaseEntity implements Serializable {
private String createdUser;
private Date createdTime;
private String modifiedUser;
private Date modifiedTime;
}
再cn.tedu.store.entity.User
实体类,继承自以上BaseEntity
类:
public class User extends BaseEntity {
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生成hashCode和equals/Serializable
}
4. 用户-注册-持久层
(a) 规划SQL语句
首先,应该分析该功能需要执行的SQL语句,当用户注册时,本质是向数据表中插入数据,则需要执行SQL语句大致是:
insert into t_user (
除了uid以外的所有字段
) values (
?,?,?,?,?...?
);
由于设计了“用户名唯一”的规则,在插入数据之前,还应该检查该用户名是否已经被占用,则可以“根据用户名查询数据,并判断是否查询到有效结果”作为判断依据,需要执行的SQL语句大致是:
select uid from t_user where username=?
(b) 接口与抽象方法
创建cn.tedu.store.mapper.UserMapper
接口,并在接口中添加抽象方法:
Integer addnew(User user);
User findByUsername(String username);
注意:完成后,应该在启动类之前添加
@MapperScan("cn.tedu.store.mapper")
注解,以配置MyBatis中的持久层接口在哪个包中。
© 配置映射
在src/main/resources下创建mappers文件夹,并在该文件夹中粘贴持久层映射的XML文件,确定文件名应该是UserMapper.xml,配置好根节点的namespace
属性,并配置以上接口中2个抽象方法的映射:
<mapper namespace="cn.tedu.store.mapper.UserMapper">
<!-- 插入用户数据 -->
<!-- Integer addnew(User user); -->
<insert id="addnew"
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"
resultType="cn.tedu.store.entity.User">
SELECT
uid
FROM
t_user
WHERE
username=#{username}
</select>
</mapper>
注意:需要检查在application.properties中是否配置了XML映射文件的位置mybatis.mapper-locations=classpath:/mappers/*.xml
完成后,在src/test/java下创建cn.tedu.store.mapper.UserMapperTests
单元测试类,并在该类中编写并执行持久层2个功能的测试方法:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTests {
@Autowired
UserMapper mapper;
@Test
public void addnew() {
User user = new User();
user.setUsername("admin");
user.setPassword("1234");
System.err.println(user);
Integer rows = mapper.addnew(user);
System.err.println("rows=" + rows);
System.err.println(user);
}
@Test
public void findByUsername() {
String username = "root888";
User result = mapper.findByUsername(username);
System.err.println(result);
}
}
5. 用户-注册-业务层
(a) 规划异常
首先,应该分析用户在执行此次操作时,可能有哪些失败的原因。
应该为所有的自定义异常创建一个公共的父类,以确定自定义异常的类别,所以,创建cn.tedu.store.service.ex.ServiceException
,它应该继承自RuntimeException
。
在当前项目中,设置了“用户名唯一”的规则,如果用户尝试注册的用户名已经被占用,则应该抛出自定义的cn.tedu.store.service.ex.UsernameDuplicateException
异常,继承自ServiceException
。
此次“注册”将执行INSERT
语句,则可能存在某些不控的因素导致插入失败,所以,还应该为这种失败的可能设计对应的cn.tedu.store.service.ex.InsertException
异常。
(b) 接口与抽象方法
创建cn.tedu.store.service.IUserService
业务层接口,并在接口中添加抽象方法:
void reg(User user) throws UsernameDuplicateException, InsertException;
在设计抽象方法时,方法的返回值仅以操作成功为前提进行设计,如果需要考虑操作失败,则使用抛出异常的方式来解决。
© 实现抽象方法
创建cn.tedu.store.service.impl.UserServiceImpl
业务层实现类,并实现以上接口,在类之前添加@Service
注解,在类中添加@Autowired private UserMapper userMapper;
持久层对象:
@Service
public class UserServiceImpl implements IUserService {
@Autowired
private UserMapper userMapper;
public void reg(User user) {
}
}
然后,重写抽象方法:
public void reg(User user) {
// 根据参数user中的getUsername()获取尝试注册的用户名
// 根据以上用户名查询用户数据
// 判断查询结果是否不为null
// 是:用户名已经被占用,抛出UsernameDuplicateException
// 用户名未被占用,允许注册
// TODO 向参数user中补全属性:盐值
// TODO 取出参数user中的原始密码
// TODO 将原始密码加密
// TODO 向参数user中补全属性:加密后的密码
// 向参数user中补全属性:isDelete-0
// 向参数user中补全属性:4项日志
// 执行注册
}
初步实现为:
@Override
public void reg(User user) throws UsernameDuplicateException, InsertException {
// 根据参数user中的getUsername()获取尝试注册的用户名
String username = user.getUsername();
// 根据以上用户名查询用户数据
User result = userMapper.findByUsername(username);
// 判断查询结果是否不为null
if (result != null) {
// 是:用户名已经被占用,抛出UsernameDuplicateException
throw new UsernameDuplicateException(
"注册失败!尝试注册的用户名(" + username + ")已经被占用!");
}
// 用户名未被占用,允许注册
// TODO 向参数user中补全属性:盐值
// TODO 取出参数user中的原始密码
// TODO 将原始密码加密
// TODO 向参数user中补全属性:加密后的密码
// 向参数user中补全属性:isDelete-0
user.setIsDelete(0);
// 向参数user中补全属性:4项日志
Date now = new Date();
user.setCreatedUser(username);
user.setCreatedTime(now);
user.setModifiedUser(username);
user.setModifiedTime(now);
// 执行注册
Integer rows = userMapper.addnew(user);
if (rows != 1) {
throw new InsertException(
"注册失败!插入用户数据时出现未知错误!请联系管理员!");
}
}
完成后,在src/test/java下创建cn.tedu.store.service.UserServiceTests
单元测试类,并在该类中编写并执行以上功能的测试方法:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTests {
@Autowired
IUserService service;
@Test
public void reg() {
try {
User user = new User();
user.setUsername("Service");
user.setPassword("1234");
service.reg(user);
System.err.println("OK");
} catch (ServiceException e) {
System.err.println(e.getClass().getName());
System.err.println(e.getMessage());
}
}
}
6. 用户-注册-控制器层
(a) 统一处理异常
创建cn.tedu.store.controller.BaseController
控制类的基类,后续创建的每个控制器类都应该继承自这个基类,在基类中添加处理处理异常的方法,则每个子级的控制器类都相当于拥有这个方法:
public abstract class BaseController {
@ExceptionHandler(ServiceException.class)
@ResponseBody
public JsonResult<Void> handleException(Throwable e) {
JsonResult<Void> jr = new JsonResult<Void>();
jr.setMessage(e.getMessage());
if (e instanceof UsernameDuplicateException) {
jr.setState(2);
} else if (e instanceof InsertException) {
jr.setState(3);
}
return jr;
}
}