Project(4)
1、分析项目
当需要开发某个项目时,首先,应该分析这个项目中,需要处理哪些种类的数据!例如:用户、商品、商品类别、收藏、订单、购物车、收货地址…
然后,将以上这些种类的数据的处理排个顺序,即先处理哪种数据,后处理哪种数据!通常,应该先处理基础数据,再处理所相关的数据,例如需要先处理商品数据,才可以处理订单数据,如果多种数据之间没有明显的关联,则应该先处理简单的,再处理较难的!
则以上这些数据的处理顺序应该是:用户 > 收货地址 > 商品类别 > 商品 > 收藏 > 购物车 > 订单
当确定了数据处理顺序后,就应该分析某个用户对应的功能有哪些,以“用户”数据为例,相关功能有:注册、登录、修改密码、修改资料、上传头像…
然后,还是需要确定以上功能的开发顺序,通常,遵循“增 > 查 > 删 > 改”的顺序,则以上功能的开发顺序应该是:注册 > 登录 > 修改密码 > 修改资料 > 上传头像。
每个功能的开发都应该遵循 创建数据表 > 创建实体类 > 持久层 > 业务层 > 控制器层 > 前端页面
一次只解决一个问题
大问题拆成小问题
2、用户 - 注册 - 创建数据表
3、用户 - 注册 - 创建实体类
4、用户 - 注册 - 持久层
a.规划SQL语句
b.接口与抽象方法
c.配置映射
5、用户 - 注册 - 业务层
业务层的基本定位
a.规划异常
b.接口与抽象方法
c.实现类与重写方法
6、用户 - 注册 - 控制器层
a.处理异常
b.设计请求
c.处理请求
7、用户 - 注册 - 前端页面
8、用户 - 登录 - 持久层
a.规划SQL语句
b.接口与抽象方法
c.配置映射
9、用户 - 登录 - 业务层
a.规划异常
b.接口与抽象方法
c.实现类与重写方法
10、用户 - 登录 - 控制器层
a.处理异常
b.设计请求
c.处理请求
11、用户 - 登录 - 前端页面
12、用户 - 修改密码 - 持久层
a.规划SQL语句
b.接口与抽象方法
c.配置映射
注:用于测试的数据,在测试完成之后,需要将这些数据删除。
13、用户 - 修改密码 - 业务层
a.规划异常
b.接口与抽象方法
c.实现类与重写方法
14、用户 - 修改密码 - 控制器层
a.处理异常
b.设计请求
c.处理请求
15、用户 - 修改密码 - 前端页面
16、用户 - 登陆拦截器
后续会有越来越多的操作需要先登录才能进行,为了保证统一处理则应该在项目中创建cn.tedu.store.interceptor.LoginInterceptor
拦截器类,实现HandlerInterceptor
接口并重写preHandle()
抽象方法:
/**
* 登录拦截器
*/
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 验证用户是否登录?登录--放行;未登录 -- 重定向到登录界面
// 获取session对象
HttpSession session = request.getSession();
// 根据uid判断是否登录,因为在登录成功时,会在session中存入uid
if(session.getAttribute("uid") == null) {
// 没有uid,重定向
response.sendRedirect("/web/login.html");
// 拦截
return false;
}
return true;
}
}
然后,还需要配置拦截器,在SpringBoot项目中,需要使用自定义类进行配置,则需要创建cn.tedu.store.conf.LoginInterceptorConfigurer
类,实现WebMvcConfigurer
接口并重写addInterceptors(InterceptorRegistry registry)
抽象方法,且在类前添加@Configuration
注解:
/**
* 登录拦截器的配置类
* @author DELL
*
*/
@Configuration
public class LoginInterceptorConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 创建拦截器对象
HandlerInterceptor interceptor = new LoginInterceptor();
// 创建白名单
List<String> excludePaths = new ArrayList<String>();
excludePaths.add("/users/reg");
excludePaths.add("/users/login");
excludePaths.add("/web/login.html");
excludePaths.add("/web/register.html");
excludePaths.add("/bootstrap3/**");
excludePaths.add("/css/**");
excludePaths.add("/images/**");
excludePaths.add("/js/**");
// 注册拦截器,并设置黑白名单
registry.addInterceptor(interceptor)
.addPathPatterns("/**")
.excludePathPatterns(excludePaths);
}
}
17、用户 - 修改个人资料 - 持久层
a.规划SQL语句
修改个人资料的SQL语句大致是:
UPDATE
t_user
SET
phone=?, email=?, gender=?,
modified_user=?, modified_time=?
WHERE
uid=?
在执行修改之前,还应该检查用户的数据是否正确,例如:用户数据是否存在、是否被标记为已删除。检查所需的功能已经完成,无需重复开发。
另外,“修改个人资料”和“修改密码”不同,首先需要完成界面的显示!即用户打开界面时,应该显示当前登录的用户的相关信息!则需要:
SELECT
username, phone,
email, gender
FROM
t_user
WHERE
uid=?
根据uid查询数据的持久层功能已经存在,后续在开发时,只需要补充查询的字段列表即可。
b.接口与抽象方法
在UserMapper
接口添加新的抽象方法:
/**
* 修改个人信息
* @param user 用户提交的信息
* @return 受影响的行数
*/
Integer updateInfo(User user);
c.配置映射
在UserMapper.xml
中配置以上方法的映射:
<!-- 修改个人信息 -->
<!-- Integer updateInfo(User user); -->
<update id="updateInfo">
UPDATE
t_user
SET
<if test="phone != null">
phone=#{phone},
</if>
<if test="email != null">
email=#{email},
</if>
<if test="gender != null">
gender=#{gender},
</if>
modified_user=#{modifiedUser},
modified_time=#{modifiedTime}
WHERE
uid=#{uid}
</update>
修改原来的findByUid
的配制,查询更多的字段:
<!-- 根据Uid查询用户数据 -->
<!-- User findByUid(Integer uid); -->
<select id="findByUid" resultType="cn.tedu.store.entity.User">
SELECT
username, phone, email, gender,
is_delete AS isDelete, password,
salt
FROM
t_user
WHERE
uid=#{uid}
</select>
完成后,编写并执行单元测试:
@Test
public void updateInfo() {
Date now = new Date();
Date modifiedTime = now;
User user = new User();
user.setUid(16);
user.setPhone("15934460492");
user.setEmail("[email protected]");
user.setGender(0);
user.setModifiedUser("管理员");
user.setModifiedTime(modifiedTime);
Integer rows = userMapper.updateInfo(user);
System.err.println("rows = " + rows);
}
18、用户 - 修改个人资料 - 业务层
a.规划异常
此次客户端涉及的操作有两个:查询当前登录的用户信息,执行修改用户的基本资料。
首先,由于是登录后才能进行修改个人资料操作,所以查询功能是不存在错误的。
则关于修改功能,可能涉及的异常是:UserNotFoundException
和UpdateException
。
b.接口与抽象方法
在IUserService
中添加两个抽象方法:
/**
* 根据用户id查询用户信息
* @param uid 用户id
* @return 返回匹配的用户数据,如果没有则返回null
*/
User getByUid(Integer uid);
/**
* 修改用户的基本资料
* @param user 封装了用户基本资料的对象,至少需要封装用户的id和用户名
* 可选择性的封装用户的手机号码、电子邮箱、年龄等。
* @throws UserNotFoundException 尝试访问的用户数据不存在,或被标记为已删除
* @throws UpdateException 更新失败异常
*/
void changeInfo(User user) throws UserNotFoundException, UpdateException;
c.实现类与重写方法
在UserServiceImpl
中重写以上2个抽象方法:
public User getByUid(Integer uid){
// 根据uid查询用户数据
// 如果查询到数据,则需要将查询结果中的password、salt、is_delete设置为null
// 将查询结果返回
}
代码实现:
@Override
public User getByUid(Integer uid) {
// 根据uid查询用户数据
// 如果查询到数据,则需要将查询结果中的password、salt、is_delete设置为null
// 将查询结果返回
User result = userMapper.findByUid(uid);
if (result != null) {
result.setPassword(null);
result.setSalt(null);
result.setIsDelete(null);
}
return result;
}
public Integer changeInfo(User user) throws UserNotFoundException, UpdateException{
// 根据参数中的uid,即user.getUid()查询数据
// 判断查询结果是否存在,查询结果是否被标记为已删除
// 创建当前时间对象
// 将时间对象和修改人封装到user中
// 执行修改操作:userMapper.updateInfo() > UPDATE t_user SET phone=?, email=?, gender=?, modified_user=?, modified_time=? WHERE uid=?
// 判断修改操作的返回值是否不为1
// 抛出UpdateException
}
代码实现:
@Override
public void changeInfo(User user) throws UserNotFoundException, UpdateException {
// 根据参数的uid,即user.getUid()查询数据
// 判断查询结果是否存在,是否被标记为已删除
User result = userMapper.findByUid(user.getUid());
if (result == null) {
throw new UserNotFoundException("更新用户数据失败!该用户不存在!");
}
if (result.getIsDelete() == 1) {
throw new UserNotFoundException("更新用户数据失败!该用户不存在!");
}
// 创建当前时间对象
Date now = new Date();
// 将时间封装到user中
user.setModifiedTime(now);
// 将user中的用户名封装到modifiedUser中
user.setModifiedUser(user.getUsername());
// 执行修改操作:userMapper.updateInfo() > UPDATE t_user SET
// phone=?,email=?,gender=?,modified_user=?,modified_time=? WHERE uid=?
Integer rows = userMapper.updateInfo(user);
// 判断以上修改操作的返回值是否不为1
// 是则:抛出异常:UpdateException
if (rows != 1) {
throw new UpdateException("更新用户数据失败!出现未知错误!请联系系统管理员!");
}
}
在UserServiceTests
中编写并执行单元测试:
@Test
public void testGetByUid() {
User user = service.getByUid(17);
System.err.println(user);
}
@Test
public void testChangerInfo() {
try {
User user = new User();
user.setUid(17);
user.setUsername("Gin");
user.setPhone("1593446049Gin");
user.setEmail("[email protected]");
user.setGender(1);
service.changeInfo(user);
System.err.println("OK");
}catch(ServiceException e) {
System.err.println(e.getClass().getName());
System.err.println(e.getMessage());
}
}
19、用户 - 修改个人资料 - 控制器层
a.处理异常
无
b.设计请求
关于获取当前登录的用户的信息的请求:
请求路径:/users/get_info
请求参数:HttpSession session
请求方式:GET
响应数据:JsonResult<User>
关于执行修改个人信息的请求:
请求路径:/users/change_info
请求参数:User user,HttpSession session
请求方式:POST
响应数据:JsonResult<Void>
c.处理请求
//modified_ser、uid的来源应该是:session中向参数user封装
@GetMapping("get_info")
public JsonResult<User> getInfo(HttpSession session) {
// 从session中获取uid
Integer uid = Integer.valueOf(session.getAttribute("uid").toString());
// 查询匹配的数据
User data = userService.getByUid(uid);
// 响应
return new JsonResult<User>(SUCCESS, data);
}
//使用RequestMapping是为了方便在地址栏进行测试
@RequestMapping("change_info")
public JsonResult<Void> changeInfo(User user, HttpSession session) {
// 从session中获取uid和username
Integer uid = Integer.valueOf(session.getAttribute("uid").toString());
String username = session.getAttribute("username").toString();
// 将uid和username封装到user中
user.setUid(uid);
user.setUsername(username);
// 执行修改
userService.changeInfo(user);
// 响应
return new JsonResult<Void>(SUCCESS);
}
优化:
以上代码中,需要频繁从session中获取uid,username,则可以在父类BaseController
中添加新方法:
BaseController
:
/**
* 从session中获取uid
* @param session
* @return session中的uid
* 添加 final 表示该方法不可以被重写
*/
protected final Integer getUidFromSession(HttpSession session) {
return Integer.valueOf(session.getAttribute("uid").toString());
}
/**
* 从session中获取username
* @param session
* @return session中的username
*/
protected final String getUsernameFromSession(HttpSession session) {
return session.getAttribute("username").toString();
}
UserController
:
// 从session中获取uid和username
Integer uid = getUidFromSession(session);
String username = getUsernameFromSession(session);
打开浏览器,先登录,分别通过http://localhost:8080/users/get_info
和http://localhost:8080/users/[email protected]&phone=13100131007&gender=1
进行测试。
20、用户 - 修改个人资料 - 前端页面
<script type="text/javascript">
$(document).ready(function() {
showUserInfo();
$("#btn-change-info").click(function() {
$.ajax({
// url: 请求交到哪里去
// data: 请求提交的参数,以上的form表单中,在需要提交的参数的控件中添加name属性;
// form表单上添加id属性
// type: 请求方式
// dataType: 服务器端响应的结果的类型
// success: 响应成功时的处理函数
"url" : "/users/change_info",
"data" : $("#form-change-info").serialize(),
"type" : "post",
"dataType" : "json",
"success" : function(json) {
if (json.state == 2000) {
alert("修改成功!");
//跳转到某个页面
} else {
alert(json.message);
}
},
"error" : function() {
alert("您的登录已过期!请重新登录!");
// location.href = "/web/login.html";
}
});
});
function showUserInfo() {
$.ajax({
// url: 请求交到哪里去
// data: 请求提交的参数,以上的form表单中,在需要提交的参数的控件中添加name属性;
// form表单上添加id属性
// type: 请求方式
// dataType: 服务器端响应的结果的类型
// success: 响应成功时的处理函数
"url" : "/users/get_info",
"type" : "get",
"dataType" : "json",
"success" : function(json) {
if (json.state == 2000) {
// 给用户名、电话、邮箱输入框添加id属性
$("#inp-username").val(json.data.username);
$("#inp-phone").val(json.data.phone);
$("#inp-email").val(json.data.email);
// 给性别输入框添加id属性
// if (json.data.gender == 1) {
// $("#gender-male").attr("checked", "checked");
// } else {
// $("#gender-female").attr("checked", "checked");
// }
// 修改性别输入框中的value值为1、0
var radio = json.data.gender == 1 ? $("#gender-male") : $("#gender-female");
radio.attr("checked", "checked");
} else {
alert(json.message);
}
}
});
}
});
</script>