# 电脑商城项目实战
**Repository Path**: darkski/computer-mall-project-practice
## Basic Information
- **Project Name**: 电脑商城项目实战
- **Description**: SpringBoot+MyBatis+MySQL
- **Primary Language**: Java
- **License**: MulanPSL-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 1
- **Created**: 2022-08-08
- **Last Updated**: 2024-05-05
## Categories & Tags
**Categories**: Uncategorized
**Tags**: Java
## README
电脑商城项目实战
#### 介绍
比较简单的练习项目
SpringBoot+MyBatis+MySQL
#### 项目分析
(1)项目功能:登录、注册、热销商品、用户管理(密码、个人i西南西、头像、收货地址)、购物车(展示、增加、删除)、订单模块
(2)开发顺序:注册、登录、用户管理、购物车、商品、订单模块
(3)模块的开发
* 持久层开发:依据前端页面的设置规划相关的SQL语句,以及进行配置
* 业务层开发:核心功能的控制、业务的操作以及异常处理
* 控制层开发:接收请求、处理响应
* 前端开发:JS、JQuery、AJAX这些技术连接后台
#### 项目环境
(1)JDK:1.8及以上版本
(2)Maven:配置到idea,3.6.1版本
(3)数据库:Maria DB,MySQL5.1及以上版本
(4)开发平台:idea
# 一、搭建项目
(1)项目名称:store
(2)项目结构:com.cy.store
````
java web
mybatis
mysql driver
````
(3)资源文件:resources文件夹下
* static:静态资源
* templates:模板
(4)单元测试:test.com.cy.store
(5)配置数据库的连接元信息
````properties
# 应用服务 WEB 访问端口
server.port=8080
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mapper/*xml
#指定Mybatis的实体目录
mybatis.type-aliases-package=com.cy.store.mybatis.entity
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/store?serverTimezone=UTC
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=123
````
(6)创建数据库
见tools\sql_src文件夹
(7)测试连接
* SpringBoot主类启动成功
* 单元测试类中测试数据库的来凝结是否可以正常加载
(8)访问项目的静态资源是否可以正常的架子啊。所有的静态资源放在static目录下
> 注意:idea对js代码的兼容性较差,如果不能正常的加载
>
> 1、idea缓存清理
>
> 2、clear-instal
>
> 3、rebuild重新构建
>
> 4、重启idea和操作系统
# 二、用户注册
### 1、创建数据表
### 2、创建用户实体类
#### 2.1 BaseEntity基类
````java
/**
* 项目中许多实体类都会有日志相关的四个属性,所以在创建实体类之前,应先创建这些实体类的基类,将4个日志属性声明在基类中
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@EqualsAndHashCode
public class BaseEntity implements Serializable {
private String createdUser;
private Date createdTime;
private String modifiedUser;
private Date modifiedTime;
}
````
#### 2.2 User类
````java
/**
* 用户实体类:get和set方法,equals和HashCode()方法、ToString方法
*/
@Data
@ToString
@EqualsAndHashCode
//@Component SpringBoot约定大于配置
public class User extends BaseEntity implements Serializable {
private Integer uid;
private String username;
private String password;
private String salt;
private String phone;
private String email;
private Integer gender;
private String avatar;
private Integer is_delete;
}
````
### 3、注册——持久层
通过MyBatis来操作数据库。即在做Mybatis的开发流程
#### 3.1 规划需要执行的SQL语句
(1)用户的注册功能,相当于在做数据的插入操作
````sql
insert into t_user (username, password) values ()
````
(2)在注册时,要首先进行查询当前用户名是否存在,存在才能进行注册——查询语句
````sql
select * from t_user where username = ?
````
#### 3.2 设计接口和抽象方法
定义Mapper接口。在项目的目录结构下构建一个mapper包,在这个包下再根据不同模块来创建mapper接口。
(1)创建一个UserMapper的接口
````java
//@Mapper 不推荐在接口中是哟个Mapper注解,因为一个项目中会又多个Mapper,推荐在MainApplication中采用MapperScan注解
public interface UserMapper {
/**
* 插入用户的数据
* @param user
* @return 受到影响的行数
*/
Integer insert(User user);
/**
* 根据用户名查找用户数据
* @param username
* @return 如果找到,返回用户数据;如果没有找到,返回null
*/
User findByUserName(String username);
}
````
(2)在启动类配置上mapper接口文件的位置
````java
// MapperScan注解指定当前项目中的Mapper接口路径的指定位置,在项目启动的时候自动加载接口文件
@MapperScan("com.cy.mapper")
````
#### 3.3 编写映射
定义xml映射文件,于对应的接口进行关联。所有映射文件需要放置在resources目录下,在这个目录下擦黄健一个mapper文件夹,在这个文件中存放Mapper的映射文件
(1)创建映射文件,和接口名称保持一致。
````xml
````
(2)配置接口中的方法对应的SQL语句,借助标签来完成
````xml
insert into t_user (
username,password,salt,phone,email,gender,avatar,is_delete,created_user,created_time,modified_user,modified_time
) values (
#{username},#{password},#{salt},#{phone},#{email},#{gender},#{avatar},#{isDelete},#{createdUser},#{createdTime},#{modifiedUser},#{modifiedTime}
)
````
(3)单元测试
> 每个独立的层编写完毕后,需要编写单元测试,来测试当前的功能。
> 在test包结构下创建一个mapper包,在这个包下在创建持久层测试
### 4、注册——业务层
#### 4.1 规划异常
(1)RuntimeException异常,作为异常的子类,然后再去定义具体异常的类型来继承这个异常
> 即业务层异常的基类,ServiceException异常,这个异常继承RuntimeException异常,异常机制建立

````java
/**
* 业务层异常的基类
*/
public class ServiceException extends RuntimeException {
public ServiceException() {
super();
}
public ServiceException(String message) {
super(message);
}
public ServiceException(String message, Throwable cause) {
super(message, cause);
}
public ServiceException(Throwable cause) {
super(cause);
}
protected ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
````
> 根据业务层不同的功能来详细定义具体的异常类型,统一的去继承ServiceException异常类
(2)用户在进行注册的时候可能会产生用户名被占用的错误,抛出一个异常:UsernameDuplicatedException异常
````java
// 用户名被占用异常
public class UsernameDuplicatedException extends ServiceException {...同上...}
````
(3)正在执行数据插入操作的时候,服务器、数据库宕机,处于正在执行插入操作的过程中产生的异常InsertException异常
````java
/**
* 数据插入过程中产生的异常
*/
public class InsertException extends ServiceException {...同上...}
````
#### 4.2 设计接口和抽象方法
(1)在service包下创建一个IUserService接口
````java
/**
* 用户模块业务层接口
*/
public interface IUserService {
// 注册用户
void reg(User user);
}
````
(2)创建一个实现类
````java
/**
* 用户模块业务层的实现类
*/
public class UserServiceImpl implements IUserService {
@Autowired
private UserMapper userMapper;
@Override
public void reg(User user) {
String username = user.getUsername();
// 先判断
User result = userMapper.findByUserName(username);
if (result != null) {
// 抛出异常
throw new UsernameDuplicatedException("用户名被占用!");
}
// 数据的补全操作
user.setIsDelete(0);
user.setCreatedUser(user.getUsername());
Date date = new Date();
user.setCreatedTime(date);
user.setModifiedTime(date);
// 再插入
Integer rows = userMapper.insert(user);
if (rows != 1) {
throw new InsertException("用户注册过程中产生了未知异常!");
}
}
}
````
(3)单元测试:UserServiceTests类
### 5、注册——控制层
#### 5.1 创建响应
状态码、状态描述信息、数据。
这部分功能封装再一个类中,将这类作为方法返回值,返回给前端浏览器
````java
/**
* Json格式的数据进行响应
*/
public class JsonResult implements Serializable {
// 状态码
private Integer state;
// 描述信息
private String message;
// 泛型表示任何类型的数据
private E data;
public JsonResult() {
}
public JsonResult(Integer state) {
this.state = state;
}
// 异常的捕获
public JsonResult(Throwable e) {
this.message = e.getMessage();
}
public JsonResult(Integer state, E data) {
this.state = state;
this.data = data;
}
}
````
#### 5.2 设计请求
依据当前的业务功能模块来进行设计
````
请求路径:/user/reg
请求参数: User user
请求类型: POST
响应结果: JsonResult
````
#### 5.3 处理请求
(1)创建控制层对应的类UserController。依赖于业务层的接口
````java
/**
* 控制层
* @author DarkSky
* @version 1.0
*/
//@Controller
@RestController //@Controller + ResponseBody
@RequestMapping("users")
public class UserController {
@Autowired
private IUserService userService;
@RequestMapping("reg")
//@ResponseBody // 表示将此方法的响应结果以Json格式进行数据的响应给到前端
public JsonResult reg(User user) {
// 创建响应结果对象
JsonResult result = new JsonResult<>();
try {
userService.reg(user);
result.setState(200);
result.setMessage("用户注册成功!");
} catch (UsernameDuplicatedException e) {
result.setState(4000);
result.setMessage("用户名被占用!");
} catch (InsertException e) {
result.setState(5000);
result.setMessage("注册时产生未知异常!");
}
return result;
}
}
````
#### 5.4 控制层优化设计
在控制层剥离一个父类,在此父类中统一的去处理关于异常的相关操作,编写一个BaseController类,统一处理异常
````java
/**
* 控制层基类
*/
@RestController
public class BaseController {
// 操作成功的状态码
public static final int OK = 200;
// 请求处理方法,这个方法的返回值需要传递给前端的数据,则返回值应该为JsonResult
// 自动将异常对象传递给此方法的参数列表
@ExceptionHandler(ServiceException.class) // 用于统一处理抛出的异常:凡是括号内的异常都拦截到这里处理
public JsonResult handleException(Throwable e) {
JsonResult result = new JsonResult<>();
if (e instanceof UsernameDuplicatedException) {
result.setState(4000);
result.setMessage("用户名被占用!");
} else if (e instanceof InsertException) {
result.setState(5000);
result.setMessage("注册时产生未知异常!");
}
return result;
}
}
@RestController
@RequestMapping("users")
public class UserController extends BaseController {
@Autowired
private IUserService userService;
@RequestMapping("reg")
public JsonResult reg(User user) {
userService.reg(user);
return new JsonResult<>(OK);
}
}
````
### 6、注册——前端页面
在register页面中编写发送请求的方法,点击时间来完成。选中对应的按钮($("选择器")),再去添加点击事件,$.ajax()函数发送异步请求。
> JQuery封装了一个函数,称为@.ajac()函数,通过对象调用ajax函数,可以异步加载相关的请求,依靠的是JavaScript提供的一个对象XHR(XmlHttpResponse)
>
> 异步:即页面不动,数据动
#### 6.1 ajax()
(1)ajax的使用
> * ajax接收多个参数
>
> * 参数与参数之间 用","进行分割
>
> * 参数的声明顺序没有要求
>
> * 每一组参数的组成:参数名称: "参数的值"
> * 参数名称:不能随意定义,必须使用ajax规定好的
> * 参数的值:使用字符串来表示
````javascript
需要传递一个方法体作为方法的参数来使用
$.ajax({
// 方法体
url: "",
type: "",
data: "",
dataType: "",
success: function() {
},
error: function() {
}
});
````
(2)ajax函数参数的含义
| 参数 | 功能描述 | 示例 |
| -------- | ------------------------------------------------------------ | ----------------------------------- |
| url | 表示请求的地址(URL地址),不能包含参数列表的内容 | url: "localhost:8080/users/reg" |
| type | 请求类型(GET,POST) | type: "POST" |
| data | 向指定的请求url地址提交的数据 | data: "username=laohu&password=123" |
| dataType | 提交的数据的类型,数据类型一般指定为Json类型 | dataType: "json" |
| success | 当服务器正常响应客户端时,会自动调用success参数的方法,并且将服务器返回的数据以参数的形式传递到这个方法的参数上 | |
| error | 当服务器未正常响应客户端时,会自动调用error参数的方法,并且将服务器返回的数据以参数的形式传递到这个方法的参数上 | |
> js代码可以独立声明在一个js的文件里或者声明在一个script标签中
(3)示例
````javascript
````
(4)js代码无法正常被服务器解析执行
解决方案
1. 在项目的maven下clean清理项目,后install重新部署
2. 在项目的file选项下-cash清理缓存
3. 重新狗啊金项目:build选项下-rebuild选项
4. 重启idea
5. 重启电脑
# 三、用户登录
> 当用户输入用户名和密码将数据提交给后台数据库进行查询,如果存在对应的用户名和密码则表示登录成功,登录成功之后跳转到系统的主页,即index.html页面,跳转在前端使用Jquery来实现
### 1、登录——持久层
#### 1.1 规划需要执行的SQL语句
依据用户提交的用户名和密码做select查询,数据库只参与数据相关的操作,业务逻辑都在业务层(如查询在持久层,比较在业务层)
````mysql
select * from t_user where username = ?
````
> 说明:如果在分析过程中,发现某个功能模块被开发完成,可以省略该步骤,但是分析过程不能省略
#### 1.2 接口设计和抽象方法
### 2、登录——业务层
#### 2.1 规划异常
(1)用户名对应的密码错误,密码匹配失败的异常:PasswordNotMatchException异常,属于运行时异常,业务层异常。
(2)用户名未找到,抛出异常:UsernameNotFoundException异常,属于业务层异常
(3)异常的编写
* 业务层异常需要继承ServiceException异常类
* 在具体的异常类中重写构造方法
#### 2.2 设计业务层接口和方法
(1)直接在IUserSercvice中编写抽象方法,login(String username, String password),将当前登录成功的用户已当前用户对象的形式返回给客户端,这样在切换页面时不用重复的发送请求,状态管理:可以将数据保存在cookie或者session中,可以避免重复读很高的数据多次频繁操作数据库进行获取
> 可以这样规定:
>
> 用户名、用户id存放在session中
>
> 用户头像存放在cookie中
(2)在实现类中实现接口的方法
````java
// 用户登录
User login(String username, String password);
````
(3)单元测试
#### 2.3 抽象方法的实现
````java
public User login(String username, String password) {
// 根据用户名称查询用户数据是否存在
User result = userMapper.findByUserName(username);
if (result == null || result.getIsDelete()==1) {
throw new UsernameNotFoundException("用户数据不存在!");
}
// 比较密码是否匹配
// (1)获取数据库中对应的密码
String oldPassword = result.getPassword();
// 和用户传递的密码进行比较
// (2)获取盐值
String salt = result.getSalt();
// (3)传递的密码加盐
String newMd5Password = getMD5Password(password, salt);
// (4)比较
if (!newMd5Password.equals(oldPassword)) {
throw new PasswordNotMatchException("密码错误!");
}
// 数据中转:数据的压缩,提升系统性能
User user = new User();
user.setUid(result.getUid());
user.setUsername(result.getUsername());
user.setAvatar(result.getAvatar());
return user;
}
````
### 3、登录——控制层
业务层抛出的异常是什么,需要再统一异常处理类中进行统一的捕获和处理,如果业务层抛出的异常已经被处理过,则不需要重复添加
#### 3.1 处理异常
````java
else if (e instanceof InsertException) {
result.setState(5000);
result.setMessage("注册时产生未知异常!");
} else if (e instanceof UsernameNotFoundException) {
result.setState(5001);
result.setMessage("用户数据不存在异常!");
}
````
#### 3.2 设计请求
````
请求路径:/users/login
请求方式:POST
请求数据:String username, String password
响应结果:压缩后的User
````
#### 3.3 处理请求
````java
@RequestMapping("/login")
public JsonResult login(String username, String password) {
User data = userService.login(username, password);
return new JsonResult(OK, data);
}
````
### 4、登录——前端页面
在login.html页面中发送ajax请求
````js
````
# 四、用户登录会话session
session对象主要存储在服务器端,可以用于保存服务器的临时数据的对象,所保存的数据可以在整个项目中都可以通过访问来获取,把session的数据看作一个共享的数据。首次登录时所获取到的数据,转移到session对象中即可。session.getAttrbute("key")可以将获取session中数据这种行为进行封装,封装在父类BaseController类中(因为在此项目结构中,只有在控制层才需要使用到session对象)
对session对象中数据的获取、数据的设置进行封装
* 数据的获取——封装在父类中
* 数据的设置——当用户登录成功后进行数据的设置,设置到全局的session对象
````java
/**
* 获取session对象的uid
* @param session 传入的session参数
* @return uid
*/
protected final Integer getUidFromSession(HttpSession session) {
return Integer.valueOf(session.getAttribute("uid").toString());
}
/**
* 获取session对象的username
* @param session session参数对象
* @return username
*/
protected final String getUsernameFromSession(HttpSession session) {
return session.getAttribute("username").toString();
}
````
> 在登录的方法中,将数据封装在session对象中
>
> 服务器本身自动创建一个全局的session对象。在SpringBoot中,直接使用session对象,直接将HttpSession类型的对象作为请求处理方法的参数,会自动将全局的session对象注入到请求处理方法的session型参上——修改控制层
# 五、拦截器
拦截器:首先将所有的请求统一拦截到拦截器中,可以在拦截器中定义过滤的规则,如果不满足系统设置的过滤规则,统一的处理是重新打开login.html(重定向和转发,推荐使用重定向)
在SpringBoot项目中拦截器的定义和使用:SpringBoot是依靠SpringMVC来完成的,SpringMVC提供了一个HandlerInterceptor接口,用于表示定义一个拦截器。首先自定义一个类,实现该接口
### 1、实现拦截器
````java
/**
* 拦截所有请求,检测全局session对象中是否有uid数据,有则放行,没有则重定向至登录页面
* @param request 请求对象
* @param response 响应对象
* @param handler 处理器——url和Controller的映射
* @return 如果返回值为true,表示放行请求,如果返回值为false,表示拦截请求
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// HttpServletRequest对象获取session对象
Object uid = request.getSession().getAttribute("uid");
if (uid == null) {
// 重定向
response.sendRedirect("/web/login.html");
// 结束后续调用
return false;
}
return true;
}
````
### 2、注册过滤器
#### 2.1 添加白名单、添加黑名单
* 白名单:login.html、register.html、login/reg/、index.html、product.html
* 黑名单(用户登录状态下才能访问)
#### 2.2 注册过滤器
借助WebMvcConfigure接口,可以将用户定义的拦截器进行注册,才能保证拦截器能够生效和使用。定义一个实现该接口的类。配置信息建议存放在项目的config包结构下
````java
/**
* 处理器拦截器的注册
*/
@Configuration // 加载当前的拦截器,并注册
public class LoginInterceptorConfigurer implements WebMvcConfigurer {
/**
* 将自定义的拦截器进行注册
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 创建拦截器对象
HandlerInterceptor interceptor = new LoginInterceptor();
// 配置白名单
List patterns = new ArrayList<>();
patterns.add("/bootstrap3/**");
patterns.add("/css/**");
patterns.add("/images/**");
patterns.add("/js/**");
patterns.add("/web/register.html");
patterns.add("/web/login.html");
patterns.add("/web/product.html");
patterns.add("/users/reg");
patterns.add("/users/login");
// 完成拦截器的注册
registry.addInterceptor(interceptor).addPathPatterns("/**").excludePathPatterns(patterns);
}
}
````
# 六、修改密码
### 1、修改密码——持久层
#### 1.1 规划需要执行的SQL语句
根据用户的uid修改用户的password值
````sql
update t_user set password = ? , modifiled_user=? , modified_time=? where uid = ?
````
根据uid查找用户:保证用户存在,未被删除且原始密码正确
````sql
select * from t_user where uid = ?
````
#### 1.2 设计接口和抽象方法
UserMapper接口,将以上两个方法抽象出来
````java
/**
* 修改密码
* @param uid id
* @param password 新密码
* @param modifiedUser 修改的执行者
* @param modifiedTime 修改的时间
* @return 受影响的行数
*/
Integer updatePasswordByUid(Integer uid, String password, String modifiedUser, Date modifiedTime);
/**
* 根据用户id查询用户数据
* @param uid id
* @return 返回查到的对象
*/
User findByUid(Integer uid);
````
#### 1.3 映射
````xml
update t_user set
password=#{password},
modified_user = #{modifiedUser},
modified_time = #{modifiedTime},
where uid = ${uid}
````
#### 1.4 单元测试
### 2、修改密码——业务层
#### 2.1 规划异常
(1)用户原密码错误,已经删除,uid用户找不到
(2)update在更新的时候,可能产生未知的异常,UpdateException
#### 2.2 设计接口和抽象方法
执行修改密码
````java
void changePassword(Integer uid, String username, String oldPassword, String newPassword);
````
业务层实现
````java
public void changePassword(Integer uid, String username, String oldPassword, String newPassword) {
User result = userMapper.findByUid(uid);
if (result == null || result.getIsDelete() == 1) {
throw new UserNotFoundException("用户不存在!");
}
// 比较
String oldMd5Password = getMD5Password(oldPassword, result.getSalt());
if (!result.getPassword().equals(oldMd5Password)) {
throw new PasswordNotMatchException("原密码错误!");
}
// 设置新密码
String newMd5Password = getMD5Password(newPassword, result.getSalt());
Integer rows = userMapper.updatePasswordByUid(uid, newMd5Password, username, new Date());
if (rows != 1) {
throw new UpdateException("更新数据时产生未知异常!");
}
}
````
单元测试
### 3、修改密码——控制层
#### 3.1 处理异常
业务层有异常就处理,新的异常需要配置在统一的异常处理方法中
````java
else if (e instanceof UpdateException) {
result.setState(5003);
result.setMessage("更新数据时产生未知异常!");
}
````
#### 3.2 设计请求
````
请求路径:/users/change_password
请求方式:POST
请求数据:String oldPassword, String newPassword, HttpSession session
响应结果:JsonResult
````
#### 3.3 处理请求
````java
@RequestMapping("/change_password")
public JsonResult changePassword(String oldPassword, String newPassword, HttpSession session) {
Integer uid = getUidFromSession(session);
String username = getUsernameFromSession(session);
userService.changePassword(uid, username, oldPassword, newPassword);
return new JsonResult<>(OK);
}
````
### 4、修改密码——前端页面
在password.html中添加ajax请求的处理
# 七、个人资料
### 1、个人资料——持久层
#### 1.1 规划SQL语句
(1)更新用户信息
````mysql
update t_user set phone=?, email=?, gender=?, modified_user=?, modified_time=? where uid = ?
````
(2)根据用户名查询用户的数据
````mysql
select * from t_user where uid = ?
````
> 查询用户不用重复开发,但是思路不能少
#### 1.2 设计接口和抽象方法
````java
Integer updateInfoByUid(User user);
````
#### 1.3 映射
````java
UPDATE t_user
SET
phone = #{phone},
email = #{email},
gender = #{gender},
modified_user = #{modifiedUser},
modified_time = #{modifiedTime}
WHERE uid = ${uid}
````
#### 1.4 功能测试
### 2、个人资料——业务层
#### 2.1 规划异常
(1)涉及两个功能
* 当打开页面时,获取用户的信息,并且填充到文本框中
* 在页面上检测用户是否点击了修改按钮,如果检测到则执行修改用户信息的操作
(2)异常
打开页面的时候可能找不到用户的数据,点击删除按钮之前需要再次去检测用户的数据是否存在
#### 2.2 接口和抽象方法
```
// 根据用户id查询用户数据
User getByUid(Integer uid);
// 修改用户信息
void changeInfo(Integer uid, String username, User user);
```
#### 2.3 抽象方法的实现
````java
@Override
public User getByUid(Integer uid) {
User result = userMapper.findByUid(uid);
if (result == null || result.getIsDelete() == 1) {
throw new UserNotFoundException("用户未找到!");
}
// 压缩数据
User user = new User();
user.setUsername(result.getUsername());
user.setPhone(result.getPhone());
user.setEmail(result.getEmail());
user.setGender(result.getGender());
return null;
}
@Override
public void changeInfo(Integer uid, String username, User user) {
User result = userMapper.findByUid(uid);
if (result == null || result.getIsDelete() == 1) {
throw new UserNotFoundException("用户未找到!");
}
user.setUid(uid);
//user.setUsername(username);
user.setModifiedUser(username);
user.setModifiedTime(new Date());
Integer rows = userMapper.updateInfoByUid(user);
if (rows != 1) {
throw new UpdateException("更新数据时发生未知异常!");
}
}
````
#### 2.4 单元测试
### 3、个人资料——控制层
#### 3.1 处理异常
> 暂无
#### 3.2 设计请求
(1)设置只要打开页面就发送当前用户数据的查询
````
请求路径:/users/get_bu_uid
请求方式:GET
请求数据:HttpSession session
响应结果:JsonResult
````
(2)点击修改按钮发送修改用户数据的修改操作请求设计
````
请求路径:/users/change_info
请求方式:POST
请求数据:User user, HttpSession session
响应结果:JsonResult
````
#### 3.3 处理请求
````java
@RequestMapping("/get_by_uid")
public JsonResult getByUid(HttpSession session) {
User data = userService.getByUid(getUidFromSession(session));
return new JsonResult<>(OK, data);
}
@RequestMapping("/change_info")
public JsonResult changeInfo(User user, HttpSession session) {
userService.changeInfo(getUidFromSession(session), getUsernameFromSession(session), user);
return new JsonResult<>(OK);
}
````
### 4、个人资料——前端页面
(1)在打开userdata.html页面自动发送ajax请求(get_by_uid),查询到的数据填充到这个页面
(2)在检测到用户点击了修改按钮之后发送一个ajax请求(change_info)
````js
````
# 八、上传头像
### 1、上传头像——持久层
#### 1.1 SQL语句的规划
将对象文件保存在操作系统上,然后把这个文件路径给记录下来,因为在记录路径的时候是非常便捷和方便的,将来如果要打开该问价可以依据路径去找到该文件。在数据库中保存文件路径即可,实际开发环境中一般将静态资源(图片、文件、其他资源文件)放在某台电脑上,把这个电脑作为一个单独的服务器使用。
上传头像,即对应更新用户avatar字段的SQL语句
````mysql
update t_user set avatar=?, modified_user=?, modified_time=? where uid= ?
````
#### 1.2 设计接口和抽象方法
UserMapper接口中定义抽象方法用于修改用户头像
> @Param("SQL映射文件中#{}占位符对应的变量名")
>
> 解决的问题:当SQL语句的占位符和映射接口的方法的参数名不一致时,需要将某个参数强行注入到某个占位符变量上时,或参数过多找不到对应参数时,用@Param(绑定)标注映射关系
````java
/**
* 根据用户uid修改用户头像
* @param uid 用户id
* @param avatar 头像资源地址——反序列化
* @param modifiedUser 修改人
* @param modifiedTime 修改时间
* @return 受影响行数
*/
Integer updateAvatarByUid(@Param("uid") Integer uid,
@Param("avatar") String avatar,
@Param("modifiedUser") String modifiedUser,
@Param("modifiedTime") Date modifiedTime);
````
#### 1.3 接口的映射
````xml
UPDATE t_user
SET
avatar = #{avatar},
modified_user = #{modifiedUser},
modified_time = #{modifiedTime}
WHERE
uid = #{uid}
````
#### 1.4 单元测试
### 2、上传头像——业务层
#### 2.1 规划异常
(1)用户数据不存在,找不到对应的用户数据
(2)更新的时候,产生未知异常
> 无需开发
#### 2.2 设计接口和抽象方法
````java
void changeAvatar(Integer uid, String avatar, String username);
````
#### 2.3 抽象方法的实现
````java
@Override
public void changeAvatar(Integer uid, String avatar, String username) {
User result = userMapper.findByUid(uid);
if (result == null || result.getIsDelete() == 1) {
throw new UserNotFoundException("用户未找到!")
}
Integer rows = userMapper.updateAvatarByUid(uid, avatar, username, new Date());
if (rows != 1) {
throw new UpdateException("更新头像时产生未知异常!");
}
}
````
#### 2.4 单元测试
### 3、上传头像——控制层
#### 3.1 规划异常
````
文件异常的父类:
FileUploadException extends RunTimeException 泛指文件上传的异常
具体文件异常:
FileEmptyException 文件为空的异常
FileSizeException 文件大小超出限制
FileTypeException 文件类型异常
FileUploadIOException 文件读写的异常
````
#### 3.2 处理异常
````java
@ExceptionHandler({ServiceException.class, // 用于统一处理抛出的异常:凡是括号内的异常都拦截到这里处理
FileUploadException.class}) // 增加参数
public JsonResult handleException(Throwable e) {
...;
else if (e instanceof FileEmptyException) {
result.setState(6000);
result.setMessage("上传的文件为空的异常!");
} else if (e instanceof FileSizeException) {
result.setState(6001);
result.setMessage("上传的文件的大小超出了限制值!");
} else if (e instanceof FileTypeException) {
result.setState(6002);
result.setMessage("上传的文件类型超出了限制!");
} else if (e instanceof FileStateException) {
result.setState(6003);
result.setMessage("上传的文件状态异常!");
} else if (e instanceof FileUploadIOException) {
result.setState(6004);
result.setMessage("上传文件时读写异常!");
}
}
````
#### 3.3 设计请求
````
请求路径:/users/change_avatar
请求方式:POST(GET提交数据2KB左右)
请求数据:HttpSession session, MutipartFile file
响应结果:JsonResult
````
#### 3.3 处理请求
> (头像图片的地址): C:\Users\CHArbe\AppData\Local\Temp\tomcat-docbase.8080.406424320104424952\upload
````java
// MultipartFile接口是由SpringMVC提供的一个接口,包装了获取文件类型的数据(任何类型的file都可以接收)
@RequestMapping("/change_avatar")
public JsonResult changeAvatar(HttpSession session,
@RequestParam("file") MultipartFile file) {// 将请求中的参数和该方法的参数进行标记和映射
if (file.isEmpty()) {
throw new FileEmptyException("文件为空!");
}
if (file.getSize() > AVATAR_MAX_SIZE) {
throw new FileSizeException("文件超出限制!");
}
String contentType = file.getContentType();
if (!AVATAR_TYPE.contains(contentType)) {
throw new FileTypeException("文件类型不支持!");
}
// 上传文件地址:../upload/文件.png
String parent = session.getServletContext().getRealPath("upload");
// 创建一个file指向路径,判断文件是否存在
File dir = new File(parent);
if (!dir.exists()) {
dir.mkdirs(); // 创建当前目录
}
//System.out.println(dir); C:\Users\CHArbe\AppData\Local\Temp\tomcat-docbase.8080.406424320104424952\upload
// 获取文件名称,UUID工具类生成一个新的字符串作为文件名
String originalFilename = file.getOriginalFilename();
System.out.println("filename:" + originalFilename);
int index = originalFilename.lastIndexOf(".");
// 文件后缀
String suffix = originalFilename.substring(index);
String filename = UUID.randomUUID().toString().toUpperCase() + suffix;
// 创建空文件
File dest = new File(dir, filename);
// 将file中数据写入到空文件中
try {
file.transferTo(dest);
} catch (FileStateException e) {
throw new FileStateException("文件状态异常!");
} catch (IOException e) {
throw new FileUploadIOException("文件读写异常!");
}
// 返回的头像路径
String avatar = "/upload" + filename;
userService.changeAvatar(getUidFromSession(session), getUsernameFromSession(session), avatar);
return new JsonResult<>(OK, avatar);
}
````
### 4、上传头像——前端页面
在uplaod.html中,使用表单进行文件的商户采纳,需要给表单添加属性
````html