Bootstrap

SpringBoot电脑商城-收货地址

在这里插入图片描述

1. 新增收获地址

1.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;

1.2 创建实体类

/** 收货地址数据的实体类 */
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;
    //....

1.3 持久层

  • 规划需要执行的SQL语句
    1.对应的是插入语句:
    insert into t_address values ();
    
    2.一个用户的收货地址规定最多只能有20条数据对应。所以,在插入用户数据之前先做查询操作,如果收货地址已经20条记录,抛收货地址逻辑控制方面的一个异常。
    select count(*) t_address where uid = ?;
    
  • 接口与抽象方法
    创建一个接口AddressMapper,在这个接口中来定义上面两个Sql语句抽象方法定义。
    /** 收货地址持久层的接口 */
    public interface AddressMapper {
    
       //插入用户的收获地址数据
       Integer insert(Address address);
    
       //根据用户的id统计收获地址数量
       Integer countByUid(Integer uid);
    }
    
  • 配置SQL映射
    创建一个AddressMapper.xml映射文件,在这个文件中添加SQL的映射。
    <?xml version="1.0" encoding="UTF8" ?>
    <!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 type="com.cy.store.entity.Address" id="AddressEntityMap">
           <id property="aid" column="aid"/>
           <result property="provinceCode" column="province_code"/>
           <result property="provinceName" column="province_name"/>
           <result property="cityCode" column="city_code"/>
           <result property="cityName" column="city_name"/>
           <result property="areaCode" column="area_code"/>
           <result property="areaName" column="area_name"/>
           <result property="isDefault" column="is_default"/>
           <result property="createdUser" column="created_user"/>
           <result property="createdTime" column="created_time"/>
           <result property="modifiedUser" column="modified_user"/>
           <result property="modifiedTime" column="modified_time"/>
       </resultMap>
    
       <insert id="insert" keyProperty="aid" useGeneratedKeys="true">
           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>
    
       <select id="countByUid" resultType="java.lang.Integer">
            select count(*) from t_address where uid = #{uid}
        </select>
    
    </mapper>
    
    然后,在test下的mapper文件夹下创建AddressMapperTest
    @Autowired
    private AddressMapper addressMapper;
    
    @Test
    public void insert() {
        Address address = new Address();
        address.setUid(9);
        address.setPhone("1234242");
        address.setName("女朋友");
        addressMapper.insert(address);
    }
    
    @Test
    public void countByUid() {
        System.out.println(addressMapper.countByUid(9));
    }
    

1.4 业务层

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

    查询到的结果大于20,这时候抛出业务控制的异常AddressCountLimitException。

    /** 收货地址总数超出限制的异常(不超过20条)**/
    public class AddressCountLimitException extends ServiceException{
    //...
    

    插入数据时产生异常。

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

    /** 收获地址业务层接口 */
    public interface IAddressService {
       void addNewAddress(Integer uid, String username, Address address);
    }
    
  • 实现抽象方法
    创建一个AddressServiceImpl实现类,去实现接口中抽象方法。

    在配置文件中定义最大收获地址总数,这样如果以后想要修改总数就会很方便。

    # Spring读取配置文件中数据:@Value("${user.address.max-count}")
    user.address.max-count=20
    
    @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.countByUid(uid);
           if(count >= maxCount) {
               throw new AddressCountLimitException("用户收获地址超出上限");
           }
    
           address.setUid(uid);
           Integer isDefault = count == 0 ? 1 : 0; //isDefault设置为1,表示默认地址
           address.setIsDefault(isDefault);
           //补全4项日志
           address.setCreatedUser(username);
           address.setModifiedUser(username);
           address.setCreatedTime(new Date());
           address.setModifiedTime(new Date());
    
           //插入收获地址的方法
           Integer rows = addressMapper.insert(address);
           if(rows != 1) {
               throw new InsertException("插入用户的收获地址产生未知异常");
           }
       }
    }
    

    测试业务层功能是否正常。

    @Autowired
    private IAddressService addressService;
    
    @Test
    public void addNewAddress() {
        Address address = new Address();
        address.setPhone("432312121");
        address.setName("男朋友");
        addressService.addNewAddress(23,"管理员",address);
    }
    

1.5 控制层

  • 处理异常
    业务层抛出了收货地址总数超标的异常,在BaseController中进行处理。
    else if (e instanceof AddressCountLimitException) {
        result.setState(4003);
        result.setMessage("用户收获地址超出上限的异常");
    }
    
  • 设计请求
    /addresses/add_new_address
    post
    Address address, HttpSession session
    JsonResult<Void>
    
  • 处理请求
    在控制层创建AddressController来处理用户收获地址的请求和响应。
    @RequestMapping("addresses")
    @RestController
    public class AddressController extends BaseController {
       @Autowired
       private IAddressService addressService;
    
       @RequestMapping("add_new_address")
       public JsonResult<Void> addNewAddress(Address address, HttpSession session) {
           Integer uid = getuidFromSession(session);
           String username = getUsernameFromSession(session);
           addressService.addNewAddress(uid, username, address);
           return new JsonResult<>(OK);
       }
    }
    
    测试:http://localhost:8080/addresses/add_new_address?name=tom&phone=2323121

1.6 前端页面

<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("新增收获地址失败");
				}
			},
			error: function (xhr) {
				alert("新增收获地址时产生未知的异常" + xhr.message);
			}
		});
	});
</script>

2. 获取省市区列表

在这里插入图片描述
地区列表从数据库获取出来。

2.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;
INSERT INTO t_dict_district VALUES (1,'110100','110101','东城区'),...

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

2.2 实体类

/* 表示省市区的数据实体类 */
public class District extends BaseEntity {
    private Integer id;
    private String parent;
    private String code;
    private String name;

2.3 持久层

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

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

抽象方法定义。DistrictMapper接口。

public interface DistrictMapper {
    /**
     * 根据父代号查询区域信息
     * @param parent 父代号
     * @return 某个父区域下的所有区域列表
     */
    List<District> findByParent(String parent);
}

映射mapper

<?xml version="1.0" encoding="UTF8" ?>
<!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">

    <select id="findByParent" resultType="com.cy.store.entity.District">
        select * from t_dict_district where parent=#{parent}
        order by code ASC
    </select>

</mapper>

单元测试

@Autowired
private DistrictMapper districtMapper;

@Test
public void findByParent() {
    List<District> list = districtMapper.findByParent("210100");
    for (District district : list) {
        System.out.println(district);
    }
}

2.4 业务层

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

public interface IDistrictService {
    /**
     * 根据父代号来查询区域信息(省市区)
     * @param parent
     * @return
     */
    List<District> getByParent(String parent);
}

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

@Service
public class IDistrictServiceImpl implements IDistrictService {

    @Autowired
    private DistrictMapper districtMapper;

    @Override
    public List<District> getByParent(String parent) {
        List<District> list = districtMapper.findByParent(parent);
        //在进行网路数据传输时,为了尽量避免无效数据的传递,可以将无效数据设置为null
        for (District district : list) {
            district.setId(null);
            district.setParent(null);
        }
        return list;
    }
}

3.单元测试

@Autowired
private IDistrictService districtService;

@Test
public void getByParent() {
    List<District> list = districtService.getByParent("86");
    for (District district : list) {
        System.out.println(district);
    }
}

2.5 控制层

  • 设计请求
    /district/
    get
    String parent
    JsonResult<List<District>>
    
  • 请求处理
    创建一个DistrictController类,编写处理请求的方法
    @RequestMapping("districts")
    @RestController
    public class DistrictController extends BaseController{
    
       @Autowired
       private IDistrictService districtService;
    
       @RequestMapping({"/",""})
       public JsonResult<List<District>> getByParent(String parent) {
           List<District> data = districtService.getByParent(parent);
           return new JsonResult<>(OK,data);
       }
    }
    
    district请求添加到白名单中。
    pattrens.add("/districts/**");
    
    然后启动服务器,访问 http://localhost:8080/districts/?parent=86 进行测试。

2.6 前端页面

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

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

2.检查前端页面在提交省市区数据时是否有相关name属性和id属性。
3.运行前端看是否还可以正常保存数据(除了省市区以外)

3. 获取省市区的名称

将行政区下拉框动态的从数据库中获取
在这里插入图片描述

3.1 持久层

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

select * from t_dist_district where code = ?;

2.在DistrictMapper接口定义出来

String findNameByCode(String code);

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

<select id="findNameByCode" resultType="java.lang.String">
    select name from t_dict_district where code=#{code}
</select>

4.单元测试

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

3.2 业务层

1.在业务层没有异常需要进行处理
2.定义对应的业务层接口中的抽象方法

String getNameByCode(String code);

3.在子类中进行实现

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

4.测试可以省略不写(一般超过8行建议测试)

3.3 业务层优化

1.添加地址层AddressServiceImpl,依赖于IDistrictService层

//在添加用户的收获地址的业务层依赖于IDistrictService的业务层接口
@Autowired
private IDistrictService districtService;

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

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

3.4 前端页面

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

//value属性用于表示当前的这个区域的code值
let defaultOption = "<option value='0'>---- 请选择 ----</option>"
$(document).ready(function () {
	showProvinceList();
	//设置默认的“请选择”的值,作为控件的默认值
	$("#city-list").append(defaultOption);
	$("#area-list").append(defaultOption);
});

$("#city-list").change(function () {
	//获取行政区父代码
	let parent = $("#city-list").val();
	//表示清空select下拉列表中的所有option元素
	$("#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;
				for (let i = 0; i < list.length; i++) {
					let opt = "<option value='"+list[i].code+"'>"+list[i].name+"</option>";
					$("#area-list").append(opt);
				}
			} else {
				alert("区县信息加载失败");
			}
		}
	});
});

//change()函数用于监听某个控件是否发生改变,一旦发生改变就会触发参数的函数
$("#province-list").change(function () {
	//获取行政区父代码
	let parent = $("#province-list").val();
	//表示清空select下拉列表中的所有option元素
	$("#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;
				for (let i = 0; i < list.length; i++) {
					let opt = "<option value='"+list[i].code+"'>"+list[i].name+"</option>";
					$("#city-list").append(opt);
				}
			} else {
				alert("城市信息加载失败");
			}
		}
	});
});

/* 省的下拉列表数据展示 */
function showProvinceList() {
	$.ajax({
		url: "/districts/",
		type: "post",
		data: "parent=86",
		dataType: "JSON",
		success: function (json) {
			if(json.state == 200) {
				let list = json.data;
				for (let i = 0; i < list.length; i++) {
					let opt = "<option value='"+list[i].code+"'>"+list[i].name+"</option>";
					$("#province-list").append(opt);
				}
			} else {
				alert("省/直辖区信息加载失败");
			}
		}
	});
}

在这里插入图片描述
说明:省、城市、区县,三者有依赖关系,城市依赖省、区县依赖城市。所以省发生改变,城市和区县都要恢复默认值,城市发生改变,则区县恢复默认值。

4. 收货地址列表展示

在这里插入图片描述

4.1 持久层

1.数据库数据的查询操作

select * from t_address where uid = ? order by is_default desc, created_time desc;

2.接口和抽象方法

//根据用户id查询用户的收货地址数据
List<Address> findByUid(Integer uid);

3.在xml中添加对应SQL语句映射

<select id="findByUid">
    select * from t_address where uid=#{uid}
    order by is_default desc, created_time desc;
</select>

4.测试

@Test
public void findByUid() {
    List<Address> list = addressMapper.findByUid(9);
    System.out.println(list);
}

4.2 业务层

1.不用抛出相关的异常,不用进行异常的设计
2.设计业务层的接口和抽象方法

List<Address> getByUid(Integer uid);

3.在实现类中实现此方法的逻辑

@Override
public List<Address> getByUid(Integer uid) {
    List<Address> list = addressMapper.findByUid(uid);
    for (Address address : list) {
    	//不需要的数据设置为null,节省资源
        address.setProvinceCode(null);
        address.setCityCode(null);
        address.setAreaCode(null);
        address.setTel(null);
        address.setIsDefault(null);
        address.setCreatedTime(null);
        address.setCreatedUser(null);
        address.setModifiedTime(null);
        address.setModifiedUser(null);
    }
    return list;
}

4.3 控制层

1.请求设计

/addresses
HttpSession session
get
JsonResult<List<Address>>

2.实现请求方法的编写

@RequestMapping({"/",""})
public JsonResult<List<Address>> getByUid(HttpSession session) {
    Integer uid = getuidFromSession(session);
    List<Address> data = addressService.getByUid(uid);
    return new JsonResult<>(OK, data);
}

3.先登录,再访问请求的地址进行数据的测试

4.4 前端页面

在address.html页面中,编写查询用户收货地址数据的展示列表。

<script type="text/javascript">
	$(document).ready(function () {
		showAddressList();
	});
	//展示用户收货地址数据列表
	function showAddressList() {
		$.ajax({
			url: "/addresses/",
			type: "get",
			dataType: "JSON",
			success: function (json) {
				if(json.state == 200) {
					let list = json.data;
					$("#address-list").empty();
					for (let i = 0; i < list.length; i++) {
						let tr = '<tr>\n' +
								'<td>#{tag}</td>\n' +
								'<td>#{name}</td>\n' +
								'<td>#{address}</td>\n' +
								'<td>#{phone}</td>\n' +
								'<td><a class="btn btn-xs btn-info"><span class="fa fa-edit"></span> 修改</a></td>\n' +
								'<td><a class="btn btn-xs add-del btn-info"><span class="fa fa-trash-o"></span> 删除</a></td>\n' +
								'<td><a class="btn btn-xs add-def btn-default">设为默认</a></td>\n' +
								'</tr>';
						//两种替换方式
						tr = tr.replace(/#{tag}/g, list[i].tag);
						tr = tr.replace(/#{name}/g, list[i].name);
						tr = tr.replace("#{phone}", list[i].phone);
						tr = tr.replace("#{address}", list[i].address);

						$("#address-list").append(tr);
					}
					//如果是默认地址,则隐藏“设为默认地址”按钮
					$(".add-def:eq(0)").hide();
				} else {
					alert("用户收货地址信息加载失败");
				}
			}
		});
	}
</script>

5. 设置默认收货地址

在这里插入图片描述

5.1 持久层

  • SQL语句规划
    检测当前用户想设置为默认收货地址的这条数据是否存在
    select * from t_address aid = ?
    
    在修改用户的默认收货地址之前,先将该用户的所有收货地址设置为非默认。
    update t_address set is_default = 0 where uid = ?
    
    将用户当前选中的这条记录设置为默认收货地址
    update t_address set is_default = 1, modified_user = ?, modified_time = ? where aid = ?
    
  • 设计抽象方法
    在AddressMapper接口中进行定义和声明
    //根据aid查询收货地址数据
    Address findByAid(Integer aid);
    
    //根据用户的uid值来修改用户的收货地址默认为非默认
    Integer updateNonDefault(Integer uid);
    
    Integer updatDefaultByAid(
            @Param("aid") Integer aid,
            @Param("modifiedUser") String modifiedUser,
            @Param("modifiedTime") Date modifiedTime);
    
  • 配置SQL映射
    在AddressMapper.xml中进行配置
    <select id="findByAid" resultMap="AddressEntityMap">
        select * from t_address where aid=#{aid}
    </select>
    
    <update id="updateNonDefault">
        update t_address
        set is_default = 0
        where uid=#{uid}
    </update>
    <update id="updatDefaultByAid">
        update t_address
        set is_default = 1, modified_user=#{modifiedUser}, modified_time=#{modifiedTime}
        where aid = #{aid}
    </update>
    
    测试
    @Test
    public void findByAid() {
        System.out.println(addressMapper.findByAid(5));
    }
    
    @Test
    public void updateNonDefault() {
        addressMapper.updateNonDefault(10);
    }
    @Test
    public void updatDefaultByAid() {
        addressMapper.updatDefaultByAid(3,"明明",new Date());
    }
    

5.2 业务层

  • 异常规划
    1.在执行更新时产生未知的UpdateException异常(已经创建)。
    2.访问的数据不是当前登录用户的收货地址数据,非法访问:AccessDefindException。
    3.收货地址可能不存在异常AddressNotFoundException
    /** 非法访问的异常 */
    public class AccessDeniedException extends ServiceException {
    /** 收货地址数据不存在的异常 */
    public class AddressNotFoundException extends ServiceException {
    
  • 设计业务层抽象方法
    在接口IAddressService中编写抽象方法
    /**
     * 修改某个用户的某条收货地址数据为默认收货地址
     * @param aid 收货地址的id
     * @param uid 用户的id
     * @param username 表示修改执行的人
     */
    void setDefault(Integer aid, Integer uid, String username);
    
  • 实现抽象方法
    在AddressServiceImpl类中进行开发和业务设计。
    @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("非法数据访问");
        }
        //先将所有的收货地址设置非默认
        Integer rows = addressMapper.updateNonDefault(uid);
        if(rows < 1) {
            throw new UpdateException("更新数据产生未知的异常");
        }
        //将用户选中的某条地址设置为默认收货地址
        rows = addressMapper.updatDefaultByAid(aid, username, new Date());
        if(rows != 1) {
            throw new UpdateException("更新数据产生未知的异常");
        }
    }
    
    测试
    @Test
    public void setDefault() {
        addressService.setDefault(3,10,"管理员");
    }
    

5.3 控制层

  • 处理异常
    在BaseController类中进行统一处理
    else if (e instanceof AddressNotFoundException) {
        result.setState(4004);
        result.setMessage("用户的收货地址数据不存在的异常");
    }else if (e instanceof AccessDeniedException) {
        result.setState(4005);
        result.setMessage("收货地址数据非法访问的异常");
    }
    
  • 设计请求
    /address/{aid}/set_default
    @PathVariable("aid") Integer aid, HttpSession session
    get
    JsonResult<Void>
    
  • 完成请求
    在AddressController类中编写请求处理方法
    //RestFul风格的请求
    @RequestMapping("{aid}/set_default")
    public JsonResult<Void> setDefault(@PathVariable("aid") Integer aid, 
    								HttpSession session) {
        addressService.setDefault(
        	aid, 
       		getuidFromSession(session),
        	getUsernameFromSession(session));
        return new JsonResult<>(OK);
    }
    
    在浏览器先登录再访问一个请求:http://localhost:8080/addresses/4/set_default

5.4 前端页面

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

for (let i = 0; i < list.length; i++) {
	let tr = '<tr>\n' +
			'<td>#{tag}</td>\n' +
			'<td>#{name}</td>\n' +
			'<td>#{address}</td>\n' +
			'<td>#{phone}</td>\n' +
			'<td><a class="btn btn-xs btn-info"><span class="fa fa-edit"></span> 修改</a></td>\n' +
			'<td><a class="btn btn-xs add-del btn-info"><span class="fa fa-trash-o"></span> 删除</a></td>\n' +
			'<td><a οnclick="setDefault(#{aid})" class="btn btn-xs add-def btn-default">设为默认</a></td>\n' +
			'</tr>';
	//两种替换方式
	tr = tr.replace(/#{tag}/g, list[i].tag);
	tr = tr.replace(/#{name}/g, list[i].name);
	tr = tr.replace("#{phone}", list[i].phone);
	tr = tr.replace("#{address}", list[i].address);
	tr = tr.replace("#{aid}", list[i].aid);

	$("#address-list").append(tr);
}

2.在address.html页面点击“设置默认”按钮,发送ajax。完成setDefault()方法的声明和定义。

function setDefault(aid) {
	$.ajax({
		url: "/addresses/" + aid + "/set_default",
		type: "post",
		//因为用的RestFul风格,参数在url中发送
		dataType: "JSON",
		success: function (json) {
			if(json.state == 200) {
				//重新加载收货地址列表页面
				showAddressList();
			} else {
				alert("设置默认收货地址失败");
			}
		},
		error: function (xhr) {
			alert("设置默认收货地址时产生未知的异常" + xhr.message);
		}
	});
}

6. 删除收货地址

6.1 持久层

  • 规划需要执行的SQL语句
    1.在删除之前判断该数据是否存在,判断该条地址数据的归属是否是当前的用户。

    2.执行删除收货地址的信息

    delete from t_address where aid = ?
    

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

    select * from t_address where uid = ? order by modified_time desc limit 0,1
    

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

  • 设计抽象方法
    在AddressMapper接口中进行抽象方法的设计

    //根据收获地址id删除收货地址数据
    Integer deleteByAid(Integer aid);
    
    //根据用户uid查询当前用户最后一次被修改的收货地址数据
    //如果删除的是默认收货地址,则将它设置为默认收货地址
    Address findLastModified(Integer uid);
    
  • 映射SQL语句
    在AddressMapper.xml文件中进行映射

    <delete id="deleteByAid">
        delete from t_address where aid=#{aid}
    </delete>
    <select id="findLastModified" resultMap="AddressEntityMap">
        select * from t_address
        where uid=#{uid}
        order by modified_time desc limit 0,1
    </select>
    

    测试

    @Test
    public void deleteByAid(){
        addressMapper.deleteByAid(3);
    }
    
    @Test
    public void findLastModified(){
        System.out.println(addressMapper.findLastModified(10));
    }
    

6.2 业务层

  • 规划异常
    在删除的时候可能会产生未知的删除异常导致数据不能删除成功,则抛出DeleteException异常。需要定义和创建。
    /** 删除数据时产生的异常 */
    public class DeleteException extends ServiceException{
    	//...
    
  • 抽象方法设计
    在IAddressService接口中进行设计抽象方法
    /**
     * 删除用户选中的收货地址
     * @param aid 收货地址aid
     * @param uid 用户uid
     * @param username 用户名
     */
    void delete(Integer aid, Integer uid, String username);
    
  • 实现抽象方法
    @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 DeleteException("删除数据产生未知的异常");
        }
    
        Integer count = addressMapper.countByUid(uid);
        if(count == 0) {
            return; //如果没有收货地址,终止程序,不走下面的代码
        }
        
        if(result.getIsDefault() == 0) return; //删除的不是默认地址,就不走下面的代码
        
        //将这条数据的is_default字符的值设置为1
        Address address = addressMapper.findLastModified(uid);
        rows = addressMapper.updatDefaultByAid(
                address.getAid(),username,new Date()
        );
        if(rows != 1) {
            throw new UpdateException("更新数据时产生未知的异常");
        }
    
    }
    
    测试
    @Test
    public void delete(){
        addressService.delete(5,10,"管理员");
    }
    

6.3 控制层

  • 处理异常DeleteException类
    else if (e instanceof UpdateException) {
        result.setState(5004);
        result.setMessage("删除数据时产生未知的异常");
    }
    
  • 设计请求
    /addresses/{aid}/delete
    post
    Integer aid, HttpSession session
    JsonResult<Void>
    
  • 编写请求处理方法
    @RequestMapping("{aid}/delete")
    public JsonResult<Void> delete(@PathVariable("aid") Integer aid, HttpSession session) {
        addressService.delete(
                aid,getuidFromSession(session),getUsernameFromSession(session)
        );
        return new JsonResult<>(OK);
    }
    

6.4 前端页面

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

<td><a onclick="deleteByAid(#{aid})" class="btn btn-xs add-del btn-info"><span class="fa fa-trash-o"></span> 删除</a></td>

再去编写delete(aid)方法的具体实现

function deleteByAid(aid) {
	$.ajax({
		url: "/addresses/" + aid + "/delete",
		type: "post",
		//因为用的RestFul风格,参数在url中发送
		dataType: "JSON",
		success: function (json) {
			if(json.state == 200) {
				//重新加载收货地址列表页面
				showAddressList();
			} else {
				alert("删除收货地址失败");
			}
		},
		error: function (xhr) {
			alert("删除收货地址时产生未知的异常" + xhr.message);
		}
	});
}
;