电脑商城项目

系统概述与环境搭建

1 系统开发及运行环境

电脑商城系统开发所需的环境及相关软件进行介绍。

1.操作系统:Windows 10

2.Java开发包:JDK 8

3.项目管理工具:Maven 3.5.4

4.项目开发工具:IntelliJ IDEA 2019.3.3 x64

5.数据库:MySql

6.浏览器:Google Chrome

7.服务器架构:Spring Boot 2.4.7 + MyBatis 2.1.4 + AJAX

2 项目分析

1.在开发某个项目之前,应先分析这个项目中可能涉及哪些种类的数据。本项目中涉及的数据:用户、商品、商品类别、收藏、订单、购物车、收货地址。

2.关于数据,还应该要确定这些数据的开发顺序。设计开发顺序的原则是:先开发基础、简单或熟悉的数据。以上需要处理的数据的开发流程是:用户-收货地址-商品类别-商品-收藏-购物车-订单。

3.在开发每种数据的相关功能时,先分析该数据涉及哪些功能。在本项目中以用户数据为例,需要开发的功能有:登录、注册、修改密码、修改资料、上传头像。

4.然后,在确定这些功能的开发顺序。原则上,应先做基础功能,并遵循增查删改的顺序来开发。则用户相关功能的开发顺序应该是:注册-登录-修改密码-修改个人资料-上传头像。

5.在实际开发中,应先创建该项目的数据库,当每次处理一种新的数据时,应先创建该数据在数据库中的数据表,然后在项目中创建该数据表对应的实体类。

6.在开发某个具体的功能时,应遵循开发顺序:持久层-业务层-控制器-前端页面。

3 创建数据库

1.首先确保计算机上安装了MySql数据库,将来在数据库中创建与项目相关的表。

2.创建电脑商城项目对应的后台数据库系统store。

CREATE DATABASE store character SET utf8;

4 创建Spring Initializr项目

本质上Spring Initializr是一个Web应用程序,它提供了一个基本的项目结构,能够帮助开发者快速构建一个基础的Spring Boot项目。在创建Spring Initializr类型的项目时需在计算机连网的状态下进行创建。

1.首先确保计算机上安装了JDK、IDEA、mysql等开发需要使用的软件,并在IDEA中配置了Maven 3.5.4项目管理工具。

2.在IDEA欢迎界面,点击【New Project】按钮创建项目,左侧选择【Spring Initializr】选项进行Spring Boot项目快速构建

3.将Group设置为com.yu,Artifact设置为store,其余选项使用默认值。单击【Next】进入Spring Boot场景依赖选择界面。  

4.给项目添加Spring Web、MyBatis Framework、MySQL Driver的依赖。点击【Next】按钮完成项目创建。

5.首次创建完Spring Initializr项目时,解析项目依赖需消耗一定时间(Resolving dependencies of store...)。

5 配置并运行项目

5.1 运行项目

找到项目的入口类(被@SpringBootApplication注解修饰),然后运行启动类;启动过程如果控制台输出Spring图形则表示启动成功。

package com.yu.store;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StoreApplication {
    public static void main(String[] args) {
        SpringApplication.run(StoreApplication.class, args);
    }
}

5.2 配置项目

1.如果启动项目时提示:“配置数据源失败:'url'属性未指定,无法配置内嵌的数据源”。有如下的错误提示。

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

 

2.解决以上操作提示的方法:在resources文件夹下的application.properties文件中添加数据源的配置。

spring.datasource.url=jdbc:mysql://localhost:3306/store?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=mysql

 3.为了便于查询JSON数据,隐藏没有值的属性,减少流量的消耗,服务器不应该向客户端响应为NULL的属性。可以在属性或类之前添加@JsonInclude(value=Include.NON_NULL),也可以在application.properties中添加全局的配置。

# 服务器向客户端不响应为null的属性
spring.jackson.default-property-inclusion=NON_NULL

 4.SpringBoot项目的默认访问名称是“/”,如果需要修改可以手动在配置文件中指定SpringBoot 2.x访问项目路径的项目名。不建议修改。

server.servlet.context-path=/store

5.重新启动项目,则不在提示配置数据源失败的问题。  

用户注册

1 用户-创建数据表

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

USE store;

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

CREATE TABLE t_user (
	uid INT AUTO_INCREMENT COMMENT '用户id',
	username VARCHAR(20) NOT NULL UNIQUE COMMENT '用户名',
	password CHAR(32) NOT NULL COMMENT '密码',
	salt CHAR(36) COMMENT '盐值',
	phone VARCHAR(20) COMMENT '电话号码',
	email VARCHAR(30) COMMENT '电子邮箱',
	gender INT COMMENT '性别:0-女,1-男',
	avatar VARCHAR(50) COMMENT '头像',
	is_delete INT COMMENT '是否删除:0-未删除,1-已删除',
	created_user VARCHAR(20) COMMENT '日志-创建人',
	created_time DATETIME COMMENT '日志-创建时间',
	modified_user VARCHAR(20) COMMENT '日志-最后修改执行人',
	modified_time DATETIME COMMENT '日志-最后修改时间',
	PRIMARY KEY (uid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2 用户-创建实体类

1.项目中许多实体类都会有日志相关的四个属性,所以在创建实体类之前,应先创建这些实体类的基类,将4个日志属性声明在基类中。在com.cy.store.entity包下创建BaseEntity类,作为实体类的基类。

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

/** 实体类的基类 */
public class BaseEntity implements Serializable {
    private String createdUser;
    private Date createdTime;
    private String modifiedUser;
    private Date modifiedTime;

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

2.创建com.cy.store.entity.User用户数据的实体类,继承自BaseEntity类,在类中声明与数据表中对应的属性。

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

/** 用户数据的实体类 */
public class User extends BaseEntity implements Serializable {
    private Integer uid;
    private String username;
    private String password;
    private String salt;
    private String phone;
    private String email;
    private Integer gender;
    private String avatar;
    private Integer isDelete;

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

3 用户-注册-持久层

3.1 准备工作

1.在src/test/java下的com.cy.store.StoreApplicationTests测试类中编写并执行“获取数据库连接”的单元测试,以检查数据库连接的配置是否正确。

@Autowired
private DataSource dataSource;

@Test
public void getConnection() throws Exception {
	System.out.println(dataSource.getConnection());
}

2.执行src/test/java下的com.cy.toreApplicationTests测试类中的contextLoads()测试方法,以检查测试环境是否正常。

3.2 规划需要执行的SQL语句

1.用户注册的本质是向用户表中插入数据,需要执行的SQL语句大致是:

INSERT INTO t_user (除了uid以外的字段列表) VALUES (匹配的值列表)

2.由于数据表中用户名字段被设计为UNIQUE,在执行插入数据之前,还应该检查该用户名是否已经被注册,因此需要有“根据用户名查询用户数据”的功能。需要执行的SQL语句大致是:

SELECT * FROM t_user WHERE username=? 

3.3 接口与抽象方法

1.创建com.cy.store.mapper.UserMapper接口,并在接口中添加抽象方法。

package com.cy.mapper;
import com.cy.store.entity.User;

/** 处理用户数据操作的持久层接口 */
public interface UserMapper {
    /**
     * 插入用户数据
     * @param user 用户数据
     * @return 受影响的行数
     */
    Integer insert(User user);

    /**
     * 根据用户名查询用户数据
     * @param username 用户名
     * @return 匹配的用户数据,如果没有匹配的数据,则返回null
     */
    User findByUsername(String username);
}

MyBatis与Spring整合后需要实现实体和数据表的映射关系。实现实体和数据表的映射关系可以在Mapper接口上添加@Mapper注解。但建议以后直接在SpringBoot启动类中加@MapperScan("mapper包") 注解,这样会比较方便,不需要对每个Mapper都添加@Mapper注解。

@SpringBootApplication
@MapperScan("com.cy.store.mapper")
public class StoreApplication {

	public static void main(String[] args) {
		SpringApplication.run(StoreApplication.class, args);
	}
}

3.4 配置SQL映射

1.在src/main/resources下创建mapper文件夹,并在该文件夹下创建UserMapper.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">
    <resultMap id="UserEntityMap" type="com.cy.store.entity.User">
        <id column="uid" property="uid"/>
        <result column="is_delete" property="isDelete"/>
        <result column="created_user" property="createdUser"/>
        <result column="created_time" property="createdTime"/>
        <result column="modified_user" property="modifiedUser"/>
        <result column="modified_time" property="modifiedTime"/>
    </resultMap>

    <!-- 插入用户数据:Integer insert(User user) -->
    <insert id="insert" useGeneratedKeys="true" keyProperty="uid">
        INSERT INTO
            t_user (username, password, salt, phone, email, gender, avatar, is_delete, created_user, created_time, modified_user, modified_time)
        VALUES
        (#{username}, #{password}, #{salt}, #{phone}, #{email}, #{gender}, #{avatar}, #{isDelete}, #{createdUser}, #{createdTime}, #{modifiedUser}, #{modifiedTime})
    </insert>

    <!-- 根据用户名查询用户数据:User findByUsername(String username) -->
    <select id="findByUsername" resultMap="UserEntityMap">
        SELECT
            *
        FROM
            t_user
        WHERE
            username = #{username}
    </select>
</mapper>

2.由于这是项目中第一次使用SQL映射,所以需要在application.properties中添加mybatis.mapper-locations属性的配置,以指定XML文件的位置。

mybatis.mapper-locations=classpath:mapper/*.xml

3.完成后及时执行单元测试,检查以上开发的功能是否可正确运行。在src/test/java下创建com.cy.store.mapper.UserMapperTests单元测试类,在测试类的声明之前添加@RunWith(SpringRunner.class)和@SpringBootTest注解,并在测试类中声明持久层对象,通过自动装配来注入值。

@RunWith(SpringRunner.class)注解是一个测试启动器,可以加载SpringBoot测试注解。  

package com.cy.store.mapper;
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;

// @RunWith(SpringRunner.class)注解是一个测试启动器,可以加载Springboot测试注解
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTests {
    @Autowired
    private UserMapper userMapper;

}

 4.如果在第四步自动装配userMapper时,报“Could not autowire. No beans of 'UserMapper' type found”错,无法进行自动装配。解决方案是,将Autowiring for bean class选项下的Severity设置为Warning即可。

5.然后编写两个测试方法,对以上完成的两个功能进行单元测试。

单元测试方法必须为public修饰,方法的返回值类型必须为void,方法不能有参数列表,并且方法被@Test注解修饰。

@Test
public void insert() {
    User user = new User();
    user.setUsername("user01");
    user.setPassword("123456");
    Integer rows = userMapper.insert(user);
    System.out.println("rows=" + rows);
}

@Test
public void findByUsername() {
    String username = "user01";
    User result = userMapper.findByUsername(username);
    System.out.println(result);
}

如果出现org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)异常可能原因:

1.在resources文件加下创建的mapper文件夹类型没有正确选择(eclipse选择Folder,idea选择Directory)。

2.映射文件的mapper标签的namespace属性没有正确映射到dao层接口,或者application.properties中的属性mybatis.mapper-locations没有正确配置xml映射文件。

4 用户-注册-业务层

4.1 业务的定位

1.业务:一套完整的数据处理过程,通常表现为用户认为的一个功能,但是在开发时对应多项数据操作。在项目中,通过业务控制每个“功能”(例如注册、登录等)的处理流程和相关逻辑。

2.流程:先做什么,再做什么。例如:注册时,需要先判断用户名是否被占用,再决定是否完成注册。

3.逻辑:能干什么,不能干什么。例如:注册时,如果用户名被占用,则不允许注册;反之,则允许注册。

4.业务的主要作用是保障数据安全和数据的完整性、有效性。

4.2 规划异常

1.关于异常

1.请列举你认识的不少于十种异常:

Throwable
    Error
        OutOfMemoryError(OOM)
    Exception
        SQLException
        IOException
            FileNotFoundException
        RuntimeException
            NullPointerException
            ArithmeticException
            ClassCastException
            IndexOutOfBoundsException
                ArrayIndexOutOfBoundsException
                StringIndexOutOfBoundsException

2.异常的处理方式和处理原则:

异常的处理方式有:捕获处理(try...catch...finally),声明抛出(throw/throws)。如果当前方法适合处理,则捕获处理;如果当前方法不适合处理,则声明抛出。

2.异常规划

1.为了便于统一管理自定义异常,应先创建com.cy.store.service.ex.ServiceException自定义异常的基类异常,继承自RuntimeException类,并从父类生成子类的五个构造方法。

package com.cy.store.service.ex;

/** 业务异常的基类 */
public class ServiceException extends RuntimeException {
	
}

2.当用户进行注册时,可能会因为用户名被占用而导致无法正常注册,此时需要抛出用户名被占用的异常,因此可以设计一个用户名重复的com.cy.store.service.ex.UsernameDuplicateException异常类,继承自ServiceException类,并从父类生成子类的五个构造方法。

package com.cy.store.service.ex;

/** 用户名重复的异常 */
public class UsernameDuplicateException extends ServiceException {
    // Override Methods...
}

 3.在用户进行注册时,会执行数据库的INSERT操作,该操作也是有可能失败的。则创建com.cy.store.service.ex.InsertException`异常类,继承自ServiceException类,并从父类生成子类的5个构造方法。

package com.cy.store.service.ex;

/** 插入数据的异常 */
public class InsertException extends ServiceException {
    // Override Methods...
}

4.3 接口与抽象方法

1.先创建com.cy.store.service.IUserService业务层接口,并在接口中添加抽象方法。

package com.cy.store.service;
import com.cy.store.entity.User;

/** 处理用户数据的业务层接口 */
public interface IUserService {
    /**
     * 用户注册
     * @param user 用户数据
     */
    void reg(User user);
}

4.4 实现抽象方法

1.创建com.cy.store.service.impl.UserServiceImpl业务层实现类,并实现IUserService接口。在类之前添加@Service注解,并在类中添加持久层UserMapper对象。

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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/** 处理用户数据的业务层实现类 */
@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public void reg(User user) {
        // TODO
    }
}

2.reg()方法的具体实现过程

 @Override
    public void reg(User user) {
        // 根据参数user对象获取注册的用户名
        String username = user.getUsername();
        // 调用持久层的User findByUsername(String username)方法,根据用户名查询用户数据
        User result = userMapper.findByUsername(username);
        // 判断查询结果是否不为null
        if(result!=null){
            //抛出异常   // 是:表示用户名已被占用,则抛出UsernameDuplicateException异常
            throw new UsernameDuplicateException("用户名被占用");
        }


        // 创建当前时间对象
        Date now = new Date();
        System.out.println(now);
        //密码加密处理的实现:
        //(串+password+串)-----md5算法进行加密,连续加载三次
        //盐值+password+盐值-----盐值就是一个随机的字符串
        //获取盐值(随机生成一个盐值)
        String salt = UUID.randomUUID().toString().toUpperCase();
        // 补全数据:加密后的密码
        String md5Password = getMd5Password(user.getPassword(), salt);
        user.setPassword(md5Password);
        // 补全数据:盐值
        user.setSalt(salt);
        // 补全数据:isDelete(0)
        user.setIsDelete(0);
        // 补全数据:4项日志属性
        user.setCreatedUser(username);
        user.setCreatedTime(now);
        user.setModifiedUser(username);
        user.setModifiedTime(now);
        // 表示用户名没有被占用,则允许注册
        // 调用持久层Integer insert(User user)方法,执行注册并获取返回值(受影响的行数)
        Integer rows = userMapper.insert(user);
        if(rows!=1){
            throw new InsertException("在用户注册过程中产生了未知的异常");
        }
        // 判断受影响的行数是否不为1
        // 是:插入数据时出现某种错误,则抛出InsertException异常


    }

5 用户-注册-控制器

5.1 创建响应结果类

创建com.cy.store.util.JsonResult响应结果类型。

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

/**
 * 响应结果类
 * @param <E> 响应数据的类型
 */
public class JsonResult<E> implements Serializable {
    /** 状态码 */
    private Integer state;
    /** 状态描述信息 */
    private String message;
    /** 数据 */
    private E data;

    public JsonResult() {
        super();
    }

    public JsonResult(Integer state) {
        super();
        this.state = state;
    }

    /** 出现异常时调用 */
    public JsonResult(Throwable e) {
        super();
        // 获取异常对象中的异常信息
        this.message = e.getMessage();
    }

    public JsonResult(Integer state, E data) {
        super();
        this.state = state;
        this.data = data;
    }

    // Generate: Getter and Setter
}

5.2 设计请求

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

请求路径:/users/reg
请求参数:User user
请求类型:POST
响应结果:JsonResult<Void>

5.3 处理请求

1.创建com.cy.store.controller.UserController控制器类,在类的声明之前添加@RestController和@RequestMapping("users")注解,在类中添加IUserService业务对象并使用@Autowired注解修饰。

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

/** 处理用户相关请求的控制器类 */
@RestController
@RequestMapping("users")
public class UserController {
    @Autowired
    private IUserService userService;
}

 2.然后在类中添加处理请求的用户注册方法。

@RequestMapping("reg")
public JsonResult<Void> reg(User user) {
    // 创建返回值
    JsonResult<Void> result = new JsonResult<Void>();
    try {
        // 调用业务对象执行注册
        userService.reg(user);
        // 响应成功
        result.setState(200);
    } catch (UsernameDuplicateException e) {
        // 用户名被占用
        result.setState(4000);
        result.setMessage("用户名已经被占用");
    } catch (InsertException e) {
        // 插入数据异常
        result.setState(5000);
        result.setMessage("注册失败,请联系系统管理员");
    }
    return result;
}

5.4 控制器层的调整

1.然后创建提供控制器类的基类com.cy.store.controller.BaseController,在其中定义表示响应成功的状态码及统一处理异常的方法。

@ExceptionHandler注解用于统一处理方法抛出的异常。当我们使用这个注解时,需要定义一个异常的处理方法,再给这个方法加上@ExceptionHandler注解,这个方法就会处理类中其他方法(被@RequestMapping注解)抛出的异常。@ExceptionHandler注解中可以添加参数,参数是某个异常类的class,代表这个方法专门处理该类异常。

package com.cy.store.controller;
import com.cy.store.service.ex.InsertException;
import com.cy.store.service.ex.ServiceException;
import com.cy.store.service.ex.UsernameDuplicateException;
import com.cy.store.util.JsonResult;
import org.springframework.web.bind.annotation.ExceptionHandler;

/** 控制器类的基类 */
public class BaseController {
    /** 操作成功的状态码 */
    public static final int OK = 200;

    /** @ExceptionHandler用于统一处理方法抛出的异常 */
    @ExceptionHandler(ServiceException.class)
    public JsonResult<Void> handleException(Throwable e) {
        JsonResult<Void> result = new JsonResult<Void>(e);
        if (e instanceof UsernameDuplicateException) {
            result.setState(4000);
        } else if (e instanceof InsertException) {
            result.setState(5000);
        }
        return result;
    }
}

2.最后简化UserController控制器类中的用户注册reg()方法的代码。

/** 处理用户相关请求的控制器类 */
@RestController
@RequestMapping("users")
public class UserController extends BaseController {
    @Autowired
    private IUserService userService;

    @RequestMapping("reg")
    public JsonResult<Void> reg(User user) {
        // 调用业务对象执行注册
        userService.reg(user);
        // 返回
        return new JsonResult<Void>(OK);
    }
}

6 用户-注册-前端页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta charset="UTF-8">
		<!--edge浏览器H5兼容设置-->
		<meta http-equiv="X-UA-Compatible" content="IE=edge">
		<!--360浏览器H5兼容设置-->
		<meta name="renderer" content="webkit" />
		<title>电脑商城</title>
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<!--导入核心文件-->
		<script src="../bootstrap3/js/holder.js"></script>
		<link href="../bootstrap3/css/bootstrap.css" rel="stylesheet" type="text/css">
		<script src="../bootstrap3/jquery-1.9.1.min.js"></script>
		<script src="../bootstrap3/js/bootstrap.js"></script>
		<!-- 字体图标 -->
		<link rel="stylesheet" href="../bootstrap3/font-awesome-4.7.0/css/font-awesome.css" />
		<link rel="stylesheet" type="text/css" href="../css/top.css" />
		<link rel="stylesheet" type="text/css" href="../css/index.css" />
		<link rel="stylesheet" type="text/css" href="../css/layout.css" />
		<link rel="stylesheet" type="text/css" href="../css/reg.css" />
		<link rel="stylesheet" type="text/css" href="../css/footer.css" />
	</head>
	<body>
		<!--头部-->
		<header class="header">
			<img src="../images/index/stumalllogo.png" alt="" />
			<span class="pull-right"><small>欢迎访问</small><b>电脑商城</b></span>
		</header>
		<!--主体-->
		<div class="container mycontent text-left">
			<!--透明层 -->
			<div class="regDiv">
				<p>新用户注册</p>
				<!--表单开始-->
				<form id="form-reg" class="form-horizontal" role="form">
					<!--用户名-->
					<div class="form-group">
						<label class="col-md-3 control-label">名字:</label>
						<div class="col-md-8">
							<input name="username" type="text" class="form-control" placeholder="请输入用户名" id="username">
						</div>
					</div>
					<!--密码-->
					<div class="form-group">
						<label class="col-md-3 control-label"> 密码:</label>
						<div class="col-md-8">
							<input name="password" type="password" class="form-control" placeholder="请输入密码" id="password">
						</div>
					</div>
					<!--确认密码-->
					<div class="form-group">
						<label class="col-md-3 control-label"> 确认密码:</label>
						<div class="col-md-8">
							<input type="password" class="form-control" placeholder="请再次输入密码" id="password1">
						</div>
					</div>
					<!--提交按钮-->
					<div class="form-group">
						<label class="col-md-3 control-label"></label>
						<div class="col-md-8">
							<input id="btn-reg" class="btn btn-primary" type="button" value="立即注册" />
							<span class="pull-right"><small>已经有账号?</small><a href="login.html">登录</a></span>
						</div>
					</div>
				</form>
			</div>
		</div>
		<!--页脚开始-->
<!--		<div th:replace="~{common/common::footer}"></div>-->
		<!--页脚结束-->
	<script>
		$("#btn-reg").click(function(){
			var usernameText=$("#username").val();
			var usernamePatt=/^\w{5,12}$/;
			if(!usernamePatt.test(usernameText)){
				alert("用户名不合法!")
				return false;
			}
			var passwordText = $("#password").val();
			var passwordPatt=/^\w{5,12}$/;
			if(!passwordPatt.test(passwordText)){
				alert("密码不合法!");
				return false;
			}
			var password1Text=$("#password1").val();
			if(passwordText!=password1Text){
				alert("确认密码和密码不一致!");
				return false;
			}
			//通过序列化表单值,创建URL编码文本字符串
			console.log($("#form-reg").serialize());
			$.ajax({
				url: "/users/reg",
				type: "POST",
				data: $("#form-reg").serialize(),
				dataType: "json",
				success: function(json) {
					if (json.state == 200) {
						alert("注册成功!");
						// location.href = "login.html";
					} else {
						alert("注册失败!" + json.message);
					}
				}
			});
		})
	</script>
	</body>

</html>

用户登录

1 用户-登录-持久层

1.1 规划需要执行的SQL语句

用户登录功能需要执行的SQL语句是根据用户名查询用户数据,再判断密码是否正确。SQL语句大致是:

SELECT * FROM t_user WHERE username=?

2 用户-登录-业务层

2.1 规划异常

1.如果用户名不存在则登录失败,抛出com.cy.store.service.ex.UserNotFoundException异常,并从父类生成子类的五个构造方法。

package com.cy.store.service.ex;

/** 用户数据不存在的异常 */
public class UserNotFoundException extends ServiceException {
    // Override Methods...
}

 2.如果用户的isDelete字段的值为1,则表示当前用户数据被标记为“已删除”,需进行登录失败操作同时抛出UserNotFoundException。

3.如果密码错误则进行登录失败操作,同时抛出com.cy.store.service.ex.PasswordNotMatchException异常。

package com.cy.store.service.ex;

/** 密码验证失败的异常 */
public class PasswordNotMatchException extends ServiceException {
    // Override Methods...
}

4.创建以上UserNotFoundException和PasswordNotMatchException异常类,以上异常类应继承自ServiceException类。

2.2 接口与抽象方法

在IUserService接口中添加登录功能的抽象方法

/**
 * 用户登录
 * @param username 用户名
 * @param password 密码
 * @return 登录成功的用户数据
 */
User login(String username, String password);

当登录成功后需要获取该用户的id,以便于后续识别该用户的身份,并且还需要获取该用户的用户名、头像等数据,用于显示在软件的界面中,需使用可以封装用于id、用户名和头像的数据的类型来作为登录方法的返回值类型。

2.3 实现抽象方法

1.在UserServiceImpl类中添加login(String username, String password)方法并分析业务逻辑。

2.login(String username, String password)方法中代码的具体实现。

Override
public User login(String username, String password) {
    // 调用userMapper的findByUsername()方法,根据参数username查询用户数据
    User result = userMapper.findByUsername(username);
    // 判断查询结果是否为null
    if (result == null) {
        // 是:抛出UserNotFoundException异常
        throw new UserNotFoundException("用户数据不存在的错误");
    }

    // 判断查询结果中的isDelete是否为1
    if (result.getIsDelete() == 1) {
        // 是:抛出UserNotFoundException异常
        throw new UserNotFoundException("用户数据不存在的错误");
    }

    // 从查询结果中获取盐值
    String salt = result.getSalt();
    // 调用getMd5Password()方法,将参数password和salt结合起来进行加密
    String md5Password = getMd5Password(password, salt);
    // 判断查询结果中的密码,与以上加密得到的密码是否不一致
    if (!result.getPassword().equals(md5Password)) {
        // 是:抛出PasswordNotMatchException异常
        throw new PasswordNotMatchException("密码验证失败的错误");
    }

    // 创建新的User对象
    User user = new User();
    // 将查询结果中的uid、username、avatar封装到新的user对象中
    user.setUid(result.getUid());
    user.setUsername(result.getUsername());
    user.setAvatar(result.getAvatar());
    // 返回新的user对象
    return user;
}

3 用户-登录-控制器

3.1 处理异常

处理用户登录功能时,在业务层抛出了UserNotFoundException和PasswordNotMatchException异常,而这两个异常均未被处理过。则应在BaseController类的处理异常的方法中,添加这两个分支进行处理。

@ExceptionHandler(ServiceException.class)
public JsonResult<Void> handleException(Throwable e) {
	JsonResult<Void> result = new JsonResult<Void>(e);
	if (e instanceof UsernameDuplicateException) {
		result.setState(4000);
	} else if (e instanceof UserNotFoundException) {
		result.setState(4001);
	} else if (e instanceof PasswordNotMatchException) {
		result.setState(4002);
	} else if (e instanceof InsertException) {
		result.setState(5000);
	}
	return result;
}

3.2 设计请求

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

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

3.3 处理请求

1.在UserController类中添加处理登录请求的login(String username, String password)方法。

2.处理登录请求的login(String username, String password)方法代码具体实现。

@RequestMapping("login")
public JsonResult<User> login(String username, String password) {
	// 调用业务对象的方法执行登录,并获取返回值
	User data = userService.login(username, password);
	// 将以上返回值和状态码OK封装到响应结果中并返回
	return new JsonResult<User>(OK, data);
}

4 用户-登录-前端页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
	<head >
		<meta charset="UTF-8">
		<!--edge浏览器H5兼容设置-->
		<meta http-equiv="X-UA-Compatible" content="IE=edge">
		<!--360浏览器H5兼容设置-->
		<meta name="renderer" content="webkit" />
		<title>电脑商城</title>
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<!--导入核心文件-->
		<script src="../bootstrap3/js/holder.js"></script>
		<link href="../bootstrap3/css/bootstrap.css" rel="stylesheet" type="text/css">
		<script src="../bootstrap3/jquery-1.9.1.min.js"></script>
		<script src="../bootstrap3/js/bootstrap.js"></script>
		<!-- 字体图标 -->
		<link rel="stylesheet" href="../bootstrap3/font-awesome-4.7.0/css/font-awesome.css" />
		<link rel="stylesheet" type="text/css" href="../css/top.css" />
		<link rel="stylesheet" type="text/css" href="../css/index.css" />
		<link rel="stylesheet" type="text/css" href="../css/layout.css" />
		<link rel="stylesheet" type="text/css" href="../css/login.css" />
		<link rel="stylesheet" type="text/css" href="../css/footer.css" />
		<script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8"></script>
		<script src="../js/autoLogin.js" type="text/javascript"></script>
	</head>
	<body>
		<div th:replace="~{common/common::header}"></div>
		<!--主体-->
		<div class="container mycontent text-left">
			<!--透明层 -->
			<div class="loginDiv">
				<p>用户登录</p>
				<!--表单开始-->
				<form id="form-login" action=""  class="form-horizontal" role="form" >
					<!--用户名-->
					<div class="form-group">
						<label for="username" class="col-md-3 control-label">名字:</label>
						<div class="col-md-8">
							<input name="username" type="text" class="form-control" id="username"  placeholder="请输入用户名" value="cat">
						</div>
					</div>
					<!--密码-->
					<div class="form-group">
						<label for="password" class="col-md-3 control-label"> 密码:</label>
						<div class="col-md-8">
							<input name="password" type="password" class="form-control" id="password"  placeholder="请输入密码" value="123456">
						</div>
					</div>
					<!-- 记住我-->
					<div class="form-group">
						<div class="col-md-offset-3 col-md-6">
							<div class="checkbox">
								<label>
									<input type="checkbox" id="auto" name="remember">自动登录
								</label>
							</div>
						</div>
					</div>
					<!--提交按钮-->
					<div class="form-group">
						<label class="col-md-3 control-label"></label>
						<div class="col-md-8">
							<input id="btn-login" class="btn btn-primary" type="button" value="登录" />
							<span class="pull-right"><small>还没有账号?</small><a href="register.html">注册</a></span>
						</div>
					</div>
				</form>
			</div>
		</div>
		<div th:replace="~{common/common::footer}"></div>
		<script type="text/javascript">
			$("#btn-login").click(function() {
				$.ajax({
					url: "/users/login",
					type: "POST",
					data: $("#form-login").serialize(),
					dataType: "json",
					success: function(json) {
						if (json.state == 200) {
							alert("登录成功!");
							$.cookie("avatar", json.data.avatar, {expires: 7});
							console.log("cookie中的avatar=" + $.cookie("avatar"));
							location.href = "/index.html";
						} else {
							alert("登录失败!" + json.message);
						}
					}
				});
			});
		</script>

	</body>
</html>

修改密码

1 用户-修改密码-持久层

1.1 规划需要执行的SQL语句

用户修改密码时需要执行的SQL语句大致是:

UPDATE t_user SET password=?, modified_user=?, modified_time=? WHERE uid=?

在执行修改密码之前,还应检查用户数据是否存在、并检查用户数据是否被标记为“已删除”、并检查原密码是否正确,这些检查都可以通过查询用户数据来辅助完成:

SELECT * FROM t_user WHERE uid=? 

1.2 接口与抽象方法

在UserMapper接口添加updatePasswordByUid(Integer uid,String password,String modifiedUser,Date modifiedTime)抽象方法。

/**
 * 根据uid更新用户的密码
 * @param uid 用户的id
 * @param password 新密码
 * @param modifiedUser 最后修改执行人
 * @param modifiedTime 最后修改时间
 * @return 受影响的行数
 */
Integer updatePasswordByUid(
		@Param("uid") Integer uid, 
		@Param("password") String password, 
		@Param("modifiedUser") String modifiedUser, 
		@Param("modifiedTime") Date modifiedTime);

/**
 * 根据用户id查询用户数据
 * @param uid 用户id
 * @return 匹配的用户数据,如果没有匹配的用户数据,则返回null
 */
User findByUid(Integer uid);

1.3 配置SQL映射

1.在UserMapper.xml中配置updatePasswordByUid()、findByUid()抽象方法的映射。

<!-- 根据uid更新用户的密码:
	 Integer updatePasswordByUid(
		@Param("uid") Integer uid, 
		@Param("password") String password, 
		@Param("modifiedUser") String modifiedUser, 
		@Param("modifiedTime") Date modifiedTime) -->
<update id="updatePasswordByUid">
	UPDATE
		t_user 
	SET
		password = #{password},
		modified_user = #{modifiedUser},
		modified_time = #{modifiedTime} 
	WHERE
		uid = #{uid}
</update>

<!-- 根据用户id查询用户数据:User findByUid(Integer uid) -->
<select id="findByUid" resultMap="UserEntityMap">
	SELECT
		*
	FROM
		t_user
	WHERE
		uid = #{uid}
</select>

2 用户-修改密码-业务层

2.1 规划异常

1.用户在修改密码前,需要检查用户数据是否存在及是否被标记为“已删除”。如果检查不通过则应抛出UserNotFoundException异常。

2.用户修改密码时,可能会因为输入的原密码错误导致修改失败,则应抛出PasswordNotMatchException异常。

3.在执行修改密码时,如果返回的受影响行数与预期值不同,则应抛出UpdateException异常。

4.创建com.cy.store.service.ex.UpdateException异常类,继承自ServiceException类。

/** 更新数据的异常 */
public class UpdateException extends ServiceException {
	// Override Methods...
}

2.2 接口与抽象方法

在IUserService中添加changePassword(Integer uid, String username, String oldPassword, String newPassword)抽象方法。

/**
 * 修改密码
 * @param uid 当前登录的用户id
 * @param username 用户名
 * @param oldPassword 原密码
 * @param newPassword 新密码
 */
public void changePassword(Integer uid, String username, String oldPassword, String newPassword);

2.3 实现抽象方法

1.在UserServiceImpl类中实现changePassword()抽象方法。

2.changePassword()方法的具体代码。

@Override
public void changePassword(Integer uid, String username, String oldPassword, String newPassword) {
	// 调用userMapper的findByUid()方法,根据参数uid查询用户数据
	User result = userMapper.findByUid(uid);
	// 检查查询结果是否为null
	if (result == null) {
		// 是:抛出UserNotFoundException异常
		throw new UserNotFoundException("用户数据不存在");
	}
	
	// 检查查询结果中的isDelete是否为1
	if (result.getIsDelete().equals(1)) {
		// 是:抛出UserNotFoundException异常
		throw new UserNotFoundException("用户数据不存在");
	}
	
	// 从查询结果中取出盐值
	String salt = result.getSalt();
	// 将参数oldPassword结合盐值加密,得到oldMd5Password
	String oldMd5Password = getMd5Password(oldPassword, salt);
	// 判断查询结果中的password与oldMd5Password是否不一致
	if (!result.getPassword().contentEquals(oldMd5Password)) {
		// 是:抛出PasswordNotMatchException异常
		throw new PasswordNotMatchException("原密码错误");
	}
	
	// 将参数newPassword结合盐值加密,得到newMd5Password
	String newMd5Password = getMd5Password(newPassword, salt);
	// 创建当前时间对象
	Date now = new Date();
	// 调用userMapper的updatePasswordByUid()更新密码,并获取返回值
	Integer rows = userMapper.updatePasswordByUid(uid, newMd5Password, username, now);
	// 判断以上返回的受影响行数是否不为1
	if (rows != 1) {
		// 是:抛出UpdateException异常
		throw new UpdateException("更新用户数据时出现未知错误,请联系系统管理员");
	}
}

3 用户-修改密码-控制器

3.1 处理异常

在用户修改密码的业务中抛出了新的UpdateException异常,需要在BaseController类中进行处理。

@ExceptionHandler(ServiceException.class)
public JsonResult<Void> handleException(Throwable e) {
	JsonResult<Void> result = new JsonResult<Void>(e);
	if (e instanceof UsernameDuplicateException) {
		result.setState(4000);
	} else if (e instanceof UserNotFoundException) {
		result.setState(4001);
	} else if (e instanceof PasswordNotMatchException) {
		result.setState(4002);
	} else if (e instanceof InsertException) {
		result.setState(5000);
	} else if (e instanceof UpdateException) {
		result.setState(5001);
	}
	return result;
}

3.2 设计请求

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

请求路径:/users/change_password
请求参数:String oldPassword, String newPassword, HttpSession session
请求类型:POST
响应结果:JsonResult<Void>

3.3 处理请求

1.在UserController类中添加处理请求的changePassword(String oldPassword, String newPassword, HttpSession session)方法。

2.实现UserController控制器中的修改密码方法的代码。

@RequestMapping("change_password")
public JsonResult<Void> changePassword(String oldPassword, String newPassword, HttpSession session) {
	// 调用session.getAttribute("")获取uid和username
	Integer uid = getUidFromSession(session);
	String username = getUsernameFromSession(session);
	// 调用业务对象执行修改密码
	iUserService.changePassword(uid, username, oldPassword, newPassword);
	// 返回成功
	return new JsonResult<Void>(OK);
}

4 用户-修改密码-前端页面

1.在password.html页面中body标签内部的最后,添加script标签用于编写JavaScript程序。

<!DOCTYPE html >
<html xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta charset="UTF-8">
		<!--edge浏览器H5兼容设置-->
		<meta http-equiv="X-UA-Compatible" content="IE=edge">
		<!--360浏览器H5兼容设置-->
		<meta name="renderer" content="webkit" />
		<title>电脑商城</title>
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<!--导入核心文件-->
		<script src="../bootstrap3/js/holder.js"></script>
		<link href="../bootstrap3/css/bootstrap.css" rel="stylesheet" type="text/css">
		<script src="../bootstrap3/jquery-1.9.1.min.js"></script>
		<script src="../bootstrap3/js/bootstrap.js"></script>
		<!-- 字体图标 -->
		<link rel="stylesheet" href="../bootstrap3/font-awesome-4.7.0/css/font-awesome.css" />
		<link rel="stylesheet" type="text/css" href="../css/layout.css" />
		<link rel="stylesheet" type="text/css" href="../css/top.css" />
		<link rel="stylesheet" type="text/css" href="../css/footer.css" />
	</head>

	<body>
		<!--头部-->
		<header class="header">
			<!--电脑商城logo-->
			<div class="row">
				<div class="col-md-3">
					<a href="index.html">
						<img src="../images/index/stumalllogo.png" />
					</a>
				</div>
				<!--快捷选项-->
				<div class="col-md-9 top-item">
					<div th:replace="~{common/common::menu}"></div>
				</div>
			</div>
		</header>
		<!--导航 -->
		<!--分割导航和顶部-->
		<div class="row top-nav">
			<div class="col-md-6">
				<ul class="nav nav-pills">
					<li>
						<a href="#"></a>
					</li>
					<li class="active"><a href="index.html"><span class="fa fa-home"></span></a></li>
					<li><a href="#">秒杀</a></li>
					<li><a href="#">优惠券</a></li>
					<li><a href="#">电脑VIP</a></li>
					<li><a href="#">外卖</a></li>
					<li><a href="#">超市</a></li>
				</ul>
			</div>
			<div class="col-md-6">
				<form action="search.html" class="form-inline pull-right" role="form">
					<div class="form-group">
						<input type="text" class="form-control" id="search" name="search" placeholder="请输入商品名称进行搜索">
					</div>
					<button type="submit" class="btn btn-default btn-sm"><span class="fa fa-search"></span></button>
				</form>
			</div>
		</div>
		<!--头部结束-->
		<!--导航结束-->
		<div class="container">
			<div class="col-md-2">
				<!--左侧导航开始-->
				<div class="panel-group" id="accordion">
					<div class="panel panel-default">
						<div class="panel-heading">
							<!--主选项:我的订单-->
							<p class="panel-title">
								<a data-toggle="collapse" data-parent="#accordion" href="#collapseOne">我的订单</a>
							</p>
						</div>
						<div id="collapseOne" class="panel-collapse collapse">
							<div class="panel-body">
								<div><a href="orders.html">全部订单</a></div>
								<div><a href="orders.html">待付款</a></div>
								<div><a href="orders.html">待收货</a></div>
								<div><a href="orders.html">待评价</a></div>
								<div><a href="orders.html">退货退款</a></div>
							</div>
						</div>
					</div>
					<div class="panel panel-default">
						<div class="panel-heading">
							<!--主选项:资料修改-->
							<p class="panel-title">
								<a data-toggle="collapse" data-parent="#accordion" href="#collapseTwo">资料修改</a>
							</p>
						</div>
						<div id="collapseTwo" class="panel-collapse collapse in">
							<div class="panel-body">
								<div><a href="password.html"><b>修改密码</b></a></div>
								<div><a href="userdata.html">个人资料</a></div>
								<div><a href="upload.html">上传头像</a></div>
								<div><a href="addAddress.html">收货管理</a></div>
							</div>
						</div>
					</div>
				</div>
				<!--左侧导航结束-->
			</div>
			<div class="col-md-10">
				<div class="panel panel-default">
					<ul class="nav nav-tabs">
						<li class="active"><a href="password.html">修改密码</a></li>
						<li ><a href="userdata.html">个人资料</a></li>
						<li><a href="upload.html">上传头像</a></li>
						<li><a href="address.html">收货管理</a></li>
					</ul>
					<div class="panel-body">
						<!--修改密码表单开始-->
						<form id="form-change-password" class="form-horizontal" role="form">
							<div class="form-group">
								<label class="col-md-2 control-label">原密码:</label>
								<div class="col-md-8">
									<input name="oldPassword" type="text" class="form-control" placeholder="请输入原密码">
								</div>
							</div>
							<div class="form-group">
								<label class="col-md-2 control-label">新密码:</label>
								<div class="col-md-8">
									<input name="newPassword" type="text" class="form-control" placeholder="请输入新密码">
								</div>
							</div>
							<div class="form-group">
								<label class="col-md-2 control-label">确认密码:</label>
								<div class="col-md-8">
									<input type="text" class="form-control" placeholder="请再次输入新密码">
								</div>
							</div>
							<div class="form-group">
								<div class="col-sm-offset-2 col-sm-10">
									<input id="btn-change-password" type="button" class="btn btn-primary" value="修改" />
								</div>
							</div>
						</form>
					</div>
				</div>
			</div>
		</div>
		<!--页脚开始-->
		<div class="clearfix"></div>
		<div th:replace="common/common::footer"></div>
		<!--页脚结束-->
	<script>
		//password.html开始
		//修改密码的功能
		$("#btn-change-password").click(function() {
			$.ajax({
				url: "/users/change_password",
				type: "POST",
				data: $("#form-change-password").serialize(),
				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";
				}
			});
		});
		//password.html结束
	</script>
	</body>

</html>

个人资料

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

在UserMapper接口中添加updateInfoByUid(User user)方法

/**
 * 根据uid更新用户资料
 * @param user 封装了用户id和新个人资料的对象
 * @return 受影响的行数
 */
Integer updateInfoByUid(User user);

1.3 配置SQL映射

1.在UserMapper.xml中配置Integer updateInfoByUid(User user)抽象方法的映射。

<!-- 根据uid更新用户个人资料:Integer updateInfoByUid(User user) -->
<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>

2 用户-个人资料-业务层

2.1 规划异常

1.关于用户修改个人资料是由两个功能组成的。

  • 打开页面时显示当前登录的用户的信息;

  • 点击修改按钮时更新用户的信息。

2.关于打开页面时显示当前登录的用户的信息,可能会因为用户数据不存在、用户被标记为“已删除”而无法正确的显示页面,则抛出UserNotFoundException异常。

3.关于点击修改按钮时更新用户的信息,在执行修改资料之前仍需再次检查用户数据是否存在、用户是否被标记为“已删除”,则可能抛出UserNotFoundException异常。并且在执行修改资料过程中,还可能抛出UpdateException异常。

2.2 接口与抽象方法

在IUserService接口中添加两个抽象方法,分别对应以上两个功能。

/**
 * 获取当前登录的用户的信息
 * @param uid 当前登录的用户的id
 * @return 当前登录的用户的信息
 */
User getByUid(Integer uid);

/**
 * 修改用户资料
 * @param uid 当前登录的用户的id
 * @param username 当前登录的用户名
 * @param user 用户的新的数据
 */
void changeInfo(Integer uid, String username, User user);

2.3 实现抽象方法

1.在UserServiceImpl实现类中实现getByUid(Integer uid)和changeInfo(Integer uid, String username, User user)以上两个抽象方法。

2.getByUid(Integer uid)和changeInfo(Integer uid, String username, User user)方法的具体代码实现。

@Override
public User getByUid(Integer uid) {
	// 调用userMapper的findByUid()方法,根据参数uid查询用户数据
	User result = userMapper.findByUid(uid);
	// 判断查询结果是否为null
	if (result == null) {
		// 是:抛出UserNotFoundException异常
		throw new UserNotFoundException("用户数据不存在");
	}
 
	// 判断查询结果中的isDelete是否为1
	if (result.getIsDelete().equals(1)) {
		// 是:抛出UserNotFoundException异常
		throw new UserNotFoundException("用户数据不存在");
	}

	// 创建新的User对象
	User user = new User();
	// 将以上查询结果中的username/phone/email/gender封装到新User对象中
	user.setUsername(result.getUsername());
	user.setPhone(result.getPhone());
	user.setEmail(result.getEmail());
	user.setGender(result.getGender());
	
	// 返回新的User对象
	return user;
}

@Override
public void changeInfo(Integer uid, String username, User user) {
	// 调用userMapper的findByUid()方法,根据参数uid查询用户数据
	User result = userMapper.findByUid(uid);
	// 判断查询结果是否为null
	if (result == null) {
		// 是:抛出UserNotFoundException异常
		throw new UserNotFoundException("用户数据不存在");
	}

	// 判断查询结果中的isDelete是否为1
	if (result.getIsDelete().equals(1)) {
		// 是:抛出UserNotFoundException异常
		throw new UserNotFoundException("用户数据不存在");
	}

	// 向参数user中补全数据:uid
	user.setUid(uid);
	// 向参数user中补全数据:modifiedUser(username)
	user.setModifiedUser(username);
	// 向参数user中补全数据:modifiedTime(new Date())
	user.setModifiedTime(new Date());
	// 调用userMapper的updateInfoByUid(User user)方法执行修改,并获取返回值
	Integer rows = userMapper.updateInfoByUid(user);
	// 判断以上返回的受影响行数是否不为1
	if (rows != 1) {
		// 是:抛出UpdateException异常
		throw new UpdateException("更新用户数据时出现未知错误,请联系系统管理员");
	}
}

3 用户-个人资料-控制器

3.1 处理异常

说明:无需再次开发。

3.2 设计请求

1.设计用户提交显示当前登录的用户信息的请求,并设计响应的方式。

请求路径:/users/get_by_uid
请求参数:HttpSession session
请求类型:GET
响应结果:JsonResult<User>

 2.设计用户提交执行修改用户信息的请求,并设计响应的方式。

请求路径:/users/change_info
请求参数:User user, HttpSession session
请求类型:POST
响应结果:JsonResult<Void>

3.3 处理请求

1.处理获取用户信息请求

1.在UserController类中添加处理请求的getByUid()方法,并导入org.springframework.web.bind.annotation.GetMapping包。

2.getByUid(HttpSession session)方法中具体代码实现为。

@GetMapping("get_by_uid")
public JsonResult<User> getByUid(HttpSession session) {
    // 从HttpSession对象中获取uid
    Integer uid = getUidFromSession(session);
    // 调用业务对象执行获取数据
    User data = userService.getByUid(uid);
    // 响应成功和数据
    return new JsonResult<User>(OK, data);
}

 2.处理修改用户个人信息请求

1.在UserController类中添加处理请求的changeInfo(User user, HttpSession session)方法。

2.changeInfo(User user, HttpSession session)方法中具体代码实现为。

@RequestMapping("change_info")
public JsonResult<Void> changeInfo(User user, HttpSession session) {
	// 从HttpSession对象中获取uid和username
	Integer uid = getUidFromSession(session);
	String username = getUsernameFromSession(session);
	// 调用业务对象执行修改用户资料
	userService.changeInfo(uid, username, user);
	// 响应成功
	return new JsonResult<Void>(OK);
}

4 用户-个人资料-前端页面

1.在userdata.html页面中body标签内部的最后,添加script标签用于编写JavaScript程序。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

	<head>
		<meta charset="UTF-8">
		<!--edge浏览器H5兼容设置-->
		<meta http-equiv="X-UA-Compatible" content="IE=edge">
		<!--360浏览器H5兼容设置-->
		<meta name="renderer" content="webkit" />
		<title>电脑商城</title>
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<!--导入核心文件-->
		<script src="../bootstrap3/js/holder.js"></script>
		<link href="../bootstrap3/css/bootstrap.css" rel="stylesheet" type="text/css">
		<script src="../bootstrap3/jquery-1.9.1.min.js"></script>
		<script src="../bootstrap3/js/bootstrap.js"></script>
		<!-- 字体图标 -->
		<link rel="stylesheet" href="../bootstrap3/font-awesome-4.7.0/css/font-awesome.css" />
		<link rel="stylesheet" type="text/css" href="../css/layout.css" />
		<link rel="stylesheet" type="text/css" href="../css/top.css" />
		<link rel="stylesheet" type="text/css" href="../css/footer.css" />
	</head>

	<body>
		<!--头部-->
		<header class="header">
			<!--电脑商城logo-->
			<div class="row">
				<div class="col-md-3">
					<a href="index.html">
						<img src="../images/index/stumalllogo.png" />
					</a>
				</div>
				<!--快捷选项-->
				<div class="col-md-9 top-item">
					<div th:replace="~{common/common::menu}"></div>
				</div>
			</div>
		</header>
		<!--导航 -->
		<!--分割导航和顶部-->
		<div class="row top-nav">
			<div class="col-md-6">
				<ul class="nav nav-pills">
					<li>
						<a href="#"></a>
					</li>
					<li class="active"><a href="index.html"><span class="fa fa-home"></span></a></li>
					<li><a href="#">秒杀</a></li>
					<li><a href="#">优惠券</a></li>
					<li><a href="#">电脑VIP</a></li>
					<li><a href="#">外卖</a></li>
					<li><a href="#">超市</a></li>
				</ul>
			</div>
			<div class="col-md-6">
				<form action="search.html" class="form-inline pull-right" role="form">
					<div class="form-group">
						<input type="text" class="form-control" id="search" name="search" placeholder="请输入商品名称进行搜索">
					</div>
					<button type="submit" class="btn btn-default btn-sm"><span class="fa fa-search"></span></button>
				</form>
			</div>
		</div>
		<!--头部结束-->
		<!--导航结束-->
		<div class="container">
			<div class="col-md-2">
				<!--左侧导航开始-->
				<div class="panel-group" id="accordion">
					<div class="panel panel-default">
						<div class="panel-heading">
							<!--主选项:我的订单-->
							<p class="panel-title">
								<a data-toggle="collapse" data-parent="#accordion" href="#collapseOne">我的订单</a>
							</p>
						</div>
						<div id="collapseOne" class="panel-collapse collapse">
							<div class="panel-body">
								<div><a href="orders.html">全部订单</a></div>
								<div><a href="orders.html">待付款</a></div>
								<div><a href="orders.html">待收货</a></div>
								<div><a href="orders.html">待评价</a></div>
								<div><a href="orders.html">退货退款</a></div>
							</div>
						</div>
					</div>
					<div class="panel panel-default">
						<div class="panel-heading">
							<!--主选项:资料修改-->
							<p class="panel-title">
								<a data-toggle="collapse" data-parent="#accordion" href="#collapseTwo">资料修改</a>
							</p>
						</div>
						<div id="collapseTwo" class="panel-collapse collapse in">
							<div class="panel-body">
								<div><a href="password.html">修改密码</a></div>
								<div><a href="userdata.html"><b>个人资料</b></a></div>
								<div><a href="upload.html">上传头像</a></div>
								<div><a href="address.html">收货管理</a></div>
							</div>
						</div>
					</div>
				</div>
				<!--左侧导航结束-->
			</div>
			<div class="col-md-10	">
				<div class="panel panel-default">
					<ul class="nav nav-tabs">
						<li><a href="password.html">修改密码</a></li>
						<li class="active"><a href="userdata.html">个人资料</a></li>
						<li><a href="upload.html">上传头像</a></li>
						<li><a href="address.html">收货管理</a></li>
					</ul>
					<div class="panel-body">
						<!--修改资料表单开始-->
						<form id="form-change-info" class="form-horizontal" role="form">
							<div class="form-group">
								<label class="col-md-2 control-label">用户名:</label>
								<div class="col-md-8">
									<input id="username" type="text" class="form-control"  readonly="readonly">
								</div>
							</div>
							<div class="form-group">
								<label class="col-md-2 control-label">电话号码:</label>
								<div class="col-md-8">
									<input id="phone" name="phone" type="text" class="form-control" placeholder="请输入电话号码" >
								</div>
							</div>
							<div class="form-group">
								<label class="col-md-2 control-label">电子邮箱:</label>
								<div class="col-md-8">
									<input id="email" name="email" type="text" class="form-control" placeholder="请输入电子邮箱">
								</div>
							</div>
							<div class="form-group">
								<label class="col-md-2 control-label">性别:</label>
								<div class="col-md-8">
									<label class="radio-inline">
										<input id="gender-male" type="radio" name="gender" value="1">男
									</label>
									<label class="radio-inline">
										<input id="gender-female" type="radio" name="gender" value="0">女
									</label>
								</div>
							</div>
							<div class="form-group">
								<div class="col-sm-offset-2 col-sm-10">
									<input id="btn-change-info" type="button" class="btn btn-primary" value="修改" />
								</div>
							</div>
						</form>
					</div>
				</div>
			</div>
		</div>
		<!--页脚开始-->
		<div class="clearfix"></div>
		<div th:replace="common/common::footer"></div>
		<!--页脚结束-->
	<script>
		//userdata.html开始
		$(document).ready(function() {
			$.ajax({
				url: "/users/get_by_uid",
				type: "GET",
				dataType: "json",
				success: function(json) {
					if (json.state == 200) {
						console.log("username=" + json.data.username);
						console.log("phone=" + json.data.phone);
						console.log("email=" + json.data.email);
						console.log("gender=" + json.data.gender);

						$("#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");
						radio.prop("checked", "checked");
					} else {
						alert("获取用户信息失败!" + json.message);
					}
				}
			});
		});

		$("#btn-change-info").click(function() {
			$.ajax({
				url: "/users/change_info",
				type: "POST",
				data: $("#form-change-info").serialize(),
				dataType: "json",
				success: function(json) {
					if (json.state == 200) {
						alert("修改成功!");
						location.href = "userdata.html";
					} else {
						alert("修改失败!" + json.message);
					}
				},
				error: function(xhr) {
					alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
					location.href = "login.html";
				}
			});
		});
		//userdata.html结束
	</script>
	</body>

</html>

上传头像

1 用户-上传头像-持久层

1.1 规划需要执行的SQL语句

上传文件的操作其实是:先将用户上传的文件保存到服务器端的某个位置,然后将保存文件的路径记录在数据库中。当后续需要使用该文件时,从数据库中读出文件的路径,即可实现在线访问该文件。

在持久层处理数据库中的数据时,只需要关心如何记录头像文件的路径,并不需要考虑上传时保存文件的过程。所以,需要执行的SQL语句大致是:

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

1.2 接口与抽象方法

在UserMapper接口中添加updateAvatarByUid()抽象方法。

/**
 * 根据uid更新用户的头像
 * @param uid 用户的id
 * @param avatar 新头像的路径
 * @param modifiedUser 修改执行人
 * @param modifiedTime 修改时间
 * @return 受影响的行数
 */
Integer updateAvatarByUid(
		@Param("uid") Integer uid,
		@Param("avatar") String avatar,
		@Param("modifiedUser") String modifiedUser,
		@Param("modifiedTime") Date modifiedTime);

1.3 配置SQL映射

1.在UserMapper.xml中配置updateAvatarByUid()抽象方法的映射。

<!-- 根据uid更新用户的头像
	 Integer updateAvatarByUid(
		@Param("uid") Integer uid,
		@Param("avatar") String avatar,
		@Param("modifiedUser") String modifiedUser,
		@Param("modifiedTime") Date modifiedTime) -->
<update id="updateAvatarByUid">
	UPDATE
		t_user
	SET
		avatar = #{avatar},
		modified_user = #{modifiedUser},
		modified_time = #{modifiedTime}
	WHERE
		uid = #{uid}
</update>

2 用户-上传头像-业务层

2.1 规划异常

在修改头像值前先检查用户数据状态,可能抛UserNotFoundException异常;由于最终执行的是修改操作还可能抛UpdateException异常。

2.2 接口与抽象方法

在IUserService中添加changeAvatar(Integer uid, String username, String avatar)抽象方法。

/**
 * 修改用户头像
 * @param uid 当前登录的用户的id
 * @param username 当前登录的用户名
 * @param avatar 用户的新头像的路径
 */
void changeAvatar(Integer uid, String username, String avatar);

2.3 实现抽象方法

1.在UserServiceImpl类中实现changeAvatar(Integer uid, String username, String avatar)方法。

2.changeAvatar(Integer uid, String username, String avatar)方法中代码的具体实现为。

@Override
public void changeAvatar(Integer uid, String username, String avatar) {
	// 调用userMapper的findByUid()方法,根据参数uid查询用户数据
	User result = userMapper.findByUid(uid);
	// 检查查询结果是否为null
	if (result == null) {
		// 是:抛出UserNotFoundException
		throw new UserNotFoundException("用户数据不存在");
	}
	
	// 检查查询结果中的isDelete是否为1
	if (result.getIsDelete().equals(1)) {
		// 是:抛出UserNotFoundException
		throw new UserNotFoundException("用户数据不存在");
	}
	
	// 创建当前时间对象
	Date now = new Date();
	// 调用userMapper的updateAvatarByUid()方法执行更新,并获取返回值
	Integer rows = userMapper.updateAvatarByUid(uid, avatar, username, now);
	// 判断以上返回的受影响行数是否不为1
	if (rows != 1) {
		// 是:抛出UpdateException
		throw new UpdateException("更新用户数据时出现未知错误,请联系系统管理员");
	}
}

3 用户-上传头像-控制器

3.1 处理异常

1.在处理上传文件的过程中,用户可能会选择错误的文件上传,此时就应该抛出对应的异常并进行处理。所以需要创建文件上传相关异常的基类,即在com.cy.store.controller.ex包下创建FileUploadException类,并继承自RuntimeException类。

package com.cy.store.service.ex;

/** 文件上传相关异常的基类 */
public class FileUploadException extends RuntimeException {
    public FileUploadException() {
        super();
    }

    public FileUploadException(String message) {
        super(message);
    }

    public FileUploadException(String message, Throwable cause) {
        super(message, cause);
    }

    public FileUploadException(Throwable cause) {
        super(cause);
    }

    protected FileUploadException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

2.在处理上传的文件过程中,经分析可能会产生以下异常。这些异常类都需要继承自FileUploadException类。

// 上传的文件为空
cn.tedu.store.controller.ex.FileEmptyException
// 上传的文件大小超出了限制值
cn.tedu.store.controller.ex.FileSizeException
// 上传的文件类型超出了限制
cn.tedu.store.controller.ex.FileTypeException
// 上传的文件状态异常
cn.tedu.store.controller.ex.FileStateException
// 上传文件时读写异常
cn.tedu.store.controller.ex.FileUploadIOException

3.创建FileEmptyException异常类,并继承FileUploadException类。

package com.cy.store.service.ex;

/** 上传的文件为空的异常,例如没有选择上传的文件就提交了表单,或选择的文件是0字节的空文件 */
public class FileEmptyException extends FileUploadException {
    // Override Methods...
}

 4.创建FileSizeException异常类,并继承FileUploadException类。

package com.cy.store.service.ex;

/** 上传的文件的大小超出了限制值 */
public class FileSizeException extends FileUploadException {
    // Override Methods...
}

5.创建FileTypeException异常类,并继承FileUploadException类。

package com.cy.store.service.ex;

/** 上传的文件类型超出了限制 */
public class FileTypeException extends FileUploadException {
    // Override Methods...
}

6.创建FileStateException异常类,并继承FileUploadException类。

package com.cy.store.service.ex;

/** 上传的文件状态异常 */
public class FileStateException extends FileUploadException {
    // Override Methods...
}

7.创建FileUploadIOException异常类,并继承FileUploadException类。

package com.cy.store.service.ex;

/** 上传文件时读写异常 */
public class FileUploadIOException extends FileUploadException {
    // Override Methods...
}

8.然后在BaseController的handleException()的@ExceptionHandler注解中添加FileUploadException.class异常的处理;最后在方法中处理这些异常。

@ExceptionHandler({ServiceException.class, FileUploadException.class})
public JsonResult<Void> handleException(Throwable e) {
	JsonResult<Void> result = new JsonResult<Void>(e);
	if (e instanceof UsernameDuplicateException) {
		result.setState(4000);
	} else if (e instanceof UserNotFoundException) {
		result.setState(4001);
	} else if (e instanceof PasswordNotMatchException) {
		result.setState(4002);
	} else if (e instanceof InsertException) {
		result.setState(5000);
	} else if (e instanceof UpdateException) {
		result.setState(5001);
	} else if (e instanceof FileEmptyException) {
		result.setState(6000);
	} 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);
	}
	
	return result;
}

3.2 设计请求

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

请求路径:/users/change_avatar
请求参数:MultipartFile file, HttpSession session
请求类型:POST
响应结果:JsonResult<String>

3.3 处理请求

1.在UserController类中添加处理请求的changeAvatar(@RequestParam("file") MultipartFile file, HttpSession session)方法。

2.changeAvatar(@RequestParam("file") MultipartFile file, HttpSession session)方法中具体代码实现为。

/** 头像文件大小的上限值(10MB) */
public static final int AVATAR_MAX_SIZE = 10 * 1024 * 1024;
/** 允许上传的头像的文件类型 */
public static final List<String> AVATAR_TYPES = new ArrayList<String>();

/** 初始化允许上传的头像的文件类型 */
static {
	AVATAR_TYPES.add("image/jpeg");
	AVATAR_TYPES.add("image/png");
	AVATAR_TYPES.add("image/bmp");
	AVATAR_TYPES.add("image/gif");
}

@PostMapping("change_avatar")
public JsonResult<String> changeAvatar(@RequestParam("file") MultipartFile file, HttpSession session) {
	// 判断上传的文件是否为空
	if (file.isEmpty()) {
		// 是:抛出异常
		throw new FileEmptyException("上传的头像文件不允许为空");
	}
	
	// 判断上传的文件大小是否超出限制值
	if (file.getSize() > AVATAR_MAX_SIZE) { // getSize():返回文件的大小,以字节为单位
		// 是:抛出异常
		throw new FileSizeException("不允许上传超过" + (AVATAR_MAX_SIZE / 1024) + "KB的头像文件");
	}
	
	// 判断上传的文件类型是否超出限制
	String contentType = file.getContentType();
	// public boolean list.contains(Object o):当前列表若包含某元素,返回结果为true;若不包含该元素,返回结果为false。
	if (!AVATAR_TYPES.contains(contentType)) {
		// 是:抛出异常
		throw new FileTypeException("不支持使用该类型的文件作为头像,允许的文件类型:\n" + AVATAR_TYPES);
	}
	
	// 获取当前项目的绝对磁盘路径
	String parent = session.getServletContext().getRealPath("upload");
	// 保存头像文件的文件夹
	File dir = new File(parent);
	if (!dir.exists()) {
		dir.mkdirs();
	}
	
	// 保存的头像文件的文件名
	String suffix = "";
	String originalFilename = file.getOriginalFilename();
	int beginIndex = originalFilename.lastIndexOf(".");
	if (beginIndex > 0) {
		suffix = originalFilename.substring(beginIndex);
	}
	String filename = UUID.randomUUID().toString() + suffix;
	
	// 创建文件对象,表示保存的头像文件
	File dest = new File(dir, filename);
	// 执行保存头像文件
	try {
		file.transferTo(dest);
	} catch (IllegalStateException e) {
		// 抛出异常
		throw new FileStateException("文件状态异常,可能文件已被移动或删除");
	} catch (IOException e) {
		// 抛出异常
		throw new FileUploadIOException("上传文件时读写错误,请稍后重尝试");
	}
	
	// 头像路径
	String avatar = "/upload/" + filename;
	// 从Session中获取uid和username
	Integer uid = getUidFromSession(session);
	String username = getUsernameFromSession(session);
	// 将头像写入到数据库中
	userService.changeAvatar(uid, username, avatar);
	
	// 返回成功头像路径
	return new JsonResult<String>(OK, avatar);
}

4 用户-上传头像-前端页面

1.然后在upload.html页面中配置用户上传头像的form表单。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta charset="UTF-8">
		<!--edge浏览器H5兼容设置-->
		<meta http-equiv="X-UA-Compatible" content="IE=edge">
		<!--360浏览器H5兼容设置-->
		<meta name="renderer" content="webkit" />
		<title>电脑商城</title>
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<!--导入核心文件-->
		<script src="../bootstrap3/js/holder.js"></script>
		<link href="../bootstrap3/css/bootstrap.css" rel="stylesheet" type="text/css">
		<script src="../bootstrap3/jquery-1.9.1.min.js"></script>
		<script src="../bootstrap3/js/bootstrap.js"></script>
		<script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8"></script>
		<!-- 字体图标 -->
		<link rel="stylesheet" href="../bootstrap3/font-awesome-4.7.0/css/font-awesome.css" />
		<link rel="stylesheet" type="text/css" href="../css/layout.css" />
		<link rel="stylesheet" type="text/css" href="../css/top.css" />
		<link rel="stylesheet" type="text/css" href="../css/footer.css" />
	</head>

	<body>
		<!--头部-->
		<header class="header">
			<!--电脑商城logo-->
			<div class="row">
				<div class="col-md-3">
					<a href="index.html">
						<img src="../images/index/stumalllogo.png" />
					</a>
				</div>
				<!--快捷选项-->
				<div class="col-md-9 top-item">
					<div th:replace="~{common/common::menu}"></div>
				</div>
			</div>
		</header>
		<!--导航 -->
		<!--分割导航和顶部-->
		<div class="row top-nav">
			<div class="col-md-6">
				<ul class="nav nav-pills">
					<li>
						<a href="#"></a>
					</li>
					<li class="active"><a href="index.html"><span class="fa fa-home"></span></a></li>
					<li><a href="#">秒杀</a></li>
					<li><a href="#">优惠券</a></li>
					<li><a href="#">电脑VIP</a></li>
					<li><a href="#">外卖</a></li>
					<li><a href="#">超市</a></li>
				</ul>
			</div>
			<div class="col-md-6">
				<form action="search.html" class="form-inline pull-right" role="form">
					<div class="form-group">
						<input type="text" class="form-control" id="search" name="search" placeholder="请输入商品名称进行搜索">
					</div>
					<button type="submit" class="btn btn-default btn-sm"><span class="fa fa-search"></span></button>
				</form>
			</div>
		</div>
		<!--头部结束-->
		<!--导航结束-->
		<div class="container">
			<div class="col-md-2">
				<!--左侧导航开始-->
				<div class="panel-group" id="accordion">
					<div class="panel panel-default">
						<div class="panel-heading">
							<!--主选项:我的订单-->
							<p class="panel-title">
								<a data-toggle="collapse" data-parent="#accordion" href="#collapseOne">我的订单</a>
							</p>
						</div>
						<div id="collapseOne" class="panel-collapse collapse">
							<div class="panel-body">
								<div><a href="orders.html">全部订单</a></div>
								<div><a href="orders.html">待付款</a></div>
								<div><a href="orders.html">待收货</a></div>
								<div><a href="orders.html">待评价</a></div>
								<div><a href="orders.html">退货退款</a></div>
							</div>
						</div>
					</div>
					<div class="panel panel-default">
						<div class="panel-heading">
							<!--主选项:资料修改-->
							<p class="panel-title">
								<a data-toggle="collapse" data-parent="#accordion" href="#collapseTwo">资料修改</a>
							</p>
						</div>
						<div id="collapseTwo" class="panel-collapse collapse in">
							<div class="panel-body">
								<div><a href="password.html">修改密码</a></div>
								<div><a href="userdata.html">个人资料</a></div>
								<div><a href="upload.html"><b>上传头像</b></a></div>
								<div><a href="address.html">收货管理</a></div>
							</div>
						</div>
					</div>
				</div>
				<!--左侧导航结束-->
			</div>
			<div class="col-md-10	">
				<div class="panel panel-default">
					<ul class="nav nav-tabs">
						<li><a href="password.html">修改密码</a></li>
						<li ><a href="userdata.html">个人资料</a></li>
						<li class="active"><a href="upload.html">上传头像</a></li>
						<li><a href="address.html">收货管理</a></li>
					</ul>
					<div class="panel-body">
						<!--上传头像表单开始-->
						<form id="form-change-avatar" class="form-horizontal" role="form" action="/users/change_avatar" method="post" enctype="multipart/form-data">
							<div class="form-group">
								<label class="col-md-2 control-label">选择头像:</label>
								<div class="col-md-5">
									<img id="img-avatar" src="../images/index/user.jpg" class="img-responsive" />
								</div>
								<div class="clearfix"></div>
								<div class="col-md-offset-2 col-md-4">
									<input type="file" name="file">
								</div>
							</div>
							<div class="form-group">
								<div class="col-sm-offset-2 col-sm-10">
									<input type="button" class="btn btn-primary" value="上传" id="btn-change-avatar"/>
								</div>
							</div>
						</form>
					</div>
				</div>
			</div>
		</div>
		<!--页脚开始-->
		<div class="clearfix"></div>
		<div th:replace="common/common::footer"></div>
		<!--页脚结束-->
	<script>
		$(document).ready(function () {
			console.log("cookie中的avatar=" + $.cookie("avatar"));
			$("#img-avatar").attr("src", $.cookie("avatar"));
		});
		$("#btn-change-avatar").click(function() {
			$.ajax({
				url: "/users/change_avatar",
				type: "POST",
				data: new FormData($("#form-change-avatar")[0]),
				dataType: "JSON",
				processData: false, // processData处理数据
				contentType: false, // contentType发送数据的格式
				success: function(json) {
					if (json.state == 200) {
						$("#img-avatar").attr("src", json.data);
						$.cookie("avatar", json.data, {expires: 7});
					} else {
						alert("修改失败!" + json.message);
					}
				},
				error: function(xhr) {
					alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
					location.href = "login.html";
				}
			});
		});
	</script>
	</body>

</html>

5 用户-上传头像-设置上传文件大小

1.SpringBoot中默认MultipartResolver的最大文件大小值为1M。如果上传的文件的大小超过1M,会抛FileSizeLimitExceededException异常。

2.如果需要调整上传的限制值,直接在启动类中添加getMultipartConfigElement()方法,并且在启动类之前添加@Configuration注解。

package com.cy.store;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;
import javax.servlet.MultipartConfigElement;

@Configuration
@SpringBootApplication
@MapperScan("com.cy.store.mapper")
public class StoreApplication {

    public static void main(String[] args) {
        SpringApplication.run(StoreApplication.class, args);
    }

    @Bean
    public MultipartConfigElement getMultipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        // DataSize dataSize = DataSize.ofMegabytes(10);
        // 设置文件最大10M,DataUnit提供5中类型B,KB,MB,GB,TB
        factory.setMaxFileSize(DataSize.of(10, DataUnit.MEGABYTES));
        factory.setMaxRequestSize(DataSize.of(10, DataUnit.MEGABYTES));
        // 设置总上传数据总大小10M
        return factory.createMultipartConfig();
    }
}

3.除了以上编写方法配置上传的上限值以外,还可以通过在application.properties或application.yml中添加配置来实现。

(1) 低版本:1.X

spring.http.multipart.max-file-size=10MB
spring.http.multipart.max-request-size=10MB

(2) 高版本:2.X

#方式1
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
#方式2
spring.servlet.multipart.maxFileSize=10MB
spring.servlet.multipart.maxRequestSize=10MB 

6 用户-上传头像-前端页面BUG解决

6.1 上传后显示头像

1.头像上传成功后,显示上传的头像。在upload.html页面中,是使用img标签来显示头像图片的。首先确定img标签是否添加有id="img-avatar"属性,便于后续访问该标签;而img标签是通过src属性来决定显示哪张图片的,所以修改src该属性的值即可设置需要显示的图片。修改表单添加id="form-change-avatar"属性。修改input标签,添加id="btn-change-avatar"和type="button"属性。

2.在upload.html页面中body标签内部的最后,添加script标签用于编写JavaScript程序。

  • processData:处理数据。默认情况下,processData的值是true,其代表以对象的形式上传的数据都会被转换为字符串的形式上传。而当上传文件的时候,则不需要把其转换为字符串,因此要改成false。

  • contentType:发送数据的格式。其代表的是前端发送数据的格式,默认值application/x-www-form-urlencoded。代表的是ajax的 data是以字符串的形式传递,使用这种传数据的格式,无法传输复杂的数据,比如多维数组、文件等。把contentType设置为false就会改掉之前默认的数据格式,在上传文件时就不会报错。

<script type="text/javascript">
    $("#btn-change-avatar").click(function() {
        $.ajax({
            url: "/users/change_avatar",
            type: "POST",
            data: new FormData($("#form-change-avatar")[0]),
            dataType: "JSON",
            processData: false, // processData处理数据
            contentType: false, // contentType发送数据的格式
            success: function(json) {
                if (json.state == 200) {
                    $("#img-avatar").attr("src", json.data);
                } else {
                    alert("修改失败!" + json.message);
                }
            },
            error: function(xhr) {
                alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
                location.href = "login.html";
            }
        });
	});
</script>

6.2 登录后显示头像

1.首先检查登录成功后是否返回了头像的数据。访问http://localhost:8080/users/login?username=admin&password=321测试。

2.用户名、用户Id、用户头像等数据,属于常用数据,在客户端的许多页面都可能需要使用,如果每次都向服务器提交请求获取这些数据,是非常不合适的。可以在用户登录成功后,将这些数据存储在客户端本地,后续在客户端中需要显示这些数据时,直接从本地获取即可,无需再向服务器请求这些数据。在客户端本地存取数据时,可以使用Cookie技术。

3.设计思路:当用户登录成功后,将服务器返回的头像路径存储到本地的Cookie中,在打开“上传头像”页面时,从本地的Cookie中读取头像路径并显示即可。在登录login.html页面中,当登录成功后,将用户头像路径保存到Cookie中。

$("#btn-login").click(function() {
    $.ajax({
        url: "/users/login",
        type: "POST",
        data: $("#form-login").serialize(),
        dataType: "json",
        success: function(json) {
            if (json.state == 200) {
                alert("登录成功!");
                $.cookie("avatar", json.data.avatar, {expires: 7});
                console.log("cookie中的avatar=" + $.cookie("avatar"));
                location.href = "index.html";
            } else {
                alert("登录失败!" + json.message);
            }
        }
    });
});

语法:$.cookie(名称,值,[option])。[option]参数说明:

expires:有限日期,可以是一个整数或一个日期(单位天)。如果不设置这个值,默认情况下浏览器关闭之后此Cookie就会失效。

path:表示Cookie值保存的路径,默认与创建页路径一致。

domin:表示Cookie域名属性,默认与创建页域名一样。要注意跨域的概念,如果要主域名二级域名有效则要设置“.xxx.com”。

secrue:布尔类型的值,表示传输Cookie值时,是否需要一个安全协议。

4.在upload.html页面中,默认并没有引用jqueyr.cookie.js文件,因此无法识别$.cookie()函数;所以需要在upload.html页面head标签内添加jqueyr.cookie.js文件。

<script src="../bootstrap3/js/jquery.cookie.js" type="text/javascript" charset="utf-8"></script>

 5.在打开页面时自动读取显示用户图像。获取Cookie中头像的路径,然后将获取到的头像路径设置给img标签的src属性以显示头像。在upload.html页面中的script标签的内部添加自动读取用户图像的jquery代码。

$(document).ready(function () {
    console.log("cookie中的avatar=" + $.cookie("avatar"));
    $("#img-avatar").attr("src", $.cookie("avatar"));
});

6.3 显示最新头像

以上代码表示“每次打开页面时,读取Cookie中的头像并显示”,如果此时重新上传用户头像,而Cookie中所保存的头像还是之前上传的头像路径值,无法显示最新的用户头像。所以当用户重新上传头像后,还应把新头像的路径更新到Cookie中。

1.在upload.html页面中,用户头像修改成功后,并将新的用户头像路径保存到Cookie中。

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

新增收货地址

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

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

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

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

创建com.cy.store.entity.Address新增收获地址的实体类,继承自BaseEntity类,在类中声明与数据表中对应的属性,添加Getters and Setters方法,基于唯一标识aid生成hashCode()和equals()方法

package com.cy.store.entity;

/** 收货地址数据的实体类 */
public class Address extends BaseEntity implements Serializable {
    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;

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

3 新增收货地址-持久层

3.1 各功能的开发顺序

关于收货地址数据的管理,涉及的功能有:增加,删除,修改,设为默认,显示列表。这些功能的开发顺序为:增加-显示列表-设为默认-删除-修改。

3.2 规划需要执行的SQL语句

增加收货地址的本质是插入新的收货地址数据,需要执行的SQL语句大致是:

INSERT INTO t_address (除了aid以外的字段列表) VALUES (匹配的值列表)

 后续在处理业务时,还需要确定“即将增加的收货地址是不是默认收货地址”;可以设定规则“用户的第1条收货地址是默认的,以后添加的每一条都不是默认的”;要应用该规则,就必须知道“即将增加的收货地址是不是第1条”,可以“根据用户id统计收货地址的数量”,如果统计结果为0,则即将增加的就是该用户的第1条收货地址,如果统计结果不是0,则该用户已经有若干条收货地址了,即将增加的就一定不是第1条。关于统计的SQL语句大致是:

SELECT count(*) FROM t_address WHERE uid=?

3.3 接口与抽象方法

创建com.cy.store.mapper.AddressMapper接口,并在接口中添加抽象方法。

package com.cy.store.mapper;
import com.cy.store.entity.Address;

/** 处理收货地址数据的持久层接口 */
public interface AddressMapper {
    /**
     * 插入收货地址数据
     * @param address 收货地址数据
     * @return 受影响的行数
     */
    Integer insert(Address address);

    /**
     * 统计某用户的收货地址数据的数量
     * @param uid 用户的id
     * @return 该用户的收货地址数据的数量
     */
    Integer countByUid(Integer uid);
}

3.4 配置SQL映射

1.在src/main/resources/mapper文件夹下复制粘贴得到AddressMapper.xml映射文件,修改根节点mapper的namespace属性的值为com.cy.store.mapper.AddressMapper,并在根节点中配置pojo类属性与数据库中表的字段映射。

<?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 column="created_time" property="createdTime"/>
        <result column="modified_user" property="modifiedUser"/>
        <result column="modified_time" property="modifiedTime"/>
    </resultMap>
</mapper>

2.在AddressMapper.xml映射文件的根节点中配置以上两个抽象方法的映射。

<!-- 插入收货地址数据:Integer insert(Address address) -->
<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, 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}
    )
</insert>

<!-- 统计某用户的收货地址数据的数量:Integer countByUid(Integer uid) -->
<select id="countByUid" resultType="java.lang.Integer">
    SELECT
    	COUNT(*)
    FROM
   		t_address
    WHERE
    	uid=#{uid}
</select>

4 增收货地址-业务层

1.无论用户将要增加的收货地址是不是默认收货地址,都需正常增加。即通过countByUid()方法统计的结果不管是不是0,都不能代表是错误的操作。

2.在执行插入收货地址数据之前,需判断countByUid()方法返回值是否超出上限值,如果超出上限值则抛AddressCountLimitException异常。

3.在执行插入数据时,还可能抛出InsertException异常,此异常无需再次创建。

4.创建com.cy.store.service.ex.AddressCountLimitException类后,需继承自ServiceException类。

package com.cy.store.service.ex;

/** 收货地址数量达到上限的异常 */
public class AddressCountLimitException extends ServiceException {
    // Override Methods...
}

4.2 接口与抽象方法

创建com.cy.store.service.IAddressService业务层接口,并添加抽象方法。

package com.cy.store.service;
import com.cy.store.entity.Address;

/** 处理收货地址数据的业务层接口 */
public interface IAddressService {
    /**
     * 创建新的收货地址
     * @param uid 当前登录的用户的id
     * @param username 当前登录的用户名
     * @param address 用户提交的收货地址数据
     */
    void addNewAddress(Integer uid, String username, Address address);
}

4.3 实现抽象方法

1.创建com.cy.store.service.impl.AddressServiceImpl业务层实现类,在类定义之前添加@Service注解,并实现IAddressService接口,最后在类中添加持久层对象并使用@Autowired注解修饰。

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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AddressServiceImpl implements IAddressService {
    @Autowired
    private AddressMapper addressMapper;

    @Override
    public void addNewAddress(Integer uid, String username, Address address) {
		// TODO
    }
}

2.分析重写的addNewAddress(Integer uid, String username, Address address)抽象方法中的业务逻辑。

3.addNewAddress(Integer uid, String username, Address address)方法的具体代码实现。

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 int maxCount;

    @Override
    public void addNewAddress(Integer uid, String username, Address address) {
        // 根据参数uid调用addressMapper的countByUid(Integer uid)方法,统计当前用户的收货地址数据的数量
        Integer count = addressMapper.countByUid(uid);
        // 判断数量是否达到上限值
        if (count > maxCount) {
            // 是:抛出AddressCountLimitException
            throw new AddressCountLimitException("收货地址数量已经达到上限(" + maxCount + ")!");
        }

        // 补全数据:将参数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);

        // 调用addressMapper的insert(Address address)方法插入收货地址数据,并获取返回的受影响行数
        Integer rows = addressMapper.insert(address);
        // 判断受影响行数是否不为1
        if (rows != 1) {
            // 是:抛出InsertException
            throw new InsertException("插入收货地址数据时出现未知错误,请联系系统管理员!");
        }
    }
}

4.在application.properties文件中添加收货地址数据上限值的配置。

user.address.max-count=20

5 新增收货地址-控制器

5.1 处理异常

在控制器层新增收货地址时,如果收货地址已经达到上限值,则抛出AddressCountLimitException异常,并在BaseController类中添加处理AddressCountLimitException的异常。

// ...
else if (e instanceof AddressCountLimitException) {
	result.setState(4003);
}
// ...

5.2 设计请求

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

请求路径:/addresses/add_new_address
请求参数:Address address, HttpSession session
请求类型:POST
响应结果:JsonResult<Void>

5.3 处理请求

1.创建com.cy.store.controller.AddressController控制器类继承自BaseController类,在类的声明添加@RequestMapping("addresses")和@RestController注解,在类中声明业务层对象并添加Autowired注解修饰。

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

@RestController
@RequestMapping("addresses")
public class AddressController extends BaseController {
    @Autowired
    private IAddressService addressService;    
}

 2.然后在AddressController类中添加处理请求的addNewAddress(Address address, HttpSession session)方法。

@RequestMapping("add_new_address")
public JsonResult<Void> addNewAddress(Address address, HttpSession session) {
    // 从Session中获取uid和username
    Integer uid = getUidFromSession(session);
    String username = getUsernameFromSession(session);

    // 调用业务对象的方法执行业务
    addressService.addNewAddress(uid, username, address);
    // 响应成功
    return new JsonResult<Void>(OK);
}

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

1.在addAddress.html页面中body标签内部的最后,添加script标签用于编写JavaScript程序。

<script type="text/javascript">
    $("#btn-add-new-address").click(function() {
        $.ajax({
            url: "/addresses/add_new_address",
            type: "POST",
            data: $("#form-add-new-address").serialize(),
            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";
            }
        });
	});
</script>

获取省/市/区的列表

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

1.创建省/市/区数据的com.cy.store.entity实体类,在类中声明与数据表中对应的属性,添加Getters and Setters方法,基于唯一标识id生成equals()方法及hashCode()和toString()方法。

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

/** 省/市/区数据的实体类 */
public class District implements Serializable {
    private Integer id;
    private String parent;
    private String code;
    private String name;

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

2 获取省/市/区的列表-持久层

2.1 规划需要执行的SQL语句

获取全国所有省/某省所有市/某市所有区的查询SQL语句大致是:

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

2.2 接口与抽象方法

创建com.cy.store.mapper.DistrictMapper接口,添加抽象方法。

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

/** 处理省/市/区数据的持久层接口 */
public interface DistrictMapper {
    /**
     * 获取全国所有省/某省所有市/某市所有区
     * @param parent 父级代号,当获取某市所有区时,使用市的代号;当获取省所有市时,使用省的代号;当获取全国所有省时,使用"86"作为父级代号
     * @return 全国所有省/某省所有市/某市所有区的列表
     */
    List<District> findByParent(String parent);
}

2.3 配置SQL映射

1.在src/main/resources/mapper中复制得到DistrictMapper.xml,修改根节点的namespace属性的值为以上接口文件,并配置以上抽象方法的映射。

<?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.DistrictMapper">
    <!-- 获取全国所有省/某省所有市/某市所有区:List<District> findByParent(String parent) -->
    <select id="findByParent" resultType="com.cy.store.entity.District">
        SELECT
            *
        FROM
            t_dict_district
        WHERE
            parent=#{parent}
        ORDER BY
            code ASC
    </select>
</mapper>

3 获取省/市/区的列表-业务层

3.1 规划异常

说明:无异常。

3.2 接口与抽象方法

创建com.cy.store.service.IDistrictService接口,并添加抽象方法

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

/** 处理省/市/区数据的业务层接口 */
public interface IDistrictService {
    /**
     * 获取全国所有省/某省所有市/某市所有区
     * @param parent 父级代号,当获取某市所有区时,使用市的代号;当获取某省所有市时,使用省的代号;当获取全国所有省时,使用"86"作为父级代号
     * @return 全国所有省/某省所有市/某市所有区的列表
     */
    List<District> getByParent(String parent);
}

3.3 实现抽象方法

1.创建com.cy.store.service.impl.DistrictServiceImpl类,实现IDistrictService接口,在类之前添加@Service注解,以及在类中添加持久层对象并使用@Autowired修饰。

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<District> getByParent(String parent) {
        return null;
    }
}

2.在DistrictServiceImpl实现类中实现getByParent(String parent)方法的具体代码。

@Override
public List<District> getByParent(String parent) {
	List<District> list = districtMapper.findByParent(parent);
	for (District district : list) {
		district.setId(null);
		district.setParent(null);
	}
	return list;
}

4 获取省/市/区的列表-控制器

说明:无异常。

4.2 设计请求

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

请求路径:/districts/
请求参数:String parent
请求类型:GET
响应结果:JsonResult<List<District>>
是否拦截:否,需要在拦截器的配置中添加白名单

4.3 处理请求

1.创建com.cy.store.controller.DistrictController控制器类,继承自BaseController类,在类之前添加@RequestMapping("districts")和@RestController注解,并在类中添加业务层对象,对其使用@Autowired注解修饰。

package com.cy.store.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.tedu.store.entity.District;
import cn.tedu.store.service.IDistrictService;
import cn.tedu.store.util.JsonResult;

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

 2.在类中添加处理请求的方法getByParent(String parent)及方法的实现。

@GetMapping({"", "/"})
public JsonResult<List<District>> getByParent(String parent) {
    List<District> data = districtService.getByParent(parent);
    return new JsonResult<>(OK, data);
}

3.在拦截器LoginInterceptorConfigurer类的addInterceptors(InterceptorRegistry registry)方法中将“districts”请求添加为白名单。如果已经添加无需重复添加。

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

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

1.在addAddress.html页面中的head标签内导入的distpicker.data.js和distpicker.js文件注释掉。

JQuery实现中国省市区地址三级联动插件Distpicker。

<!--
<script type="text/javascript" src="../js/distpicker.data.js"></script>
<script type="text/javascript" src="../js/distpicker.js"></script>
-->

2.在新增收货地址表单中,给"选择省"控件添加name="provinceCode"和id="province-list"属性,给"选择市"添加name="cityCode"和id="city-list"属性,给"选择区"控件添加name="areaCode"和id="area-list"属性。以上属性如果已经添加无需重复添加。

3.在addAddress.html页面中body标签内的script标签中添加获取省/市/区列表的代码。

<script type="text/javascript">
    let defaultOption = '<option value="0">----- 请选择 -----</option>';

    $(document).ready(function() {
        showProvinceList();
        $("#city-list").append(defaultOption);
        $("#area-list").append(defaultOption);
    });

    $("#province-list").change(function() {
        showCityList();
    });

    $("#city-list").change(function() {
        showAreaList();
    });

    function showProvinceList() {
        $("#province-list").append(defaultOption);
        $.ajax({
            url: "/districts",
            type: "GET",
            data: "parent=86",
            dataType: "JSON",
            success: function(json) {
                if (json.state == 200) {
                    let list = json.data;
                    console.log("count=" + list.length);
                    for (let i = 0; i < list.length; i++) {
                        console.log(list[i].name);
                        let option = '<option value="' + list[i].code + '">' + list[i].name + '</option>';
                        $("#province-list").append(option);
                    }
                }
            }
        });
    }

    function showCityList() {
        let parent = $("#province-list").val();
        $("#city-list").empty();
        $("#area-list").empty();

        $("#city-list").append(defaultOption);
        $("#area-list").append(defaultOption);

        if (parent == 0) {
            return;
        }

        $.ajax({
            url: "/districts",
            type: "GET",
            data: "parent=" + parent,
            dataType: "JSON",
            success: function(json) {
                if (json.state == 200) {
                    let list = json.data;
                    console.log("count=" + list.length);
                    for (let i = 0; i < list.length; i++) {
                        console.log(list[i].name);
                        let option = '<option value="' + list[i].code + '">' + list[i].name + '</option>';
                        $("#city-list").append(option);
                    }
                }
            }
        });
    }

    function showAreaList() {
        let parent = $("#city-list").val();
        $("#area-list").empty();
        $("#area-list").append(defaultOption);

        if (parent == 0) {
            return;
        }

        $.ajax({
            url: "/districts",
            type: "GET",
            data: "parent=" + parent,
            dataType: "JSON",
            success: function(json) {
                if (json.state == 200) {
                    let list = json.data;
                    console.log("count=" + list.length);
                    for (let i = 0; i < list.length; i++) {
                        console.log(list[i].name);
                        let option = '<option value="' + list[i].code + '">' + list[i].name + '</option>';
                        $("#area-list").append(option);
                    }
                }
            }
        });
    }
</script>

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值