Store网上商城项目

用户注册

1 创建数据表

2 创建用户的实体类

3 注册-持久层

3.1 规划需要执行

1.用户的注册功能。相当于在做数据的插入操作

insert into t_user (username,password) values (值列表)

2.在用户注册时要首先去查询当前的用户名是否存在,如果存在则不能进行注册。相当于是一条查询语句

select * from t_user where username=?

3.2 设计接口和抽象方法

定义Mapper接口。在项目的目录下创建mapper包,在这个包下根据不同的功能模块来创建mapper接口。创建一个UserMapper的接口。要在接口中定义这两个抽象方法。

public interface UserMapper {
    /**
     * 插入用户的数据
     * @param user 用户的数据
     * @return 受影响的行数(增,删,改,都用受影响行数作为返回值)
     */
    Integer insert(User user);
    /**
     * 根据用户名来查询用户的数据
     * @param username 用户名
     * @return 如果找到对应的用户则返回这个用户的数据,如果没有则返回null
     */
    User findByUsername(String username);
}

3.3 编写映射

1.定义xml映射文件,与对应的接口进行关联。所有的映射文件需要放在resources目录下,在这个目录下创建一个mapper文件夹,然后在这个文件夹下存放Mapper映射文件

2.创建接口对应的映射文件,遵循和接口的名称保存一致即可。创建一个UserMapper.xml文件。

3.配置接口中的方法对应上SQL语句上。需要借助标签来完成,insert\update\delete\select,对应的是SQL语句的增删改查操作。

4.resultMap 定义匹配不同命名规则的属性,并把resultMap的id传入到标签的resultMap属性里面

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cy.store.mapper.UserMapper">

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

    <insert id="insert" useGeneratedKeys="true" keyProperty="uid">
        INSERT INTO t_user(username, password, salt, phone, email, gender, avatar, isDelete,createdUser, createdTime, modifiedUser, modifiedTime)
        VALUES (#{username}, #{password}, #{salt}, #{phone}, #{email}, #{gender}, #{avatar}, #{isDelete},#{createdUser}, #{createdTime}, #{modifiedUser}, #{modifiedTime})
    </insert>

    <select id="findByUsername" resultMap="UserEntityMap">
        SELECT *
        FROM t_user
        WHERE username = #{username}
    </select>
</mapper>

3.4 单元测试

每个独立的层编写完毕后都需要写单元测试方法,来测试当前功能。在test包结构下创建一个mapper包,在这个包下再创建持久层的功能测试。

@SpringBootTest
@RunWith(SpringRunner.class)
public class UserMapperTests {
    @Autowired
    private UserMapper userMapper;
    @Test
    public void insert() {
        User user = new User();
        user.setUsername("tim");
        user.setPassword("123");
        Integer rows = userMapper.insert(user);
        System.out.println(rows);
    }
    @Test
    public void findByUsername(){
        User user = userMapper.findByUsername("tim");
        System.out.println(user);
    }
}

4 注册-业务层

4.1 规划异常

1.RuntimeException异常:作为这类异常的子类,然后再去定义具体的异常类型来继承这个异常。业务层异常的基类,ServiceException异常。这个异常继承RuntimeException异常。异常机制的建立。

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.业务异常:用户在进行注册时候可能会产生用户名被占用的错误,抛出一个异常:UsernameDepulitedException异常

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

3.其他异常:正在执行数据插入操作的时候,服务器宕机。处于正在执行插入的过程中所产生的异常InsertException

4.2 设计接口和抽象方法

1.在service包下创建一个IUserService接口。

public interface IUserService {
    /**
     * 用户注册方法
     * @param user 用户的数据对象
     */
    void reg(User user);
}

2.创建一个实现类UserServiceImpl类,需要实现这个接口,并且实现抽象方法

@Service
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("用户名被占用");
        }else {
            String oldPassword =user.getPassword();
            String salt = UUID.randomUUID().toString().toUpperCase();
            String md5Password = getMD5Password(oldPassword, salt);
            user.setPassword(md5Password);
            user.setSalt(salt);
            user.setIsDelete(0);
            user.setCreatedUser(user.getUsername());
            user.setModifiedUser(user.getUsername());
            Date date = new Date();
            user.setCreatedTime(date);
            user.setModifiedTime(date);
            Integer rows = userMapper.insert(user);
            if(rows != 1){
                throw new InsertException("在用户注册过程中产生了未知异常");
            }
        }
    }
    private String getMD5Password(String password,String salt){
        for (int i = 0; i < 3; i++) {
            password=DigestUtils.md5DigestAsHex((salt+password+salt).getBytes(StandardCharsets.UTF_8)).toUpperCase();
        }
        return password;
    }
}

3.在单元测试包下创建一个UserServiceTests类,在这个类中添加单元测试的功能。

@SpringBootTest
@RunWith(SpringRunner.class)
public class UserServiceTests {
    @Autowired
    private IUserService userService;
    @Test
    public void reg(){
        try {
            User user=new User();
            user.setUsername("yuanxin03");
            user.setPassword("123");
            userService.reg(user);
            System.out.println("ok");
        }catch (ServiceException e){
            System.out.println(e.getClass().getSimpleName());
            System.out.println(e.getMessage());
        }
    }
}

5 注册-控制层

5.1 创建响应

状态码,状态描述信息,数据。这部分功能封装在一个类中,将这类作为方法返回值,返回给前端浏览器。

public class JsonResult<E> implements Serializable {
    // 状态码
    private Integer state;
    // 描述信息
    private String message;
    // 数据
    private E data;
}

5.2 设计请求

根据当前的业务功能模块,进行请求的设计

请求路径:/users/reg
请求参数:User user
请求类型:Post
请求结果:JsonResult<void>

5.3 处理请求

1.创建控制层对应的类UserController类,依赖于业务层的接口。

2.异常捕获

5.4 控制层优化设计

在控制层抽离父类,在这个父类中统一的去处理关于异常的相关操作。编写一个BaseController类,统一处理异常。

6 注册-前端页面

1.在register页面中编写发送请求的方法,点击事件来完成。选中对应的按钮(\((“选择器”)),再去添加点击的事件,\).ajax()函数发送异步请求。

2.JQuery封装了一个函数,称之为$ajax()函数,通过对象调用ajax()函数,可以异步加载相关的请求。依靠的是JavaScript提供的一个对象XHR(XmlHttpResponse),封装了这个对象。

3.ajax()的使用方式。需要传递一个方法体作为方法的参数来使用:

$.ajax({
    url:"",
    type:"",
    data:"",
    dataType:"",
    success:function(){
       
    },
    error:function(){
        
    }
});

4.ajax()函数参数的含义:

5.js代码可以独立在一个js的文件里或声明在一个script标签中

用户登录

1 登录-持久层

1.1 规划需要执行的SQL语句

依据用户提交的用户名和密码做select查询。

select * from t_user where username=?

1.2 接口设计和方法

2 登录-业务层

2.1 规划异常

1.用户名对应的密码错误,密码匹配失败的异常:PasswordNotMatchException,运行时异常,业务异常。

2.用户名没有找到:UsernameNotFoundException,运行时异常,业务异常。

3.异常的编写

2.2 设计业务层接口和抽象方法

1.直接在IUserService接口中编写抽象方法,login(String username,String password)。将当前登录成功的用户数据以当前用户对象的形式返回。状态管理:可以将数据保存在cookie或者session中,可以避免重复度很高的数据多次频繁操作数据进行获取(用户id-在session中,用户的头像-cookie中)。

2.在实现类中实现父接口的抽象方法

3.在测试类中测试业务层登录的方法是否可以执行通过。

4.如果一个类没有手动创建直接将这个类复制到项目,idea找不到这个类。解决:重新构建

2.3 抽象方法实现

3 登录-控制层

3.1 处理异常

业务层抛出的异常是什么,需要在统一异常处理类中进行统一的捕获和处理,如果业务层抛出的异常类型在已经在统一异常处理类中曾经处理过,则不需要重复处理

3.2 设计请求

请求路径:/users/login
请求参数:String username, String password
请求类型:Post
请求结果:JsonResult<User>

3.3 处理请求

在UserController类中编写处理请求的方法。

@RequestMapping("/login")
public JsonResult<User> login(String username,String password){
    User login = userService.login(username, password);
    return new JsonResult<>(OK,login);
}

4 登录-前端页面

1.在login.html页面中依据前面所设置的请求来发送ajax请求。

2.访问页面进行用户的登录操作。

用户会话session

session对象主要存在服务器端,可以用于保存服务器的临时数据的对象,所保存的数据可以在整个项目中都可以通过访问来获取,把session的数据看作一个共享的数据源。首次登陆的时候所获取的用户数据,转移到session对象即可。

session.getAttrbute(“key”)可以将获取session中的数据的这种行为进行封装,封装在BaseController类中。

  1. 封装session对象中数据的获取,数据的设置。(用户登录成功后,进行数据的设置,设置到全局的session对象)

  2. 在父类中封装两个数据:获取uid和获取username对应的两个方法。用户头像暂不考虑,封装在cookie中

  3. 在登录的方法中将数据封装在session对象中。服务器本身自动创建有session对象,已经是一个全局的session对象。SpringBoot直接使用session对象,直接将HttpSession类型的对象作为请求处理方法的参数,会自动将全局的session对象注入到请求处理方法的session形参上

    @RequestMapping("/login")
    public JsonResult<User> login(String username, String password, HttpSession session){
        User login = userService.login(username, password);
        session.setAttribute("uid",login.getUid());
        session.setAttribute("username",login.getUsername());
        System.out.println(getUidFromSession(session));
        System.out.println(getUsernameFromSession(session));
        return new JsonResult<>(OK,login);
    }
    

拦截器

拦截器:首先将所有的请求统一拦截到拦截器中,开发者可以在拦截器中定义过滤的规则,如果不满足系统的设置的过滤规则,统一的处理是重新去打开login.html页面(重定向和转发),推荐使用重定向。

在SpringBoot项目中实现拦截器的定义和使用。SpringBoot是依靠SpringMVC来完成的。SpringMVC提供了一个接口HandlerInterceptor接口,用于表示定义一个拦截器。首先,自定义一个类,再用这个类去实现这个接口。

1.自定义一个类,用这个类实现HandlerInterceptoer接口。

/**
 * 定义一个拦截器
 */
public class LoginInterceptor implements HandlerInterceptor {
    /**
     * 检测全局session对象中是否有uid数据,如果有则放行,如果没有则重定向到登录页面
     * @param request 请求对象
     * @param response 响应对象
     * @param handler 处理器
     * @return 如果返回值为true表示放行当前的请求,如果返回值为false则表示拦截当前的请求
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Object uid = request.getSession().getAttribute("uid");
        if (uid==null){
            response.sendRedirect("/web/login.html");
            return false;
        }
        return true;
    }
}

2.注册过滤器:添加白名单(哪些资源可以在不登录的情况下访问:login.html\register.html\login\reg\index.html\product.html)、添加黑名单(登录时才可以访问的页面资源)

3.注册过滤技术:借助WebMvcConfigure接口,可以将用户定义的拦截器进行注册,才可以保证拦截器能够生效和使用。定义一个类,然后让这个类实现WebMvcConfigure接口。配置信息,建议存放在项目的config包结构下。

public class LoginInterceptorConfigurer implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        WebMvcConfigurer.super.addInterceptors(registry);
    }
}

修改密码

需要用户提交原始密码和新密码,再根据当前登录的用户进行信息的修改操作。

1 修改密码-持久层

1.1 规划需要执行的SQL语句

根据用户的uid修改用户的password值。

update t_user set password=?,modified_user?=modified_time=?where uid=?

根据uid查询用户的数据。在修改密码之前,首先要保证当前这用户的数据存在(is_delete),检测是否被标记为删除、检测输入的原始密码是否正确。

select * from t_user where uid=?

1.2 UserMapper接口定义方法

1.3 UserMapper.xml完善SQL语句

1.4 单元测试

2 修改密码-业务层

2.1 规划可能产生的异常

1 用户输入的原密码错误 PasswordNotMatch异常

2 用户已删除

3 uid找不到用户

4 update更新时产生的未知的异常,UpadateException

2.2 设计接口和抽象方法

执行用户修改密码的核心方法

2.3 编写单元测试

3 修改密码-控制层

3.1 处理异常

UpdateException需要配置在统一的异常处理方法中。

3.2 设计请求

/users/changPassword
POST
String oldPassword,String newPassword
JsonResult<void>

3.3 处理请求

@RequestMapping("/change_password")
    public JsonResult<Void> 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 修改密码-前端页面

个人资料

1.个人资料-持久层

1.1 需要规划的SQL语句

1.更新用户信息的SQL语句

update t_user set phone=?,email=?,gender=?,modified_user=?,modified_time=? where uid=?

2.根据用户名来查询用户的数据

select * from t_user where uid=?

1.2 接口与抽象方法

更新用户的信息方法的定义。

	/**
     * 更新用户的数据信息
     * @param user 用户的数据
     * @return 返回值为受影响的行数
     */
    Integer updateInfoByUid(User user);

1.3 抽象方法的映射

UserMapper.xml文件

<update id="updateInfoByUid">
    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>

1.4 测试

@Test
public void updateInfoByUid(){
    User user=new User();
    user.setUid(11);
    user.setPhone("1232");
    user.setEmail("98670@qq.com");
    user.setGender(1);
    Integer integer = userMapper.updateInfoByUid(user);
    System.out.println(integer);
}

2.个人资料-业务层

2.1 异常规划

  1. 设计两个功能:

    • 当打开页面时,获取用户的信息并填充到对应的文本框中。

    • 检测用户是否点击了修改按钮,如果检测到则执行修改用户信息的操作。

  2. 打开页面的时候可能找不到用户数据,点击删除按钮之前需要再次的去检测用户的数据是否存在

2.2 接口和抽象方法

主要有两个功能的模块,对应的是两个抽象的方法的设计。

/**
* 根据用户的uid查询用户数据
* @param uid
* @return 用户的数据
*/
User getByUid(Integer uid);

/**
* 修改用户信息
* @param session
* @param user
*/
void changeInfo(Session session,User user);

2.3 实现抽象方法

在UserServiceImpl类中添加两个抽象方法的具体实现。

	@Override
    public User getByUid(Integer uid) {
        User result = userMapper.findByUid(uid);
        if (result==null||result.getIsDelete()==1){
            throw new UsernameNotFoundException("用户数据不存在");
        }else {
            User user=new User();
            user.setEmail(result.getEmail());
            user.setPhone(result.getPhone());
            user.setGender(result.getGender());
            return user;
        }

    }

    @Override
    public void changeInfo(Integer uid,String username, User user) {
        User result = userMapper.findByUid(uid);
        if (result==null||result.getIsDelete()==1){
            throw new UsernameNotFoundException("用户数据不存在");
        }else {
            user.setUid(uid);
            user.setModifiedUser(username);
            user.setModifiedTime(new Date());
            Integer integer = userMapper.updateInfoByUid(user);
            if (integer!=1){
                throw new UpdateException("更新数据时产生未知异常");

            }
        }
    }

2.4 接口测试

	@Test
	public void getByUid(){
        System.out.println(userService.getByUid(11));
    }
    @Test
    public void changeInfo(){
        User user = new User();
        user.setPhone("1234");
        user.setEmail("56565@qq.com");
        user.setGender(0);
        userService.changeInfo(11,"denlu",user);
    }

3.个人资料-控制层

3.1 处理异常

3.2 设计请求

1.设置-打开页面就发送当前用户的查询。

/users/get_by_uid
GET
HttpSession session
JsonResult<User>

2.点击修改按钮发送用户的数据修改操作请求的设计。

/users/change_info
POST
User user,HttpSession session
JsonResult<Void>

3.3 处理请求

4. 个人资料-前端页面

1.在打开userdata.html页面自动发送ajax请求,查询到的数据填充到这个页面。

2.在检测到用户点击了修改按钮之后发送了一个ajax请求(change_info)。

上传头像

1.上传头像-持久层

1.1 SQL语句的规划

将对象文件保存在操作系统上,然后再把这个文件路径给记录下来,因为在记录路径的时候是非常的便捷和方便的。将来如果要打开这个文件,可以根据路径去找到这个文件。所以数据库里只要保存文件存储路径即可。将保存的静态资源(图片,文件,其他资源文件)放在某台电脑上,再把这台电脑作为一台单独的服务器使用。

对应是一个用户avatar的sql语句

update t_user set avatar=?,modified_user=?,modified_time=? where uid=?

1.2 设计接口和抽象方法

UserMapper接口中来定义这个给抽象方法用于修改用户头像

1.3 接口的映射

UserMapper.xml文件中编写映射的SQL语句。

2.上传头像-业务层

2.1 规划异常

1.用户数据不存在,找不到对应的用户的数据。

2.更新的时候,各种未知的异常产生。

2.2 设计接口和抽象方法

	/**
     * 根据uid修改头像
     * @param uid 用户的id
     * @param avatar 用户的头像路径
     * @param username 用户名
     */
    void changeAvatar(Integer uid,String avatar,String username);

2.3 实现抽象方法

@Override
public void changeAvatar(Integer uid, String avatar, String username) {
    User user = userMapper.findByUid(uid);
    if(user==null||user.getIsDelete().equals(1)){
        throw new UsernameNotFoundException("用户数据不存在");

    }
    Integer integer = userMapper.updateAvatarByUid(uid, avatar, username, new Date());
    if(integer!=1){
        throw new UpdateException("更新用户头像产生未知的异常");

    }
}

2.4 测试

3.上传头像-控制层

3.1 规划异常

父类:FileUploadException 泛指文件上传的异常
    FileEmptyException 文件为空的异常
    FileSizeException 文件大小超出限制
    FileTypeException 文件类型异常
    FileUploadIOException 文件读写的异常
    FileStateException 文件状态异常

五个构造方法显式声明出来,再去继承相关的父类

3.2 异常处理

在基类BaseController类中进行编写和统一处理。

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 FileUploadException){
    result.setState(6004);
    result.setMessage("文件上传异常");
}

3.3 设计请求

/users/change_avatar
POST(GET请求提交数据2KB)
HttpSession session,MutipartFile file
JsonResult<String>

3.4 实现请求

    /**
     * MultipartFile接口是SpringMVC提供的一个接口,这个接口为我们包装了获取文件类型的数据(任何类型的file都可以接收),SpringBoot整合了SpringMVC,只需要在处理请求的方法参数列表上声明一个参数类型的MutipartFile的参数,然后SpringBoot自动将传递给服务的文件数据赋值给这个参数
     * @param session
     * @param file
     * @return
     */
    @RequestMapping("change_avatar")
    public JsonResult<String> changeAvatar(HttpSession session, @RequestParam("file") MultipartFile file) {
        if (file.isEmpty()){
            throw new FileEmptyException("文件为空");
        }
        if (file.getSize()>AVATAR_MAX_SIZE){
            throw new FileSizeException("文件超出限制");
        }
        if (!AVATAR_TYPE.contains(file.getContentType())){
            throw new FileTypeException("文件类型不支持");
        }

        String parent=session.getServletContext().getRealPath("upload");
        File dir = new File(parent);
        if (!dir.exists()){
            dir.mkdirs();//创建当前的目录
        }

        String originalFilename = file.getOriginalFilename();

        int index = originalFilename.lastIndexOf(".");
        String suffix=originalFilename.substring(index);//获取后缀(.XXX)
        String filename = UUID.randomUUID().toString().toUpperCase()+suffix;//拼接随机生成的文件名和前面获取到的字符串
        File dest = new File(dir, filename);//在存储路径下写入同名空文件,传回空文件
        try {
            file.transferTo(dest);//头像文件写入空文件中
        } catch (FileStateException e) {
            throw new FileStateException("文件状态异常");
        } catch (IOException e) {
            throw new FileUploadIOException("文件读写异常");
        }

        Integer uidFromSession = getUidFromSession(session);
        String usernameFromSession = getUsernameFromSession(session);
        String avatar="/upload/"+filename;
        userService.changeAvatar(uidFromSession,avatar,usernameFromSession);
        return new JsonResult<>(OK,avatar);

    }

4.上传头像-前端页面

在upload页面中编写上传头像代码

说明:如果直接使用表单进行文件的上传,需要给表单显示的添加一个属性enctype=”multipart/form-data”声明出来,不会将目标文件的数据结构做修改再上传,不同于字符串

5.解决Bug

5.1 更改默认大小的限制

SpringMVC默认为1MB文件可以进行上传,需要手动修改SpringMVC默认上传文件的大小。

方式1:直接在配置文件中进行配置

spring.servlet.multipart.max-file-size=10MB

方式2:采用Java代码的形式来设置文件的上传大小的限制。主类中进行配置,可以定义一个方法,必须使用@Bean修,返回值为MutipartConfigElement。在类的前面添加一个@Configuration进行修饰

@Bean
MultipartConfigElement getMultipartConfigElement(){
    MultipartConfigFactory multipartConfigFactory = new MultipartConfigFactory();
    multipartConfigFactory.setMaxFileSize(DataSize.of(10, DataUnit.MEGABYTES));
    return multipartConfigFactory.createMultipartConfig();
}

5.2 显示头像

在页面中通过ajax请求来t提交文件,提交完成后,后端返回json串,从中解析出data中的数据,设置到img头像标签的src属性上

serialize()//将表单数据自动拼接成key=value的结构进行提交给服务器,一般提交是普通的控件类型中的数据(text\password\radio\checkbox)等等
FormdData类//将表单中数据保持原有结构进行数据的提交
--- new FormData($("#form")[0]);
ajax默认处理数据时按照字符串的形式进行处理,以及默认会采用字符串的形式进行提交数据。所以要关闭这两个默认的功能。
--- processData:false,
--- contentType:false,

5.3 登录后显示头像

更新头像成功后,将服务器返回的头像路径保存在客户端cookies对象中,然后每次检查用户打开上传头像页面,这个页面中通过ready()方法来自动检测去读取cookie中头像并设到src属性上。

1.设置cookie中的值

2.在upload页面中引入cookie.js文件

3.在upload.html页面中通过ready()自动读取cookie中的数据。

新增收货地址

1 新增收货地址-数据表的创建

CREATE TABLE t_address (
	aid INT AUTO_INCREMENT COMMENT '收货地址id',
	uid INT COMMENT '归属的用户id',
	name VARCHAR(20) COMMENT '收货人姓名',
	province_name VARCHAR(15) COMMENT '省-名称',
	province_code CHAR(6) COMMENT '省-行政代号',
	city_name VARCHAR(15) COMMENT '市-名称',
	city_code CHAR(6) COMMENT '市-行政代号',
	area_name VARCHAR(15) COMMENT '区-名称',
	area_code CHAR(6) COMMENT '区-行政代号',
	zip CHAR(6) COMMENT '邮政编码',
	address VARCHAR(50) COMMENT '详细地址',
	phone VARCHAR(20) COMMENT '手机',
	tel VARCHAR(20) COMMENT '固话',
	tag VARCHAR(6) COMMENT '标签',
	is_default INT COMMENT '是否默认:0-不默认,1-默认',
	created_user VARCHAR(20) COMMENT '创建人',
	created_time DATETIME COMMENT '创建时间',
	modified_user VARCHAR(20) COMMENT '修改人',
	modified_time DATETIME COMMENT '修改时间',
	PRIMARY KEY (aid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2 新增收货地址-创建实体类

	private Integer aid;
    private Integer uid;
    private String name;
    private String provinceName;
    private String provinceCode;
    private String cityName;
    private String cityCode;
    private String areaName;
    private String areaCode;
    private String zip;
    private String address;
    private String phone;
    private String tel;
    private String tag;
    private Integer isDefault;

3 新增收货地址-持久层

3.1 各功能的开发顺序

当前收货地址功能模块:列表的展示,修改,删除,设置默认,新增收货地址。

开发顺序:新增-展示-设置默认-删除-修改

3.2 规划需要执行的SQL语句

1.新增-对应的是插入语句

insert into t_address(除了aid外字段列表) values(字段值列表)

2.一个用户的收货地址最多只能有20条数据对应。在插入用户数据前,先做查询操作。收货地址逻辑控制方面的一个异常。

select count(*) t_address where uid=?

3.3 接口和抽象方法

1.创建一个新的接口Address,在这个接口中来定义上面两个SQL语句抽象方法的定义。

    /**
     * 插入用户的收货地址数据
     * @param address 收货地址数据
     * @return 受影响的行数
     */
    Integer insert(Address address);

    /**
     * 根据用户的uid统计用户记录的收货地址数量
     * @param uid 用户的uid
     * @return 当前用户的收货地址总数
     */
    Integer countByUid(Integer uid);

3.4 配置SQL映射

resource下的AddressMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cy.store.mapper.AddressMapper">
    	<resultMap id="AddressEntityMap" type="com.cy.store.entity.Address">
        <id column="aid" property="aid"/>
        <result column="province_code" property="provinceCode"/>
        <result column="province_name" property="provinceName"/>
        <result column="city_code" property="cityCode"/>
        <result column="city_name" property="cityName"/>
        <result column="area_code" property="areaCode"/>
        <result column="area_name" property="areaName"/>
        <result column="is_default" property="isDefault"/>
        <result column="created_user" property="createdUser"></result>
        <result column="created_time" property="createdTime"></result>
        <result column="modified_user" property="modifiedUser"></result>
        <result column="modified_time" property="modifiedTime"></result>
    </resultMap>
    <insert id="insert" useGeneratedKeys="true" keyProperty="aid">
        INSERT INTO t_address(uid, name, province_name, province_code, city_name, city_code,area_name, area_code, zip, address, phone, tel, tag, isDefault,created_user, created_time, modified_user, modified_time)
        VALUES (#{uid},#{name},#{provinceName},#{provinceCode},#{cityName},#{cityCode},#{areaName},#{areaCode},#{zip},#{address},#{phone},#{tel},#{tag},#{isDefault},#{createdUser},#{createdTime},#{modifiedUser},#{modifiedTime})
    </insert>
    <select id="countByUid" resultType="java.lang.Integer">
        SELECT count(*) from t_address WHERE uid=#{uid}
    </select>
</mapper>

2.在test下的mapper文件夹下创建AddressMapperTests的测试类

4 新增收货地址-业务层

4.1 规划异常

如果用户是第一插入用户的收货地址,规则:当用户插入的地址是第一条,需要将当前地址作为默认收货地址,如果查询到统计总数为0,则将当前地址的is_default值设置为1。查询统计的结果为0不代表异常。查询结果大于20,这时需要抛出业务控制的异常AddressCountLimitException异常。自行创建这个异常。

4.2 接口和抽象方法

1.创建一个IAddressService接口,在接口中定义业务的抽象方法

4.3 实现抽象方法

5 新增收货地址-控制层