SpringBoot+MyBatis+MySQ二手商城项目

1.项目功能:登录、注册、热销商品、用户管理(密码、个人信息、头像、收货地址)、购物车(展示、增加、删除)、订单模块。

2.开发顺序:注册、登录、用户管理、购物车、商品、订单模块。

3.某一个模块的开发:

  • 持久层开发:依据前端页面的设置规划相关的SQL语句,以及进行配置
  • 业务层开发:核心功能控制、业务操作以及异常的处理
  • 控制层开发:接受请求、处理响应
  • ·前端开发:

五、注册-控制层

5.1创建响应

//   状态码
    private Integer state;
//    描述信息
    private String message;
//    数据
    private E data;

5.2设计请求

请求路径:/user/reg

请求参数:User user

请求类型:Post

响应结果:JsonResult

5.3处理请求

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

package com.cy.store.service.impl;

import com.cy.store.entity.User;
import com.cy.store.mapper.UserMapper;
import com.cy.store.service.IUserService;
import com.cy.store.service.ex.InsertException;
import com.cy.store.service.ex.UsernameDuplicaredException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.util.Date;
import java.util.UUID;

@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public Integer reg(User user) {
        //判断是否被注册
        String username = user.getUsername();
        User result = userMapper.finfByUsername(username);
        //判断结果集是否为null,如果不为null,抛出用户名被占用异常
        if (result!=null){
            throw new UsernameDuplicaredException("用户名被占用");
        }

        //密码加密处理的实现: md5算法的形式:67dhdsgh-yeuwrey121-yerui374-yrwirei-67123
        // 〔串+password +串) ---- md5算法进行加密,连续加载三次
        //盐值+password +盐值----盐值就是一个随机的字符串
        String oldPassword = user.getPassword();
        //随机生成一个盐值
        String salt = UUID.randomUUID().toString().toUpperCase();
        user.setSalt(salt);
        String md5Password = getMD5Password(oldPassword, salt);
        //将加密的密码重新补全到user中
        user.setPassword(md5Password);

        //补全数据:is_delete设置为0,0为未删,1为删
        user.setIsDelete(0);
        //补全数据:4个日志信息
        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("产生了未知的异常");
        }
        return rows;
    }
    /*定义一个md5算法的加密处理*/
    private String getMD5Password(String password,String salt){
        for (int i = 0; i < 3; i++) {
            password = DigestUtils.md5DigestAsHex((salt + password + salt).getBytes()).toUpperCase();
        }

        return password;
    }
}

SpringBoot+MyBatis+MySQ二手商城项目_第1张图片

 5.4控制层优化设计

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

import org.springframework.web.bind.annotation.ExceptionHandler;

import java.rmi.server.ServerCloneException;

public class BaseController {
//    操作成功的状态码
    public static final int OK=200;
    //请求处理方法,这个方法的返回值就是需要传递给全端的数据
    // 自动将异常对象传递给此方法的参数列表上
    //当项目中产生异常,被统一拦截到此方法中,这个方法此时就冲当的是请求处理方法,方法的返回值直接给到前端
    @ExceptionHandler(ServerCloneException.class)//用来统一处理抛出的异常
    public JsonResult handleException(Throwable e){
        JsonResult result = new JsonResult<>(e);
        if (e instanceof UsernameDuplicaredException){
            result.setState(4000);
            result.setMessage("用户名已经被占用");
        }else if (e instanceof InsertException){
            result.setState(5000);
            result.setMessage("注册时发生异常");
        }
        return result;
    }
}

重新构建reg()方法

    @RequestMapping("reg")
//    @ResponseBody//表示此方法的响应结果以json格式进行数据的响应到前端
    public JsonResult reg(User user){

            userService.reg(user);

        return new JsonResult<>(OK);
    }

6注册-前端页面

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

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

3.ajax()使用方式。需要传递一个方法体作为方法的参数来使用,一对大括号称之为方法体。语法结构:

SpringBoot+MyBatis+MySQ二手商城项目_第2张图片

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

SpringBoot+MyBatis+MySQ二手商城项目_第3张图片

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

7用户登录

当用户输入用户名和密码将数据提交给后台数据库进行查询,如果存在对应的用户名和密码则表示登录成功,登录成功之后跳转到系统的主页就是index.html页面跳转在前端使用jquery来完成。

1.登录-持久层

1.1规划需要执行的SQL语句

依据用户提交的用户名和密码做select查询。密码的比较在业务层执行

select * from t_user where username=?

1.2接口设计和方法

不用重复开发

2.登录-业务层

2.1规划异常

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

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

3.异常编写:

  • 业务层异常需要继承ServiceException异常类。

  • 在具体的异常类中定义构造方法(可以使用快捷键来生成,有5个构造方法)。      

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

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

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

3.在测试类中测试

4.如果一个类没有手动创建直接将这个类复制到项目,idea找不到这个类。之前缓存导致不能够正常找到这类的符号。重新构建项目。

2.3抽象方法实现

 public User login(String username, String password) {
        //根据用户名称来查询用户的数据是否存在,如果不在则抛出异常
        User result = userMapper.finfByUsername(username);
        if (result==null){
            throw new UserNotFoundException("用户数据不存在");
        }
        //检查用户的密码是否匹配
        //1.先获取到数据库中的加密之后的密码
        String oldPassword = result.getPassword();
        //2.和用户传过来的数据进行比较
        //2.1先获取盐值:上一次在注册时所自动生成的盐值
        String salt = result.getSalt();
        //2.2将用户的密码按照md5算法规则进行加密
        String newMD5Password = getMD5Password(password, salt);
        //3将密码进行比较
        if(!newMD5Password.equals(oldPassword)){
            throw new PasswordNotMatchException("用户密码错误");
        }
        //判断is_delete字段是否为1,表示标记为删除
        if (result.getIsDelete()==1){
            throw  new UserNotFoundException("用户数据存在");
        }
        //调用mapper层的findByUsername来查询用户的数据,提升系统调优
        User user = new User();
        user.setUid(result.getUid());
        user.setUsername(result.getUsername());
        user.setAvatar(result.getAvatar());

        return user;
    }

    /*定义一个md5算法的加密处理*/
    private String getMD5Password(String password,String salt){
        for (int i = 0; i < 3; i++) {
            password = DigestUtils.md5DigestAsHex((salt + password + salt).getBytes()).toUpperCase();
        }

        return password;
    }

3.登录-控制层

3.1处理异常

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

else if (e instanceof UserNotFoundException){
            result.setState(5001);
            result.setMessage("用户数据不存在");
        }else if (e instanceof PasswordNotMatchException){
            result.setState(5002);
            result.setMessage("用户名的密码错误");
        }

3.2设计请求

SpringBoot+MyBatis+MySQ二手商城项目_第4张图片

3.3处理请求

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

4.登录-前端页面

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

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

用户会话session

session对象主要存在服务器端,可以用于保存服务器的临时数据的对象,所保存的数据可以在整个项目中都可以通过访问来获取,把session的数据看做一个共享的数据。首次登录的时候所获取的用户的数据,转移到session对象即可。seession.getAttrbute("key")可以将获取session中的数据这种行为进行封装,封装在BaseController类中。

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

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

 /**
     * 获取seesion对象中的uid
     * @param session session对象
     * @return 当前登录的用户uid的值
     */
    protected final Integer getuidFromSession(HttpSession session){
        return Integer.valueOf(session.getAttribute("uid").toString());
    }

    /**
     * 获取用户username
     * @param session
     * @return
     */
    protected final String getUsernameFromSession(HttpSession session){
        return session.getAttribute("username").toString();
    }

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

 @RequestMapping("login")
    public JsonResult login(String username, String password, HttpSession session){
        User data = userService.login(username, password);
        //向session对象中完成数据的绑定(session是全局的)
        session.setAttribute("uid",data.getUid());
        session.setAttribute("username",data.getUsername());
        System.out.println(getuidFromSession(session));
        System.out.println(getUsernameFromSession(session));
        return new JsonResult(OK,data);
    }

拦截器

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

1.首先自定义一个类,在这个类实现这个Handlerlnterceptor接口。

源码解析

/**
 * 定义一个拦截器
 */
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 obj = request.getSession().getAttribute("uid");
        if (obj==null){
            //说明用户没有登录过系统,重定向到login.html
            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) {

    }
}
/处理器拦截器的组成
@Configuration//加载当前的拦截器并进行组成
public class LoginInterceptorConfigurer implements WebMvcConfigurer {

    @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/index.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值。

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

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

select * from t_user where uid=?

1.2设计接口和抽象方法

/**
     * 根据用户的uid来修改用户密码
     * @param uid 用户的id
     * @return 放回值为受影响的行数
     */
    Integer updatePasswordByUid(Integer uid ,
                                String password,
                                String modifiedUser,
                                Date modifiedTime
                                );

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

 1.3SQL映射

配置到映射文件中


        update t_user set
          password=#{password},
          modified_user=#{modifiedUser},
          modified_time=#{modifiedTime},
         where uid = #{uid} ;
    
    

 1.4持久层单元测试

    @Test
    public void updatePasswordByUid(){
        userMapper.updatePasswordByUid(9,"321","管理员",new Date());
    }
    @Test
    public void findByUid(){
        System.out.println(userMapper.findByUid(9));
    }

2.修改密码-业务层

2.1规划异常

1.用户的源密码错误,is_delete==1、uid找不到,在用户没有发现的异常。

2.update在更新的时候,有可能产生未知的异常,UpdateException。

2.2设计接口和抽象异常

执行用户修改密码的和谐方法

void changePassword(Integer uid,
                        String username,
                        String oldpassword,
                        String newPassword
                        );

再实现类中实现当前的抽象方法

 @Override
    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 md5Password = getMD5Password(oldpassword, result.getSalt());
        if (!result.getPassword().equals(oldpassword)){
            throw new PasswordNotMatchException("密码错误");
        }
        //将新的密码设置到数据库中
        String md5Password1 = getMD5Password(newPassword, result.getSalt());
        Integer rows = userMapper.updatePasswordByUid(uid, md5Password1, username, new Date());
        if (rows!=1){
            throw new UpdateException("更新数据产生未知异常");
        }

    }

3.修改密码-控制层

3.1处理异常

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

else if (e instanceof UpdateException){
            result.setState(5001);
            result.setMessage("更新时发生异常");
        }

3.2设计请求

/users/change_password

post

String oldPassword,String newPassword,HttpSession session

//需要和表单中的name属性值保持一致

JsonResult

3.3处理请求

 @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);
    }

个人资料

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 t_user
    SET
    
    phone = #{phone},
    email = #{email},
    gender = #{gender},
      modified_user=#{modifiedUser},
      modified_time=#{modifiedTime}
      where uid = #{uid} ;

1.4测试方法

@Test
public void updateInfoByUid(){
    User user = new User();
    user.setUid(9);
    user.setPhone("11518164565");
    user.setEmail("[email protected]");
    user.setGender(1);
    Integer integer = userMapper.updateInfoByUid(user);
}

2.个人资料-业务层

2.1异常规划

1.设计两个功能:

  • 当打开页面是获取用户的信息并且填充到对应的文本框中。
  • 检测用户是否点击了修改按钮,如果检测到则执行修改用户信息的操作。

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

2.2接口和抽象方法

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

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

/**
 * 更新用户的数据操作
 * @param uid
 * @param username
 * @param user
 */
void changeInfo(Integer uid,String username,User user);

2.3.实现抽象方法

 @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 user;
    }

    /**
     *
     *user对象中的数据phone \email\gender,手动再将uid\username封装user对象中
     */
    @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 _by_uid

GET
Httpsession sessionson

Result

2.点击修改按钮发生用户的数据修改操作

/users /change_info

POST
User user,

Httpsession sessions onResult

3.3处理请求

   @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){
        Integer uid = getuidFromSession(session);
        String username = getUsernameFromSession(session);
        userService.changeInfo(uid,username,user);
        return new JsonResult<>(OK);
    }

4.个人资料-前端页面

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

 /**
			 * 一旦检测到当前页面被加载就会触发ready里面的方法
			 * $(document).ready(function () {
			 *
			 * })
             */
            $(document).ready(function () {
                $.ajax({
                    url:"/users/get_by_uid",
                    type:"GET",
                    data:$("#form-change-info").serialize(),
                    dataType:"JSON",
                    success:function (json) {
                        if(json.state==200){
                            //将查询到的数据重新设置到控件中
                            $("#username").val(json.data.username);
                            $("#phone").val(json.data.phone);
                            $("#email").val(json.data.email);
                            let radio = json.data.gender == 0 ? $("#gender-female") : $("#gender-male");
                            // prop()表示给某个元素添加属性及属性的值
                            radio.prop("checked","checked")

                        }else {
                            alert("用户的数据不存在");
                        }
                    },
                    error:function (xhr) {
                        alert("修改密码时产生未知异常"+xhr.message)
                    }

                });
            });

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

上传头像

1上传头像-持久层

1.1SQL语句的规划

将对象文件保存在操作系统上,然后在把这个文件路径给记录在,因为在记录路径的是是非常便捷和方便,将如果要打开这个文件可以依据这个路径去找到这个文件。在数据库中需要要保存这个文件的路径即可。将左右的静态资源(图片、文件、其他资源文件)方法某台电脑上,在把这台电脑作为一台单独的服务器使用。

对应是一个更新用户avatar字段的sql语句。

   /**
     * 更好uid值来修改用户的头像
     * @param uid
     * @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接口的映射


        UPDATE t_user
        SET
          avater = #{avater},
          modified_user = #{modifiedUser},
          modified_time = #{modifiedTime},
          where uid = #{uid} ;
    

1.4测试

2上传头像-业务层

2.1规划异常

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

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

无需重复开发

 2.2设计接口和抽象方法

  /**
     * 修改用户的头像
     * @param uid
     * @param avatar
     * @param username
     */
    void changeAvatat(Integer uid ,String avatar,String username);

2.3实现抽象方法

 @Override
    public void changeAvatat(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("更新用户头像产生未知的异常");
        }

    }

 测试

 @Test
    public void changeAvatar(){
        userServiceImpl.changeAvatat(9,"/upload/test.png","小明");
    }

3上传头像-控制层

3.1规划异常

文件异常的父类:
        Fileup1oadException泛指文件上传的异常(父类)继承RuntimeException
父类是:FileuploadException
        FileEmptyException文件为空的异常

        FilesizeException文件大小超出限制

        FileTypeException文件类型异常
        FileuploadIOException文件读写的异常

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

3.2处理异常

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

else if (e instanceof FileSizeException) {
            result.setState(6001);
        } else if (e instanceof FileTypeException) {
            result.setState(6002);
        } else if (e instanceof FileStateException) {
            result.setState(6003);
        } else if (e instanceof FileUploadIOException) {
            result.setState(6004);
        }

在异常统一处理方法的参数列表上增加新的异常处理作为他的参数

@ExceptionHandler({ServerCloneException.class,FileUploadIOException.class})//用来统一处理抛出的异常

3.3设计请求

/users/change_avatar

POST(GET请求提交数据2kB)

Httpsession session,MutipartFile file

JsonResult

3.4实现请求

 @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("文件超出限制");
        }
        //如果集合包含某个元素则放回true
        String contentType = file.getContentType();
        if (!AVATAR_TYPE.contains(contentType)){
            throw new FileTypeException("文件类型不支持");
        }
        //上传文件../upload/文件.png
        String parent = session.getServletContext().getRealPath("upload");
        //file对象指向这个路径,file是否存在
        File dir = new File(parent);
        if (!dir.exists()){//检测目录是否存在
            dir.mkdir();//创建当前的目录
        }
        //获取文件名称,UUID工具生成一个新的字符串作为文件名
        //例如:avatar01.png
        String originalFilename = file.getOriginalFilename();
        System.out.println("OriginalFilename="+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);//将file文件中的数据写入dest文件中
        } catch (FileStateException e) {
            throw new FileUploadException("文件状态异常");
        }catch (IOException e){
            throw new FileUploadException("文件读写异常");
        }

        Integer uid = getuidFromSession(session);
        String username = getUsernameFromSession(session);
        //放回头像的路径/upload/test.png
        String avater = "/upload"+filename;
        userService.changeAvatat(uid,avater,username);
        //返回用户头像的路径给前端页面,将来用于头像展示使用
        return new JsonResult<>(OK,avater);

    }

4上传头像-前端页面

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

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

5.解决Bug

5.1更改默认的大小限制

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

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

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

方式2:需要采用Java代码的形式来设置文件的上传大小的限制。主类中进行配置,可以定义一个方法,必须使用@Bean修饰来修饰。在类的前天添加@Configration注解进行修改类。MutipartConfigElement类型。

@Bean
	public MultipartConfigElement getMultipartConfigElement(){
		//创建一个配置工厂类对象
		MultipartConfigFactory factory = new MultipartConfigFactory();
		//设置需要创建的对象的相关信息
		factory.setMaxFileSize(DataSize.of(10, DataUnit.MEGABYTES));
		factory.setMaxRequestSize(DataSize.of(15, DataUnit.MEGABYTES));
		return factory.createMultipartConfig();
	}

5.2显示头像

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

  • serialize():可以将表单数据数据自动拼接成key=value的结构进行提交给服务器—般提交是普通的控件类型中的数据(textlpassword\radiolcheckbox)等等
  • · FormData类:将表单中数据保持原有的结构进行数据的条件。

new FormData($("#form")[0]);//文件类型的数据可以使用FormData对象进行存储

  • ajax默认处理数据时按照字符串的形式进行处理,以及默认会采用字符串的形式进行提交数据。关闭这两个默认的功能。
processData:false,//处理数据的形式,关闭处理数据
contentType:false,//提交数据的形式,关闭默认提交数据的形式

5.3登录后显示头像

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

1.设置cookie中的值:

导入cookie.js文件

.调用cookie方法:

$.cookie(key,value,time)//单位为天

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

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

$(document).ready(function () {
				let avatar = $.cookie("avatar");
				$("img-avatar").attr("src",avatar);
            });

5.4显示最新头像

在更改完头像后,将最新的头像地址,再次保存到cookie,同名保存会覆盖有cookie中值。

$.cookie("avatar",json.data,{expires:7})

新增收货地址

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.新增收货地址创建实体类

创建一个类Address类,在类中定义表的相关字段,采用驼峰命名法方式,最后再去继承BaseEntity类。

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接口和抽象方法

创建一个接口AddressMapper,在这个接口中定义上面两个SQL语句抽象方法定义。

/**
	 *  新增收货地址
	 * @param address 地址的实体类
	 * @return 受影响的行数
	 */
	Integer insert(Address address);
	
	/**
	 *  根据用户的id统计收货地址数量
	 * @param uid   用户的id
	 * @return  当前用户的收货地址总数
	 */
	Integer countByUid(Integer uid);

3.4配置SQL映射

创建一个AddressMapper.xml映射文件,在这个文件中添加

  
        
        
        
        
        
        
        
        
        
        
        
        
    

    
        INSERT INTO t_address (
            uid, name, province_name, province_code, city_name, city_code, area_name, area_code, zip,
            address, phone, tel, tag, is_default, 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}
        )
    


    

3.测试业务层功能是否正常。AddressServiceTest测试类来测试业务功能。

@Test
public void insert() {
    Address address = new Address();
    address.setPhone("13333333333");
    address.setName("张四");
    addressService.addNewAddress(7,"管理员",address);
}

4.新增收货地址-控制层

4.1处理异常

如果用户是第一插入用户的收货地址,规则:当用户插入的地址是第一条时,需要将当前地址作为默认的收货地址,如果查询到统计总数为0则将当前地址的is_default值设置为1。查询统计的结果为0不代表异常。

查询到的结果大于20了,这时候需要抛出业务控制的异常AddressCountLimitException异常。自行创建这个异常。

/**
 *收货地址总数限制在(20)条
 */
public class ServiceException extends RuntimeException{

。。。。。。

}

插入数据是产生的异常InsertExceprion,不需要重复创建。

4.2接口和抽象方法

1.创建—个lAddressService接口,在中定义业务的抽象方法。

package com.cy.store.service;

import com.cy.store.entity.Address;

/**
 * 收获地址业务层接口
 */
public interface IAddressService {

    void addNewAddress(Integer uid, String username, Address address);
}

⒉.创建一个AddressServicelmpl实现类,去实现接口中抽象方法。在配置文件中定义数据。

在配置文件中定义数据

#Spring读取配置文件:@value("${user.address.max-count}}"),注解在变量上
user.address.max-count=20;

在实现类中实现业务控制

package com.cy.store.service.impl;

import com.cy.store.entity.Address;
import com.cy.store.mapper.AddressMapper;
import com.cy.store.service.IAddressService;
import com.cy.store.service.ex.AddressCountLimitException;
import com.cy.store.service.ex.InsertException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.Date;

/*新增地址的实现类*/
@Service
public class AddressServiceImpl implements IAddressService{
    @Autowired
    private AddressMapper addressMapper;
    @Value("${user.address.max-count}")
    private Integer maxCount;
    @Override
    public void addNewAddress(Integer uid, String username, Address address) {
        Integer count = addressMapper.countByid(uid);
        if (count>=maxCount){
            throw new AddressCountLimitException("用户收货地址超出上限");
        }
        // 补全数据:将参数uid封装到参数address中
        address.setUid(uid);
        // 补全数据:根据以上统计的数量,得到正确的isDefault值(是否默认:0-不默认,1-默认),并封装
        Integer isDefault = count == 0 ? 1 : 0;
        address.setIsDefault(isDefault);
        // 补全数据:4项日志
        Date now = new Date();
        address.setCreatedUser(username);
        address.setCreatedTime(now);
        address.setModifiedUser(username);
        address.setModifiedTime(now);
        //插入收货地址的方法
        Integer rows = addressMapper.insert(address);
        if(rows!=1){
            throw new InsertException("插入数据时产生了未知的异常");
        }

    }
}

3.测试业务层功能是否正常。AddressServiceTest测试类来测试业务功能。

  @Test
    public void addNewAddress(){
        Address address = new Address();
        address.setPhone("123456");
        address.setName("女朋友");
        addressService.addNewAddress(9,"管理员",address);
    }

5.新增收货地址-控制层

5.1处理异常

业务层抛出了收货地址总数超标的异常,在BaseController类中捕获。

else if(e instanceof AddressCountLimitException){
	        result.setstate(4003);
	        result.setMessage("用户地址超出限制");
	    }

5.2设计请求

/addressses/add_new_address
post
Address address,HttpSession session
JsonResult放回给前端

5.3处理请求

在控制层新建AddressController来处理用户收货地址的请求和响应。

package com.cy.store.controller;

import com.cy.store.entity.Address;
import com.cy.store.service.IAddressService;
import com.cy.store.util.JsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;

/*用户新增地址的业务层*/
@RequestMapping("/addresses")
@RestController
public class AddressController extends BaseController {

    @Autowired
    private IAddressService addressService;

    @RequestMapping("/add_new_address")
    public JsonResult addNewAddress(Address address,HttpSession session){
        Integer uid = getUidFromSession(session);
        String username = getUsernameFromSession(session);
        addressService.addNewAddress(uid,username,address);
        return new JsonResult<>(OK);
    }
}

注意:要先登陆了再测试,要不然访问不了session;

先登录用户,然后在访问http://localhost:8080/addresses/add_new_address?name=tom&phone=17858802222进行测试。

 @Test
    public void addNewAddress(){
        Address address = new Address();
        address.setPhone("123456");
        address.setName("女朋友");
        addressService.addNewAddress(9,"管理员",address);
    }

6 新增收货地址-前端页面

获取省市区列表

1.获取省市区列表-数据库

CREATE TABLE t_dict_district (
  id int(11) NOT NULL AUTO_INCREMENT,
  parent varchar(6) DEFAULT NULL,
  code varchar(6) DEFAULT NULL,
  name varchar(16) DEFAULT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

parent属性表示的是父区域代码号,省的代码号+86。

获取省市区列表-实体类

创建一个District实体类。

   private Integer id;//唯一标识
    private String parent;//父区域代号
    private String code;//本区域代号
    private  String name;//代号所表示的名称

2.获取省市区列表-实体类

创建一个District实体类。

   private Integer id;//唯一标识
    private String parent;//父区域代号
    private String code;//本区域代号
    private  String name;//代号所表示的名称

3.获取省市区列表-持久层

查询语句,根据父代号进行查询。

select * from t_dict_district where parent=? order by code ASC

抽象方法的定义。 DistrictMapper接口。

package com.cy.store.mapper;

import com.cy.store.entity.District;

import java.util.List;

public interface DistrictMapper {

    /**
     * 根据父代号查询区域信息
     * @param parent 父代号
     * @return 某区域下的所有区域列表
     */
    List findByParent(Integer parent);
}

在配置文件中编写对应的SQL






    


创建类测试接口

@Test
public void findByParent() {
    List districts = districtMapper.findByParent("110100");
    for (District district : districts) {
        System.out.println(district);
    }
}

4.获取省市区列表-业务层

1.创建接口IUdstrictService,并定义抽象方法。

package com.cy.store.service;

import com.cy.store.entity.District;

import java.util.List;

public interface IDistrictService {
    /**
     * 根据父代号查询区域信息
     * @param parent 父代号
     * @return 多个区域信息
     */
    List getByParent(String parent);
}

2.创建DistrictServiceImpl实现类,实现抽象方法。

package com.cy.store.service.impl;

import com.cy.store.entity.District;
import com.cy.store.mapper.DistrictMapper;
import com.cy.store.service.IDistrictService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class DistrictServiceImpl implements IDistrictService {
    @Autowired
    private DistrictMapper districtMapper;

    @Override
    public List getByParent(String parent) {
        List districts = districtMapper.findByParent(parent);
        /**在进行网络数据传输时,为了尽量避免无效数据的传递,可以将无效数据设置null
         * 节省了流量的开销,提升了效率
         */
        for (District district : districts) {
            district.setId(null);
            district.setParent(null);
        }
        return districts;
    }
}

3.单元测试

@Test
public void getByParent() {
    List districts = districtService.getByParent("86");
    for (District district : districts) {
        System.err.println(district);
    }
}

4.获取省市区列表-控制层

4.1设计请求

/districts/
GET
String parent
JsonResult

4.2请求方法

创建一个DistrictController类, 在类中来编写处理请求的方法。

@RestController
@RequestMapping("/districts")
public class DistrictController extends BaseController{
    @Autowired
    private IDistrictService districtService;

    //districts的请求都拦截到getByParent()方法
    @RequestMapping({"/",""})
    public JsonResult> getByParent(String parent){
        List data = districtService.getByParent(parent);
        return new JsonResult<>(OK,data);
    }

district请求添加白名单中。

patterns.add("/districts/**");

直接访问服务器,来访问localhost:8080/districts?parent=86访问进行测试。

5.获取省市区列表-前端页面

1.注释掉通过js来完成省市区列表加载的js代码


2.检查前端在提交省市区数据时是否有相关name属性和id属性。

3.运行前端是否还可以正常保存数据(除了省市区之外)。

获取省市区的名称

1.获取省市区的名称-持久层

1.规划根据当前code来获取当前省市区的名称,对应就是一条查询语句。

select * from t_dist_district where code=?

2.在DistrictMapper接口定义出来。

/**
 * 根据区对应的行政代号获取对应代号的省(市区)信息
 * @param code 行政代号
 * @return 省(市,区)
 */
String findNameByCode(String code);
}

3.在DistrictMapper.xml文件中添加抽象方法的映射。

   

 4.测试持久层接口

@Test
public void findNameByCode() {
    String name = districtMapper.findNameByCode("610000");
    System.out.println(name);
}

获取省市区的名称-业务层

1.在业务层没有异常处理

2.定义对应的业务层接口中的抽象方法。

String getNameByCode(String code);

3.编写接口对应的实现类

   @Override
    public String getNameByCode(String code) {
        String name = districtMapper.findNameByCode(code);
        return name;
    }

4.测试可以省略不写(超过8行以上代码都要测试)。

获取省市区的名称-控制层优化

1.添加地址层依赖于IDistrictService层。
//在添加用户的收货地址的业务层依赖于IDidtrictService的业务层接口

/*新增地址的实现类*/
@Service
public class AddressServiceImpl implements IAddressService{
    @Autowired
    private AddressMapper addressMapper;
    //在添加用户的收获地址业务层依赖于IdistrictService的业务层接口
    @Autowired
    private IDistrictService districtService;

2.在addNewAddress方法中将districtService接口中获取到的省市区数据转移到address对象,这个对象中就包含了所有用户收货地址的数据。

 //对address对象中的数据进行补全:省  市  区
    String provinceName = districtService.getNameByCode(address.getProvinceCode());
    String cityName = districtService.getNameByCode(address.getCityCode());
    String areaName = districtService.getNameByCode(address.getAreaCode());

    address.setProvinceCode(provinceName);
    address.setCityName(cityName);
    address.setAreaName(areaName);
       //对address对象的数据进行补全
        String provinceByCode = districtService.getNameByCode(address.getProvinceCode());
        String cityCode = districtService.getNameByCode(address.getCityCode());
        String areaByCode = districtService.getNameByCode(address.getAreaCode());
        address.setProvinceName(provinceByCode);
        address.setCityName(cityCode);
        address.setAddress(areaByCode);

获取省市区的名称-前端页面

1.addAddress.html页面中编写对应的省市区展示及根据不同用户的不同选择来限制对应的便签中的内容。
2.编写相关的事件代码。

设置默认收货地址

1.持久层

SQL语句的规划

1.检测当前用户设置为默认收货地址的这条数据是否存在。

select * from t_address where aid=?

2.在修改用户的收货默认地址前,先将所有的收货地址设置为非默认。

update t_address set is_default=0 where uid=?

3.将用户当前选中的这条数据设置为默认收货地址。

update t_address set is_default=1,modified_user=?,modified_time=? where aid=?

设计抽象方法

在AddressMappe接口中进行定义和声明

/**
 * 根据aid查询地址数据
 * @param aid   收货地址id
 * @return  收货地址数据,如果没有找到则返回null值
 */
Address findByAid(Integer aid);

/**
 * 根据用户的uid值来修改用户的收货地址设置为非默认
 * @param uid   用户的id值
 * @return 受影响的行数
 */
Integer updateNonDefault(Integer uid);

/**
 *
 * @param aid
 * @return
 */
Integer updateDefaultByAid(@Param("aid") Integer aid,
                           @Param("modifiedUser") String modifiedUser,
                           @Param("modifiedTime") Date modifiedTime);

配置SQL映射

AddressMapper.xml文件中进行配置


	    UPDATE t_address SET is_default=0 WHERE uid=#{uid}
	
	
	
	    UPDATE t_address SET is_default=1,modified_user=#{modifiedUser},modified_time=#{modifiedTime} WHERE aid=#{aid}
	
	
	

在单元测试方法中进行测试。

   @Test
public void findByAid(){
        Address byAid = addressMapper.findByAid(15);
        System.out.println(byAid);
    }

    @Test
public void updateNonDefault(){
    addressMapper.updateNonDefault(7);
    }

    @Test
public void updateDefaultByAid(){
    addressMapper.updateDefaultByAid(15,"管理员",new Date());
    }

2. 业务层

2.1 异常规划

1.在执行更新时产生位置的UpdateException异常.该异常已经创建

2.访问的数据不是当前登录用户的收货地址,非法访问:AccessDeniedException异常。

3.收货地址可能不存在异常:addressNotFoundException异常

2.2 抽象方法

在接口IAddressService中编写抽象方法

/**
     * 修改某个用户的某条收货地址数据为默认收货地址
     * @param aid 收货地址的id
     * @param uid 用户的id
     * @param username 表示修改的执行人
     */
void setAddressDefault(Integer aid,Integer uid,String username);

实现抽象方法

在实现类中实现接口中的方法

@Override
public void setDefault(Integer aid, Integer uid, String username) {
    Address result = addressMapper.findByAid(aid);
    if(result == null){
        throw new AddressNotFoundException("收货地址不存在");
    }
    //检测当前收货地址归属
    if(!result.getUid().equals(uid)){
        throw new AccessDeniedException("非法数据访问");
    }
    //先将所有的收货地址设置为0(不是默认地址)
    Integer rows = addressMapper.updateNonDefault(uid);
    if(rows < 1 ){
        throw new UpdateException("修改数据时产生了未知的异常");
    }
    //设置选中的地址数据为1(默认地址)
    Integer rows1 = addressMapper.updateDefaultByAid(aid, username, new Date());
    if(rows1 != 1){
        throw new UpdateException("修改数据时产生了未知的异常");
    }
}

编写测试类测试

@Test
public void setDefault(){
    addressService.setDefault(16,7,"管理员");
}

控制层

###处理异常
在BaseController类中进行统一的处理。

else if(e instanceof AddressCountLimitException){
        result.setstate(4003);
        result.setMessage("用户地址超出限制");
    }else if(e instanceof AccessDeniedException){
        result.setstate(4004);
        result.setMessage("非法数据访问");
    }

请求设计

/addresses/{aid}/set_default
@PathVariable(“id”) Integer aid,HttpSession session
GET
JsonResult

完成请求方法

在AddressController类中编写请求方法。

	//RestFul风格的请求编写
	@RequestMapping("{aid}/set_default")
	public JsonResult setDefault(@PathVariable("aid") Integer aid,HttpSession session){
	    Integer uid = getUidFromSession(session);
	    String username = getUsernameFromSession(session);
	    addressService.setDefault(aid,uid,username);
	    return new JsonResult<>(OK);
	}
打开浏览

器先登录再去访问一个请求路径localhost:8080/addresses/{aid}/set_default

4.前端页面

1.给设置默认收货地址按钮添加一个onclick属性,指向一个方法的调用,在这个方法中来完成ajax请求的方法。

let tr = '\n' +
	'#{tag}\n' +
	'#{name}\n' +
	'#{address}\n' +
	'#{phone}\n' +
	' 修改\n' +
	' 删除\n' +
	'设为默认\n' +
	'';
tr = tr.replace(/#{tag}/g, list[i].tag);
tr = tr.replace(/#{name}/g, list[i].name);
tr = tr.replace("#{address}", list[i].address);
tr = tr.replace("#{phone}", list[i].phone);
tr = tr.replace("#{aid}",list[i].aid); 

address.html页面点击"设置默认"按钮,来发送ajax请求。完成setDefault()方法的声明和定义。

/*设置地址为默认地址*/
function setDefault(aid) {
	$.ajax({
		url:"/addresses/"+ aid +"/set_default",
		type:"GET",
		dataType:"JSON",
		success:function(json){
			if(json.state == 200){
				//重新加载收货地址页面
				showAddressList();
			}else {
				alert("设置默认收货地址失败");
			}
		},
		error:function (xhr) {
			alert("设置默认收货地址时产生了未知的异常" + xhr.message);
		}
	});
}

先登录再访问address.html页面进行测试。

删除收货地址

持久层

规划需要执行的SQL语句。

1.在删除之前判断该数据是否存在,判断该条地址的归属是否是当前的用户。不用重复开发。
2.执行删除收货地址的信息。

delect * from t_address where aid=?

3.如果用户删除的是默认收货地址,将剩下的地址中的某一条设置为默认的收货地址。规则可以自定义:最新修改的收货地址设置为默认的收货地址(modified_time的字段值)。

#limit (n-1)n,pageSize
select * from t_address where uid=? order by modified_time DESC limit 0,1

4.如果用户本身就只有一条收货地址的数据,删除后其他操作就可以不进行了。

设计抽象方法

在AddressMapper中进行抽象方法的设计。

/**
 * 根据地址id删除地址信息
 * @param aid   地址id
 * @return 影响的行数
 */
Integer deleteByAid(Integer aid);

/**
 * 根据uid查询最后一次修改的地址
 * @param uid 用户id
 * @return 地址信息
 */
Address findLastModified(Integer uid);

映射SQL语句

在AddressMapper.xml文件中进行映射。


    DELETE FROM t_address WHERE aid=#{aid}



在测试类中进行测试

 @Test
public void deleteByAid(){
        Integer integer = addressMapper.deleteByAid(2);
        System.out.println(integer);
    }

    @Test
public void findLastModified(){
        Address lastModified = addressMapper.findLastModified(7);
        System.out.println(lastModified);
    }

业务层

规划异常

在执行删除的时候可能会产生未知的异常导致数据不能删除成功,则抛出DeleteException异常。需要定义和创建

/*删除数据时产生的异常*/
public class DeleteException extends ServiceException {...}

抽象方法的设计

在IAddressService编写抽象方法

 /**
     * 删除用户选中的收获地址数据
     * @param aid 收获地址id
     * @param uid 用户id
     * @param username  用户名
     */
    void delete(Integer aid,Integer uid,String username);

实现抽象方法

	/**删除用户的收货地址*/
    void delete(Integer aid,Integer uid,String username);
在AddressServiceImpl类中实现接口中的抽象方法
    @Override
    public void delete(Integer aid, Integer uid, String username) {
        Address result = addressMapper.findByAid(aid);
        if(result == null){
            throw new AddressNotFoundException("地址信息不存在");
        }//检测当前收货地址归属
        if(!result.getUid().equals(uid)){
            throw new AccessDeniedException("非法数据访问");
        }
        Integer rows = addressMapper.deleteByAid(aid);
        if(rows!= 1){
            throw new UpdateException("删除用户地址时产生了未知的异常");
        }
        Integer count = addressMapper.countByUid(uid);
        if(count == 0){
            //直接终止程序
            return;
        }
        if(result.getIsDefault() == 0){
            return;
        }
        Address address = addressMapper.findLastModified(uid);
        //将这条数据的idDefault设置为1
        Integer integer = addressMapper.updateDefaultByAid(address.getAid(), username, new Date());
        if(integer != 1){
            throw new UpdateException("更新数据时产生未知的异常");
        }
    }

测试类进行测试

  @Test
    public void delete(){
        addressService.delete(16,7,"管理员");
    }

控制层

1.需要处理异常DeleteException类。

else if(e instanceof DeleteException){
        result.setstate(5002);
        result.setMessage("删除数据时发生了未知的异常");
    }

4.访问地址localhost:8080/addresses/{aid}/delete进行测试。

前端页面

在address.html页面中来添加删除按钮的事件

	' 删除\n' 
再去编写deleteByAid(aid)方法的具体实现
	function deleteByAid(aid) {
		$.ajax({
			url: "/addresses/" + aid + "/delete",
			type: "POST",
			dataType: "JSON",
			success: function(json) {
				if (json.state == 200) {
					showAddressList();
				} else {
					alert("删除收货地址失败!" + json.message);
				}
			},
			error: function(json) {
				alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + json.status);
				location.href = "login.html";
			}
		});
	}

商品热销排行

商品-创建数据表

1.使用use命令先选中store数据库。

USE store;

2.在store数据库中创建t_product数据表。

CREATE TABLE t_product (
  id int(20) NOT NULL COMMENT '商品id',
  category_id int(20) DEFAULT NULL COMMENT '分类id',
  item_type varchar(100) DEFAULT NULL COMMENT '商品系列',
  title varchar(100) DEFAULT NULL COMMENT '商品标题',
  sell_point varchar(150) DEFAULT NULL COMMENT '商品卖点',
  price bigint(20) DEFAULT NULL COMMENT '商品单价',
  num int(10) DEFAULT NULL COMMENT '库存数量',
  image varchar(500) DEFAULT NULL COMMENT '图片路径',
  status int(1) DEFAULT '1' COMMENT '商品状态  1:上架   2:下架   3:删除',
  priority int(10) DEFAULT NULL COMMENT '显示优先级',
  created_time datetime DEFAULT NULL COMMENT '创建时间',
  modified_time datetime DEFAULT NULL COMMENT '最后修改时间',
  created_user varchar(50) DEFAULT NULL COMMENT '创建人',
  modified_user varchar(50) DEFAULT NULL COMMENT '最后修改人',
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

商品-创建实体类

创建com.cy.store.entity.Product类,并继承自BaseEntity类。在类中声明与数据表中对应的属性。

package com.cy.store.entity;

/** 商品数据的实体类 */
public class Product extends BaseEntity implements Serializable {
    private Integer id;
    private Integer categoryId;
    private String itemType;
    private String title;
    private String sellPoint;
    private Long price;
    private Integer num;
    private String image;
    private Integer status;
    private Integer priority;

    // Generate: Getter and Setter、Generate hashCode() and equals()、toString()
}

商品-热销排行-持久层

规划需要执行的SQL语句

查询热销商品列表的SQL语句大致是。

SELECT * FROM t_product WHERE status=1 ORDER BY priority DESC LIMIT 0,4

接口与抽象方法

在com.cy.store.mapper包下创建ProductMapper接口并在接口中添加查询热销商品findHotList()的方法。

package com.cy.store.mapper;
import com.cy.store.entity.Product;
import java.util.List;

/** 处理商品数据的持久层接口 */
public interface ProductMapper {
    /**
     * 查询热销商品的前四名
     * @return 热销商品前四名的集合
     */
    List findHotList();
}

配置SQL映射

1.在main\resources\mapper文件夹下创建ProductMapper.xml文件,并在文件中配置findHotList()方法的映射。




    
        
        
        
        
        
        
        
        
    

    
    

2.在com.cy.store.mapper包下创建ProductMapperTests测试类,并添加测试方法。

package com.cy.store.mapper;
import com.cy.store.entity.Product;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ProductMapperTests {
    @Autowired
    private ProductMapper productMapper;

    @Test
    public void findHotList() {
        List list = productMapper.findHotList();
        System.out.println("count=" + list.size());
        for (Product item : list) {
            System.out.println(item);
        }
    }
}

商品-热销排行-业务层

规划异常

说明:无异常。

接口与抽象方法

创建com.cy.store.service.IProductService接口,并在接口中添加findHotList()方法。

package com.cy.store.service;
import com.cy.store.entity.Product;
import java.util.List;

/** 处理商品数据的业务层接口 */
public interface IProductService {
    /**
     * 查询热销商品的前四名
     * @return 热销商品前四名的集合
     */
    List findHotList();
}

实现抽象方法

1.创建com.cy.store.service.impl.ProductServiceImpl类,并添加@Service注解;在类中声明持久层对象以及实现接口中的方法

package com.cy.store.service.impl;
import com.cy.store.entity.Product;
import com.cy.store.mapper.ProductMapper;
import com.cy.store.service.IProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

/** 处理商品数据的业务层实现类 */
@Service
public class ProductServiceImpl implements IProductService {
    @Autowired
    private ProductMapper productMapper;

    @Override
    public List findHotList() {
        List list = productMapper.findHotList();
        for (Product product : list) {
            product.setPriority(null);
            product.setCreatedUser(null);
            product.setCreatedTime(null);
            product.setModifiedUser(null);
            product.setModifiedTime(null);
        }
        return list;
    }
}

2.在com.cy.store.service包下创建测试类ProductServiceTests,并编写测试方法。

package com.cy.store.service;
import com.cy.store.entity.Product;
import com.cy.store.service.ex.ServiceException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ProductServiceTests {
    @Autowired
    private IProductService productService;

    @Test
    public void findHotList() {
        try {
            List list = productService.findHotList();
            System.out.println("count=" + list.size());
            for (Product item : list) {
                System.out.println(item);
            }
        } catch (ServiceException e) {
            System.out.println(e.getClass().getSimpleName());
            System.out.println(e.getMessage());
        }
    }
}

商品-热销排行-控制器

处理异常

说明:无异常。

设计请求

1.设计用户提交的请求,并设计响应的方式。

请求路径:/products/hot_list
请求参数:无
请求类型:GET
响应结果:JsonResult>
是否拦截:否,需要将index.html和products/**添加到白名单

2.在LoginInterceptorConfigurer类中将index.html页面和products/**请求添加到白名单。 

patterns.add("/web/index.html");
patterns.add("/products/**");

处理请求

1.创建com.cy.controller.ProductController类继承自BaseController类,类添加@RestController和@RequestMapping(“products”)注解,并在类中添加业务层对象。

package com.cy.store.controller;
import com.cy.store.entity.Product;
import com.cy.store.service.IProductService;
import com.cy.store.util.JsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;

@RestController
@RequestMapping("products")
public class ProductController extends BaseController {
    @Autowired
    private IProductService productService;
}

2.在类中添加处理请求的getHotList()方法。

@RequestMapping("hot_list")
public JsonResult> getHotList() {
    List data = productService.findHotList();
    return new JsonResult>(OK, data);
}

3.完成后启动项目,直接访问http://localhost:8080/products/hot_list进行测试。

商品-热销排行-前端页面

1.在index.html页面给“热销排行”列表的div标签设置id属性值。

2.在index.html页面中body标签内部的最后,添加展示热销排行商品的代码。


3.完成后启动项目,直接访问http://localhost:8080/web/index.html进行测试。

显示商品详情

商品-显示商品详情-持久层

规划需要执行的SQL语句

根据商品id显示商品详情的SQL语句大致是

SELECT * FROM t_product WHERE id=?

接口与抽象方法

在ProductMapper接口中添加抽象方法。

/**
 * 根据商品id查询商品详情
 * @param id 商品id
 * @return 匹配的商品详情,如果没有匹配的数据则返回null
 */
Product findById(Integer id);

配置SQL映射

1.在ProductMapper.xml文件中配置findById(Integer id)方法的映射。



2.在ProductMapperTests测试类中添加测试方法。

@Test
public void findById() {
    Integer id = 10000017;
    Product result = productMapper.findById(id);
    System.out.println(result);
}

商品-显示商品详情-业务层

规划异常

如果商品数据不存在,应该抛出ProductNotFoundException,需要创建com.cy.store.service.ex.ProductNotFoundException异常。

package com.cy.store.service.ex;

/** 商品数据不存在的异常 */
public class ProductNotFoundException extends ServiceException {
    // Override Methods...
}

接口与抽象方法

在业务层IProductService接口中添加findById(Integer id)抽象方法。

/**
 * 根据商品id查询商品详情
 * @param id 商品id
 * @return 匹配的商品详情,如果没有匹配的数据则返回null
 */
Product findById(Integer id);

实现抽象方法

1.在ProductServiceImpl类中,实现接口中的findById(Integer id)抽象方法。

@Override
public Product findById(Integer id) {
    // 根据参数id调用私有方法执行查询,获取商品数据
    Product product = productMapper.findById(id);
    // 判断查询结果是否为null
    if (product == null) {
        // 是:抛出ProductNotFoundException
        throw new ProductNotFoundException("尝试访问的商品数据不存在");
    }
    // 将查询结果中的部分属性设置为null
    product.setPriority(null);
    product.setCreatedUser(null);
    product.setCreatedTime(null);
    product.setModifiedUser(null);
    product.setModifiedTime(null);
    // 返回查询结果
    return product;
}

2.在ProductServiceTests测试类中编写测试方法。

@Test
public void findById() {
    try {
        Integer id = 100000413;
        Product result = productService.findById(id);
        System.out.println(result);
    } catch (ServiceException e) {
        System.out.println(e.getClass().getSimpleName());
        System.out.println(e.getMessage());
    }
}

商品-显示商品详情-控制器

处理异常

在BaseController类中的handleException()方法中添加处理ProductNotFoundException的异常。

// ...
else if (e instanceof ProductNotFoundException) {
	result.setState(4006);
}
// ...

设计请求

设计用户提交的请求,并设计响应的方式

请求路径:/products/{id}/details
请求参数:@PathVariable("id") Integer id
请求类型:GET
响应结果:JsonResult

处理请求

1.在ProductController类中添加处理请求的getById()方法。

@GetMapping("{id}/details")
public JsonResult getById(@PathVariable("id") Integer id) {
    // 调用业务对象执行获取数据
    Product data = productService.findById(id);
    // 返回成功和数据
    return new JsonResult(OK, data);
}

 2.完成后启动项目,直接访问http://localhost:8080/products/10000017/details进行测试。

商品-显示商品详情-前端页面

1.检查在product.html页面body标签内部的最后是否引入jquery-getUrlParam.js文件,如果引入无需重复引入。


2.在product.html页面中body标签内部的最后添加获取当前商品详情的代码。


3.完成后启动项目,先访问http://localhost:8080/web/index.html页面,然后点击“热销排行”中的某个子项,将跳转到product.html商品详情页,观察页面是否加载的是当前的商品信息。

加入购入车

购物车-创建数据表

1.使用use命令先选中store数据库。

USE store;

2.在store数据库中创建t_cart用户数据表。

CREATE TABLE t_cart (
	cid INT AUTO_INCREMENT COMMENT '购物车数据id',
	uid INT NOT NULL COMMENT '用户id',
	pid INT NOT NULL COMMENT '商品id',
	price BIGINT COMMENT '加入时商品单价',
	num INT COMMENT '商品数量',
	created_user VARCHAR(20) COMMENT '创建人',
	created_time DATETIME COMMENT '创建时间',
	modified_user VARCHAR(20) COMMENT '修改人',
	modified_time DATETIME COMMENT '修改时间',
	PRIMARY KEY (cid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

购物车-创建实体类

在com.cy.store.entity包下创建购物车的Cart实体类。

package com.cy.store.entity;
import java.io.Serializable;

/** 购物车数据的实体类 */
public class Cart extends BaseEntity implements Serializable {
    private Integer cid;
    private Integer uid;
    private Integer pid;
    private Long price;
    private Integer num;

    // Generate: Getter and Setter、Generate hashCode() and equals()、toString()
}

购物车-添加购物车-持久层

规划需要执行的SQL语句

向购物车表中插入商品数据的SQL语句大致是:

insert into t_cart (除了cid以外的字段列表) values (匹配的值列表);

如果用户曾经将某个商品加入到购物车过,则点击“加入购物车”按钮只会对购物车中相同商品数量做递增操作。

update t_cart set num=? where cid=?

关于判断“到底应该插入数据,还是修改数量”,可以通过“查询某用户是否已经添加某商品到购物车”来完成。如果查询到某结果,就表示该用户已经将该商品加入到购物车了,如果查询结果为null,则表示该用户没有添加过该商品。

select * from t_cart where uid=? and uid=?

接口与抽象方法

在com.cy.store.mapper包下创建CartMapper接口,并添加抽象相关的方法。

package com.cy.store.mapper;
import com.cy.store.entity.Cart;
import org.apache.ibatis.annotations.Param;
import java.util.Date;

/** 处理购物车数据的持久层接口 */
public interface CartMapper {
    /**
     * 插入购物车数据
     * @param cart 购物车数据
     * @return 受影响的行数
     */
    Integer insert(Cart cart);

    /**
     * 修改购物车数据中商品的数量
     * @param cid 购物车数据的id
     * @param num 新的数量
     * @param modifiedUser 修改执行人
     * @param modifiedTime 修改时间
     * @return 受影响的行数
     */
    Integer updateNumByCid(
            @Param("cid") Integer cid,
            @Param("num") Integer num,
            @Param("modifiedUser") String modifiedUser,
            @Param("modifiedTime") Date modifiedTime);

    /**
     * 根据用户id和商品id查询购物车中的数据
     * @param uid 用户id
     * @param pid 商品id
     * @return 匹配的购物车数据,如果该用户的购物车中并没有该商品,则返回null
     */
    Cart findByUidAndPid(
            @Param("uid") Integer uid,
            @Param("pid") Integer pid);
}

配置SQL映射

1.在resources.mapper文件夹下创建CartMapper.xml文件,并在文件中配置以上三个方法的映射。




    
        
        
        
        
        
    

    
    
        INSERT INTO t_cart (uid, pid, price, num, created_user, created_time, modified_user, modified_time)
        VALUES (#{uid}, #{pid}, #{price}, #{num}, #{createdUser}, #{createdTime}, #{modifiedUser}, #{modifiedTime})
    

    
    
        UPDATE
            t_cart
        SET
            num=#{num},
            modified_user=#{modifiedUser},
            modified_time=#{modifiedTime}
        WHERE
            cid=#{cid}
    

    
    

2.在com.cy.store.mapper包下创建CartMapperTests测试类,并添加测试方法。

package com.cy.store.mapper;
import com.cy.store.entity.Cart;
import com.cy.store.entity.Product;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Date;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class CartMapperTests {
    @Autowired
    private CartMapper cartMapper;

    @Test
    public void insert() {
        Cart cart = new Cart();
        cart.setUid(1);
        cart.setPid(2);
        cart.setNum(3);
        cart.setPrice(4L);
        Integer rows = cartMapper.insert(cart);
        System.out.println("rows=" + rows);
    }

    @Test
    public void updateNumByCid() {
        Integer cid = 1;
        Integer num = 10;
        String modifiedUser = "购物车管理员";
        Date modifiedTime = new Date();
        Integer rows = cartMapper.updateNumByCid(cid, num, modifiedUser, modifiedTime);
        System.out.println("rows=" + rows);
    }

    @Test
    public void findByUidAndPid() {
        Integer uid = 1;
        Integer pid = 2;
        Cart result = cartMapper.findByUidAndPid(uid, pid);
        System.out.println(result);
    }
}

购物车-添加购物车-业务层

规划异常

在插入数据时,可能抛出InsertException异常;在修改数据时,可能抛出UpdateException异常。如果不限制购物车中的记录的数量,则没有其它异常。

接口与抽象方法

在com.cy.store.service包下创建ICartService接口,并添加抽象方法。

package com.cy.store.service;

/** 处理商品数据的业务层接口 */
public interface ICartService {
    /**
     * 将商品添加到购物车
     * @param uid 当前登录用户的id
     * @param pid 商品的id
     * @param amount 增加的数量
     * @param username 当前登录的用户名
     */
    void addToCart(Integer uid, Integer pid, Integer amount, String username);
}

实现抽象方法

1.创建com.cy.store.service.impl.CartServiceImpl类,并实现ICartService接口,并在类的定义前添加@Service注解。在类中声明CartMapper持久层对象和IProductService处理商品数据的业务对象,并都添加@Autowired注修饰。

package com.cy.store.service.impl;
import com.cy.store.entity.Cart;
import com.cy.store.entity.Product;
import com.cy.store.mapper.CartMapper;
import com.cy.store.service.ICartService;
import com.cy.store.service.IProductService;
import com.cy.store.service.ex.InsertException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;

/** 处理购物车数据的业务层实现类 */
@Service
public class CartServiceImpl implements ICartService {
    @Autowired
    private CartMapper cartMapper;
    @Autowired
    private IProductService productService;
}

2.在CartServiceImpl类中实现业务层ICartService接口中定义的抽象方法。

@Override
public void addToCart(Integer uid, Integer pid, Integer amount, String username) {
    // 根据参数pid和uid查询购物车中的数据
    // 判断查询结果是否为null
    // 是:表示该用户并未将该商品添加到购物车
    // -- 创建Cart对象
    // -- 封装数据:uid,pid,amount
    // -- 调用productService.findById(pid)查询商品数据,得到商品价格
    // -- 封装数据:price
    // -- 封装数据:4个日志
    // -- 调用insert(cart)执行将数据插入到数据表中
    // 否:表示该用户的购物车中已有该商品
    // -- 从查询结果中获取购物车数据的id
    // -- 从查询结果中取出原数量,与参数amount相加,得到新的数量
    // -- 执行更新数量
}

3.addToCart(Integer uid, Integer pid, Integer amount, String username)方法的代码具体实现。

@Override
public void addToCart(Integer uid, Integer pid, Integer amount, String username) {
    // 根据参数pid和uid查询购物车中的数据
    Cart result = cartMapper.findByUidAndPid(uid, pid);
    Integer cid = result.getCid();
    Date now = new Date();
    // 判断查询结果是否为null
    if (result == null) {
        // 是:表示该用户并未将该商品添加到购物车
        // 创建Cart对象
        Cart cart = new Cart();
        // 封装数据:uid,pid,amount
        cart.setUid(uid);
        cart.setPid(pid);
        cart.setNum(amount);
        // 调用productService.findById(pid)查询商品数据,得到商品价格
        Product product = productService.findById(pid);
        // 封装数据:price
        cart.setPrice(product.getPrice());
        // 封装数据:4个日志
        cart.setCreatedUser(username);
        cart.setCreatedTime(now);
        cart.setModifiedUser(username);
        cart.setModifiedTime(now);
        // 调用insert(cart)执行将数据插入到数据表中
        Integer rows = cartMapper.insert(cart);
        if (rows != 1) {
            throw new InsertException("插入商品数据时出现未知错误,请联系系统管理员");
        }
    } else {
        // 否:表示该用户的购物车中已有该商品
        // 从查询结果中获取购物车数据的id
        Integer cid = result.getCid();
        // 从查询结果中取出原数量,与参数amount相加,得到新的数量
        Integer num = result.getNum() + amount;
        // 执行更新数量
        Integer rows = cartMapper.updateNumByCid(cid, num, username, now);
        if (rows != 1) {
            throw new InsertException("修改商品数量时出现未知错误,请联系系统管理员");
        }
    }
}

4.在com.cy.store.service包下创建测试类CartServiceTests类,并编写测试方法。

package com.cy.store.service;
import com.cy.store.entity.Product;
import com.cy.store.service.ex.ServiceException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class CartServiceTests {
    @Autowired
    private ICartService cartService;

    @Test
    public void addToCart() {
        try {
            Integer uid = 2;
            Integer pid = 10000007;
            Integer amount = 1;
            String username = "Tom";
            cartService.addToCart(uid, pid, amount, username);
            System.out.println("OK.");
        } catch (ServiceException e) {
            System.out.println(e.getClass().getSimpleName());
            System.out.println(e.getMessage());
        }
    }
}

购物车-添加购物车-控制器

处理异常

说明:无异常。

设计请求

设计用户提交的请求,并设计响应的方式

请求路径:/carts/add_to_cart
请求参数:Integer pid, Integer amount, HttpSession session
请求类型:POST
响应结果:JsonResult

处理请求

1.在com.cy.store.controller包下创建CartController类并继承自BaseController类,添加@RequestMapping(“carts”)和@RestController注解;在类中声明ICartService业务对象,并使用@Autowired注解修饰。

package com.cy.store.controller;
import com.cy.store.service.ICartService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("carts")
public class CartController extends BaseController {
    @Autowired
    private ICartService cartService;

}

2.在CartController类中添加处理请求的addToCart()方法。

@RequestMapping("add_to_cart")
public JsonResult addToCart(Integer pid, Integer amount, HttpSession session) {
    // 从Session中获取uid和username
    Integer uid = getUidFromSession(session);
    String username = getUsernameFromSession(session);
    // 调用业务对象执行添加到购物车
    cartService.addToCart(uid, pid, amount, username);
    // 返回成功
    return new JsonResult(OK);
}

3.完成后启动项目,先登录再访问http://localhost:8080/carts/add_to_cart?pid=10000017&amount=3进行测试。

购物车-添加购物车-前端页面

在ajax函数中data参数的数据设置的方式

  • data:$("from表单选择").serialize().当参数过多并且在同一个表单中,字符串的提交等。
  • data:new FormData($("form表单选择器")[0]).只适用提交文件
  • data:"userename=Tom".适合参数值固定并且参数值列表有限,可以手动拼接

let user =  "Tom"

data:"username"+user

  • 适用JSON格式提交数据

data:{

        "username":Tom,

        "age":18,

        "sex":0

}

1.在product.html页面中的body标签内的script标签里为“加入购物车”按钮添加点击事件。

$("#btn-add-to-cart").click(function() {
    $.ajax({
        url: "/carts/add_to_cart",
        type: "POST",
        data: {
            "pid": id,
            "amount": $("#num").val()
        },
        dataType: "JSON",
        success: function(json) {
            if (json.state == 200) {
                alert("增加成功!");
            } else {
                alert("增加失败!" + json.message);
            }
        },
        error: function(xhr) {
            alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
            location.href = "login.html";
        }
    });
});

$.ajax函数中参数data提交请参数的方式:

// 1.适用于参数较多,且都在同一个表单中
data: $("#form表单id属性值").serialize()
// 2.仅适用于上传文件
data: new FormData($("##form表单id属性值")[0])
// 3.参数拼接形式提交
data: "pid=10000005&amount=3"
// 4.使用JSON格式提交参数
data: {
   	"pid": 10000005,
   	"amount": 3
}

2.完成后启动项目,先登录再访问http://localhost:8080/web/index.html页面进行测试。

购物车-显示列表-持久层

规划需要执行的SQL语句

显示某用户的购物车列表数据的SQL语句大致是。

SELECT
	cid,
	uid,
	pid,
	t_cart.price,
	t_cart.num,
	t_product.title,
	t_product.price AS realPrice,
	t_product.image
FROM
	t_cart
	LEFT JOIN t_product ON t_cart.pid = t_product.id 
WHERE
	uid = #{uid}
ORDER BY
	t_cart.created_time DESC

接口与抽象方法

1.由于涉及多表关联查询,必然没有哪个实体类可以封装此次的查询结果,因此需要创建VO类。创建com.cy.store.vo.CartVO类。

VO: Value 0bject,值对象。当进行SELECT查询时,查询的结果数据多张表中的内容,此时发现结果集不直接使用某个POJO实体类来接收,POJ0实体类不能包含多表查询出来的结果。解决方式是:重新去构建一个新的对象这个对象用户存储所查询出来的结果集对应的映射,所以把想这个的对象称之为值对象。

package com.cy.store.vo;
import java.io.Serializable;

/** 购物车数据的Value Object类 */
public class CartVO implements Serializable {
    private Integer cid;
    private Integer uid;
    private Integer pid;
    private Long price;
    private Integer num;
    private String title;
    private Long realPrice;
    private String image;
    
    // Generate: Getter and Setter、Generate hashCode() and equals()、toString()
}

2.在CartMapper接口中添加抽象方法。

/**
 * 查询某用户的购物车数据
 * @param uid 用户id
 * @return 该用户的购物车数据的列表
 */
List findVOByUid(Integer uid);

配置SQL映射

1.在CartMapper.xml文件中添加findVOByUid()方法的映射。



2.在CartMapperTests测试类中添加findVOByUid()方法的测试。

@Test
public void findVOByUid() {
    List list = cartMapper.findVOByUid(31);
    System.out.println(list);
}

购物车-显示列表-业务层

规划异常

说明:无异常。

接口与抽象方法

在ICartService接口中添加findVOByUid()抽象方法。

/**
 * 查询某用户的购物车数据
 * @param uid 用户id
 * @return 该用户的购物车数据的列表
 */
List getVOByUid(Integer uid);

实现抽象方法

1.在CartServiceImpl类中重写业务接口中的抽象方法。

@Override
public List getVOByUid(Integer uid) {
    return cartMapper.findVOByUid(uid);
}

2.在CartServiceTests测试类中添加getVOByUid()测试方法。

@Test
public void getVOByUid() {
    List list = cartService.getVOByUid(31);
    System.out.println("count=" + list.size());
    for (CartVO item : list) {
        System.out.println(item);
    }
}

购物车-显示列表-控制器

处理异常

说明:无异常。

设计请求

设计用户提交的请求,并设计响应的方式。

请求路径:/carts/
请求参数:HttpSession session
请求类型:GET
响应结果:JsonResult>

处理请求

1.在CartController类中编写处理请求的代码。

@GetMapping({"", "/"})
public JsonResult> getVOByUid(HttpSession session) {
    // 从Session中获取uid
    Integer uid = getUidFromSession(session);
    // 调用业务对象执行查询数据
    List data = cartService.getVOByUid(uid);
    // 返回成功与数据
    return new JsonResult>(OK, data);
}

2.完成后启动项目,先登录再访问http://localhost:8080/carts请求进行测试。

购物车-显示列表-前端页面

1.将cart.html页面的head头标签内引入的cart.js文件注释掉。


2.给form标签添加action="orderConfirm.html"属性、tbody标签添加id="cart-list"属性、结算按钮的类型改为type="button"值。如果以上属性值已经添加过无需重复添加。

3.在cart.html页面body标签内的script标签中编写展示购物车列表的代码。
 

$(document).ready(function() {
    showCartList();
});

function showCartList() {
    $("#cart-list").empty();
    $.ajax({
        url: "/carts",
        type: "GET",
        dataType: "JSON",
        success: function(json) {
            let list = json.data;
            for (let i = 0; i < list.length; i++) {
                let tr = ''
                + ''
                + 	''
                + ''
                + ''
                + '#{title}#{msg}'
                + '¥#{realPrice}'
                + ''
                + 	''
                + 	''
                + 	''
                + ''
                + '¥#{totalPrice}'
                + ''
                + 	''
                + ''
                + '';
                tr = tr.replace(/#{cid}/g, list[i].cid);
                tr = tr.replace(/#{title}/g, list[i].title);
                tr = tr.replace(/#{image}/g, list[i].image);
                tr = tr.replace(/#{realPrice}/g, list[i].realPrice);
                tr = tr.replace(/#{num}/g, list[i].num);
                tr = tr.replace(/#{totalPrice}/g, list[i].realPrice * list[i].num);

                if (list[i].realPrice < list[i].price) {
                    tr = tr.replace(/#{msg}/g, "比加入时降价" + (list[i].price - list[i].realPrice) + "元");
                } else {
                    tr = tr.replace(/#{msg}/g, "");
                }
                $("#cart-list").append(tr);
            }
        }
    });
}

4.完成后启动项目,先登录再访问http://localhost:8080/web/cart.html页面进行测试。

增加商品数量

购物车-增加商品数量-持久层

规划需要执行的SQL语句

1.首先进行查询需要操作的购物车数据信息。

SELECT * FROM t_cart WHERE cid=?

2.然后计算出新的商品数量值,如果满足更新条件则执行更新操作。此SQL语句无需重复开发。

UPDATE t_cart SET num=?, modified_user=?, modified_time=? WHERE cid=?

接口与抽象方法

在CartMapper接口中添加抽象方法。

/**
 * 根据购物车数据id查询购物车数据详情
 * @param cid 购物车数据id
 * @return 匹配的购物车数据详情,如果没有匹配的数据则返回null
 */
Cart findByCid(Integer cid);

2.在CartMapperTests测试类中添加findByCid()测试方法。

@Test
public void findByCid() {
	Integer cid = 6;
	Cart result = cartMapper.findByCid(cid);
	System.out.println(result);
}

购物车-增加商品数量-业务层

规划异常

1.如果尝试访问的购物车数据不存在,则抛出CartNotFoundException异常。创建com.cy.store.service.ex.CartNotFoundException类。

/** 购物车数据不存在的异常 */
public class CartNotFoundException extends ServiceException {
	// Override Methods...
}

2.如果尝试访问的数据并不是当前登录用户的数据,则抛出AccessDeniedException异常。此异常类无需再次创建。

3.最终执行更新操作时,可能会抛出UpdateException异常。此异常类无需再次创建。

接口与抽象方法

在业务层ICartService接口中添加addNum()抽象方法。

/**
 * 将购物车中某商品的数量加1
 * @param cid 购物车数量的id
 * @param uid 当前登录的用户的id
 * @param username 当前登录的用户名
 * @return 增加成功后新的数量
 */
Integer addNum(Integer cid, Integer uid, String username);

实现抽象方法

1.在CartServiceImpl类中,实现接口中的抽象方法并规划业务逻辑。

public Integer addNum(Integer cid, Integer uid, String username) {
	// 调用findByCid(cid)根据参数cid查询购物车数据
	// 判断查询结果是否为null
	// 是:抛出CartNotFoundException

	// 判断查询结果中的uid与参数uid是否不一致
	// 是:抛出AccessDeniedException

	// 可选:检查商品的数量是否大于多少(适用于增加数量)或小于多少(适用于减少数量)
	// 根据查询结果中的原数量增加1得到新的数量num

	// 创建当前时间对象,作为modifiedTime
	// 调用updateNumByCid(cid, num, modifiedUser, modifiedTime)执行修改数量
}

2.实现addNum()方法中的业务逻辑代码。

@Override
public Integer addNum(Integer cid, Integer uid, String username) {
    // 调用findByCid(cid)根据参数cid查询购物车数据
    Cart result = cartMapper.findByCid(cid);
    // 判断查询结果是否为null
    if (result == null) {
        // 是:抛出CartNotFoundException
        throw new CartNotFoundException("尝试访问的购物车数据不存在");
    }

    // 判断查询结果中的uid与参数uid是否不一致
    if (!result.getUid().equals(uid)) {
        // 是:抛出AccessDeniedException
        throw new AccessDeniedException("非法访问");
    }

    // 可选:检查商品的数量是否大于多少(适用于增加数量)或小于多少(适用于减少数量)
    // 根据查询结果中的原数量增加1得到新的数量num
    Integer num = result.getNum() + 1;

    // 创建当前时间对象,作为modifiedTime
    Date now = new Date();
    // 调用updateNumByCid(cid, num, modifiedUser, modifiedTime)执行修改数量
    Integer rows = cartMapper.updateNumByCid(cid, num, username, now);
    if (rows != 1) {
        throw new InsertException("修改商品数量时出现未知错误,请联系系统管理员");
    }

    // 返回新的数量
    return num;
}

3.在CartServiceTests测试类中添加addNum()测试方法。

@Test
public void addNum() {
    try {
        Integer cid = 6;
        Integer uid = 31;
        String username = "管理员";
        Integer num = cartService.addNum(cid, uid, username);
        System.out.println("OK. New num=" + num);
    } catch (ServiceException e) {
        System.out.println(e.getClass().getSimpleName());
        System.out.println(e.getMessage());
    }
}

购物车-增加商品数量-控制器

处理异常

在BaseController类中添加CartNotFoundException异常类的统一管理。

// ...
else if (e instanceof CartNotFoundException) {
    result.setState(4007);
    result.setMessage("购物车数据不存在异常");
}
// ...

设计请求

设计用户提交的请求,并设计响应的方式。

请求路径:/carts/{cid}/num/add
请求参数:@PathVariable("cid") Integer cid, HttpSession session
请求类型:POST
响应结果:JsonResult

处理请求

1.在CartController类中添加处理请求的addNum()方法。

@RequestMapping("{cid}/num/add")
public JsonResult addNum(@PathVariable("cid") Integer cid, HttpSession session) {
    // 从Session中获取uid和username
    Integer uid = getUidFromSession(session);
    String username = getUsernameFromSession(session);
    // 调用业务对象执行增加数量
    Integer data = cartService.addNum(cid, uid, username);
    // 返回成功
    return new JsonResult(OK, data);
}

2.完成后启动项目,先登录再访问http://localhost:8080/carts/6/num/add页面进行测试。

购物车-增加商品数量-前端页面

1.首先确定在showCartList()函数中动态拼接的增加购物车按钮是绑定了addNum()事件,如果已经添加无需重复添加。


2.在script标签中定义addNum()函数并编写增加购物车数量的逻辑代码。

function addNum(cid) {
    $.ajax({
        url: "/carts/" + cid + "/num/add",
        type: "POST",
        dataType: "JSON",
        success: function(json) {
            if (json.state == 200) {
                // showCartList();
                $("#num-" + cid).val(json.data);
                let price = $("#price-" + cid).html();
                let totalPrice = price * json.data;
                $("#total-price-" + cid).html(totalPrice);
            } else {
                alert("增加商品数量失败!" + json.message);
            }
        },
        error: function(xhr) {
            alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
            location.href = "login.html";
        }
    });
}

3.完成后启动项目,先登录再访问http://localhost:8080/web/cart.html页面点击“+”按钮进行测试。

显示勾选的购物车数据

1.持久层

1.1 规划SQL语句

        用户在购物车列表页中通过随机勾选相关的商品,在点击"结算"按钮后,跳转到结算页面,在这个页面中需要展示用户在上个页面所勾选的购物车对应的数据。列表的展示,而展示的内容还是还在于购物车的表。两个页面需要用户勾选的多个cid传递给下一个页面。

SELECT
	cid,
	uid,
	pid,
	t_cart.price,
	t_cart.num,
	t_product.title,
	t_product.price AS realPrice,
	t_product.image
FROM
	t_cart
	LEFT JOIN t_product ON t_cart.pid = t_product.id 
WHERE
	cid IN (?,?,?,?)
ORDER BY
	t_cart.created_time DESC

1.2接口和抽象方法

List findVOByCid(Integer[] cids);

1.3配置对应的映射

2.业务层

1.没有需要规划的异常

2.设计业务层接口中抽象方法

List getVOByCid(Integer[] cids);

3.完成抽象方法的设计

    @Override
    public List getVOByCid(Integer uid, Integer[] cids) {
        List list = cartMapper.findVOByCid(cids);
        ListIterator lit = list.listIterator();
        while (lit.hasNext()){
            CartVO cartVO = lit.next();
            if (cartVO.getUid().equals(uid)){
                list.remove(cartVO);
            }
        }

        return list;
    }

3.控制层

1.请求的设计

/ carts /list
Integer cids ,Httpsession sessionPOST
JsonResult>

2.完成请求处理方法的定义和声明

@RequestMapping({"list"})
    public JsonResult> getVOByCid(Integer[] cids,HttpSession session) {
        List data = cartService.getVOByCid(getuidFromSession(session), cids);
        return new JsonResult>(OK, data);
    }

4.前端页面

1.将cart .html页面中“结算”按钮属性更改成type="submit".

2.orderComfirm.html页面中,再去请求ajax,中进行填充都单签页面的某个区域中


                    
                    

你可能感兴趣的:(spring,boot,mysql,java)