新增收货地址
收货地址管理页面

点击新增收货地址按钮,出现新的页面,地址信息编辑表单

各功能的开发顺序
收货地址模块的功能:列表的展示、修改、删除、设置默认、新增收货地址
功能模块的开发顺序:新增收货地址-》列表展示-》设置默认收货地址-》删除收货地址-》修改收货地址
数据库表的创建

收货地址实体类的创建
创建收货地址实体类com.bang.store.pojo.Address
,继承自BasePojo
基类,因为其同样含有四个公共字段
属性名与数据表名一致,只是注意数据库字段一般命名方式为xx_xx
,而java采用驼峰命名法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
@Data @AllArgsConstructor @NoArgsConstructor public class Address extends BasePojo{ Integer aid; Integer uid; String name; String provinceName; String provinceCode; String cityName; String cityCode; String areaName; String areaCode; String zip; String address; String phone; String tel; String tag; Integer isDefault;
}
|
持久层
规划执行的SQL语句
新增收货地址本质上是将表单数据插入对应数据库表中,对应插入语句
1
| 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(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);
|
各个平台用户收货地址的保存是有上限的,在这里我们规定每个用户最多只能有3条(便于测试,实际上允许的地址数据会多很多)地址数据, 所以总体逻辑发生改变,即插入数据之前都需判断当前用户地址数目
本质上为依据用户id(uid)查询对应数据条数
1
| select count(*) from t_address where uid=?;
|
接口和抽象方法
创建一个新的接口AddressMapper
,在该接口中定义上述两个sql
语句对应的抽象方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
public interface AddressMapper {
Integer insert(Address address);
Integer countByUid(Integer uid); }
|
配置SQL映射
在resource/mapper
下创建地址映射文件Address.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.bang.store.mapper.AddressMapper">
<resultMap id="addressPojoMap" type="com.bang.store.pojo.Address"> <id column="uid" property="uid"/> <result column="province_name" property="provinceName"/> <result column="province_code" property="provinceCode"/> <result column="city_name" property="cityName"/> <result column="city_code" property="cityCode"/> <result column="area_name" property="areaName"/> <result column="area_code" property="areaCode"/> <result column="is_default" property="isDefault"/> <result property="createUser" column="create_user"/> <result property="createTime" column="create_time"/> <result property="modifiedUser" column="modified_user"/> <result property="modifiedTime" column="modified_time"/> </resultMap>
<select id="countByUid" resultType="int"> select count(*) from t_address where uid=#{uid}; </select>
<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> </mapper>
|
单元测试
在test/java
下创建com.bang.store.mapper.AddressMapperTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package com.bang.store.mapper;
import com.bang.store.pojo.Address; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@SpringBootTest @RunWith(SpringJUnit4ClassRunner.class) public class AddressMapperTest {
@Resource AddressMapper addressMapper;
@Test public void countByUid(){ Integer count = addressMapper.countByUid(1); System.out.println("当前用户地址数目:"+count); }
@Test public void insert(){ Address address = new Address(); address.setUid(1); address.setProvinceName("安徽省");
Integer rows = addressMapper.insert(address); System.out.println(rows); } }
|
业务层
规划异常
如果用户插入的是第一条收货地址,需要将当前地址设置为默认的收货地址(即当前地址的is_default
字段设置为1)。如果查询到的结果大于3,这是需要抛出业务层的异常AddressCountLimit
表明单个用户插入地址数据已达上限
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.bang.store.service.ex;
public class AddressCountLimitException extends ServiceException{ public AddressCountLimit() { super(); }
public AddressCountLimit(String message) { super(message); }
public AddressCountLimit(String message, Throwable cause) { super(message, cause); }
public AddressCountLimit(Throwable cause) { super(cause); }
protected AddressCountLimit(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } }
|
地址数据插入过程的异常,InsertException
在之前用户信息的功能模块中已经定义过了
接口和抽象方法
创建地址业务逻辑接口IAddressService
,在其中定义抽象方法
1 2 3 4 5 6 7 8 9 10 11 12
|
public interface IAddressService {
void addAddress(Integer uid, String username, Address address); }
|
抽象方法实现
创建接口实现类,AddressServiceImpl
,在其中实现接口抽象方法
在配置文件application.properties
文件中定义收货地址数量上限
1
| user.address.max-count:3
|
Spring
读取配置文件数据方法
1 2
| @Value("${user.address.max-count}") private Integer maxCount;
|
业务逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
@Service public class AddressServiceImpl implements IAddressService { @Resource AddressMapper addressMapper;
@Value("${user.address.max-count}") private Integer maxCount; @Override public void addAddress(Integer uid, String username, Address address) { int count = addressMapper.countByUid(uid); if(count>maxCount){ throw new AddressCountLimitException("收货地址数目超出规定上限"); } if(count==0){ address.setIsDefault(1); } address.setUid(uid); address.setCreatedUser(username); address.setCreatedTime(new Date()); address.setModifiedUser(username); address.setModifiedTime(new Date()); Integer rows = addressMapper.insert(address); if(rows!=1){ throw new InsertException("地址数据插入未知异常"); }
} }
|
单元测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package com.bang.store.service;
import com.bang.store.pojo.Address; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@SpringBootTest @RunWith(SpringJUnit4ClassRunner.class) public class AddressServiceTest { @Autowired IAddressService addressService;
@Test public void addAddress(){ Integer uid = 2; String username = "孙权"; Address address = new Address(); address.setUid(uid); address.setName("王伟");
addressService.addAddress(uid,username,address); } }
|
控制层
异常处理
业务层抛出的收货地址数目大于规定最大阈值的异常处理,在控制层基类BaseController
中定义对应的逻辑
1 2 3 4
| else if (e instanceof AddressCountLimitException) { result.setState(7000); result.setMessage("收获地址数目超出上限"); }
|
设计请求
1 2 3 4
| request url: /address/add_new_address request method: POST request params: Address address,HttpSession session response data: new JsonResult<Void>
|
处理请求
新建收货地址控制层类AddressController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @RestController @RequestMapping("/address") public class AddressController extends BaseController{
@Autowired IAddressService addressService;
@RequestMapping("/add_new_address") public JsonResult<Void> addAddress(Address address, HttpSession session){ Integer uid = getUidFromSession(session); String username = getUsernameFromSession(session); addressService.addAddress(uid,username,address); return new JsonResult<>(OK,"新增收货地址成功"); } }
|
前端页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <script> $("#btn-add-new-address").click(function (){ $.ajax({ url: "/address/add_new_address" ,type: "POST" ,data: $("#form-add-new-address").serialize() ,dataType: "JSON" ,success: function (data){ if(data.state == 200){ alert("地址保存成功"); }else{ alert("地址保存失败 "+data.message); } } ,error:function (xmh){ alert("地址保存失败"+xmh.status); } }); }); </script>
|
获取省市区列表
省市区列表数据库表的创建
创建数据库存储省市区列表数据
1 2 3 4 5 6 7
| CREATE TABLE t_dict_district ( id int(11) NOT NULL AUTO_INCREMENT, parent varchar(6) DEFAULT NULL, code varchar(6) DEFAULT NULL, name varchar(16) DEFAULT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|

数据库字段说明
parent
表示父区域代码号
code
表示区域自己的代码号
name
区域名称
省市区列表实体类的创建
创建对应的实体类com.bang.store.pojo.district
1 2 3 4 5 6 7 8 9 10 11 12
|
@Data @AllArgsConstructor @NoArgsConstructor public class District { Integer id; String parent; String code; String name; }
|
省市区列表持久层
规划执行的SQL语句
查询语句,根据父代号进行查询
1
| select * from t_dict_district where parent=? order by code ASC;
|
接口和抽象方法
创建新的接口DistrictMapper
,在其中定义对应的抽象方法
1 2 3 4 5 6 7 8 9 10 11 12
|
public interface DistrictMapper {
List<District> findByParent(String parent); }
|
配置SQL映射
在resource/mapper
文件夹下创建DistrictMappper.xml
文件,编写对应的SQL映射
1 2 3 4 5 6 7 8 9
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.bang.store.mapper.DistrictMapper"> <select id="findByParent" resultType="com.bang.store.pojo.District"> select * from t_dict_district where parent=#{parent} order by code ASC; </select> </mapper>
|
单元测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package com.bang.store.mapper;
import com.bang.store.pojo.District; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.annotation.Resource; import java.util.List;
@SpringBootTest @RunWith(SpringJUnit4ClassRunner.class) public class DistrictMapperTest {
@Resource DistrictMapper districtMapper; @Test public void findByParent(){ String parent="86"; List<District> districtList = districtMapper.findByParent(parent); for (District district : districtList) { System.out.println(district); } } }
|
省市区列表持久层
规划异常
此功能无异常需要处理
抽象接口和方法
新建省市区列表业务层接口IDistrictService
,在其中创建对应的抽象方法
1 2 3 4 5 6 7 8 9 10 11
|
public interface IDistrictService {
List<District> getByParent(String parent); }
|
抽象方法实现
创建业务层接口实现类DistrictServiceImpl
,在其中实现接口中的抽象方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Service public class DistrictServiceImpl implements IDistrictService { @Resource DistrictMapper districtMapper; @Override public List<District> getByParent(String parent) { List<District> districtList = districtMapper.findByParent(parent);
for (District district : districtList) { district.setId(null); district.setParent(null); } return districtList; } }
|
单元测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package com.bang.store.service;
import com.bang.store.pojo.District; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@SpringBootTest @RunWith(SpringJUnit4ClassRunner.class) public class DistrictServiceTest { @Autowired IDistrictService districtService;
@Test public void getByParent(){ String parent = "86"; List<District> districts = districtService.getByParent(parent); for (District district : districts) { System.out.println(district); } } }
|
省市区列表控制层
设计请求
1 2 3 4
| request url: /district/ request method: GET request params: String parent response data: new JsonResult<Lsit<District>>
|
请求处理
创建新的控制层类DistrictController
,在里面编写对应的请求处理方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package com.bang.store.controller;
import com.bang.store.pojo.District; import com.bang.store.service.IDistrictService; import com.bang.store.utils.JsonResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController @RequestMapping("/district") public class DistrictController extends BaseController{
@Autowired IDistrictService districtService;
@RequestMapping({"/",""}) public JsonResult<List<District>> getByParent(String parent){ List<District> districtList = districtService.getByParent(parent); return new JsonResult<>(OK,"省市区信息获取成功",districtList); } }
|
省市区列表前端页面
前一个版本省市区信息是保存在前端页面的js
文件中,通过js
代码获取的
1 2
| <script type="text/javascript" src="../js/distpicker.data.js"></script> <script type="text/javascript" src="../js/distpicker.js"></script>
|
现将这两行代码注释掉,让省市区数据信息依靠后端接口获取
检查前端页面在提交表单数据省市区数据时是否有相关的name属性和id属性
获取省市区名称
依据省市区代码获取对应的省市区名称
省市区名称持久层
规划执行的SQL语句
依据省市区code查询对应省市区的name,本质为一条查询语句
1
| select name from t_dict_district where code=?;
|
接口和抽象方法
1 2 3 4 5 6
|
String findNameByCode(String code);
|
配置SQL映射
1 2 3
| <select id="findNameByCode" resultType="java.lang.String"> select name from t_dict_district where code=#{code}; </select>
|
单元测试
1 2 3 4 5
| @Test public void findNameByCode(){ String name = districtMapper.findNameByCode("110000"); System.out.println(name); }
|
省市区名称业务层
无特定的异常需要处理,所以跳过规划异常
接口和抽象方法
1 2 3 4 5 6
|
String getNameByCode(String code);
|
抽象方法实现
1 2 3 4 5
| @Override public String getNameByCode(String code) { String name = districtMapper.findNameByCode(code); return name; }
|
单元测试
1 2 3 4 5
| @Test public void getNameByCode(){ String name = districtService.getNameByCode("120000"); System.out.println(name); }
|
新增收货地址业务层的优化
用户新增收货地址,前端页面只会向后端传递省市区的代码,所以业务层在调用持久层接口将数据存入数据库时,需要调用IDistrictService
接口中的getNameByCode
来获取对应省市区名称,再存入数据库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
@Service public class AddressServiceImpl implements IAddressService { @Resource AddressMapper addressMapper; @Autowired IDistrictService districtService;
@Value("${user.address.max-count}") private Integer maxCount; @Override public void addAddress(Integer uid, String username, Address address) { int count = addressMapper.countByUid(uid); if(count>maxCount){ throw new AddressCountLimitException("收货地址数目超出规定上限"); } if(count==0){ address.setIsDefault(1); } address.setUid(uid); address.setCreatedUser(username); address.setCreatedTime(new Date()); address.setModifiedUser(username); address.setModifiedTime(new Date());
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);
Integer rows = addressMapper.insert(address); if(rows!=1){ throw new InsertException("地址数据插入未知异常"); }
} }
|
获取省市区前端页面
addAddress.html
页面中编写对应的省市区展示,根据用户不同的选择将对应的省市区信息填充到对应的下拉列表
编写相关事件代码
整体前端页面的逻辑
- 用户点击新增收货地址按钮
- 出现地址信息表单页面,触发事件,自动将86(代表中国地区编号)发送后端,请求省份信息列表,填充到省份下拉列表,此时城市和区县下拉列表为只存在默认选项
- 省份下拉列表值发生改变,触发事件,自动向后端发送请求(携带当前选中省份号码),请求该省份的所有城市信息列表,填充到城市下拉列表
- 城市下拉列表发生改变,出发事件,自动向后端发送请求(携带当前选中城市号码),请求该城市的所有区县信息列表,填充到区县下拉列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
| <script> let defaultOption = "<option value='0'>---- 请选择 ----</option>" $(document).ready(function () { showProvinceList(); $("#city-list").append(defaultOption); $("#area-list").append(defaultOption); })
function showProvinceList(){ $("#province-list").append("<option value='0'>---- 请选择省/直辖市 ----</option>") $.ajax({ url: "/district/" ,type: "GET" ,data: "parent=86" ,dataType: "JSON" ,success: function (data){ if(data.state == 200){ let provinceList = data.data; for(let i=0;i<provinceList.length;i++){ $("#province-list").append("<option value='"+provinceList[i].code+"'>"+provinceList[i].name+"</option>") } }else{ alert("省份数据获取失败 "+data.message); } } ,error:function (xmh){ alert("省份数据获取过程中发生未知错误"+xmh.status); } }); }
$("#province-list").change(function (){ $("#city-list").empty(); $("#area-list").empty(); $("#city-list").append("<option value='0'>---- 请选择市 ----</option>") $("#area-list").append("<option value='0'>---- 请选择区县 ----</option>") if($("#province-list").val()==0) return; $.ajax({ url: "/district/" ,type: "GET" ,data: "parent="+$("#province-list").val() ,dataType: "JSON" ,success: function (data){ if(data.state == 200){ let cityList = data.data; for(let i=0;i<cityList.length;i++){ $("#city-list").append("<option value='"+cityList[i].code+"'>"+cityList[i].name+"</option>") } }else{ alert("城市数据获取失败 "+data.message); } } ,error:function (xmh){ alert("城市数据获取过程中发生未知错误"+xmh.status); } }); })
$("#city-list").change(function (){ $("#area-list").empty(); $("#area-list").append("<option value='0'>---- 请选择区县 ----</option>") if($("#city-list").val()==0) return; $.ajax({ url: "/district/" ,type: "GET" ,data: "parent="+$("#city-list").val() ,dataType: "JSON" ,success: function (data){ if(data.state == 200){ let areaList = data.data; for(let i=0;i<areaList.length;i++){ $("#area-list").append("<option value='"+areaList[i].code+"'>"+areaList[i].name+"</option>") } }else{ alert("区县数据获取失败 "+data.message); } } ,error:function (xmh){ alert("区县数据获取过程中发生未知错误"+xmh.status); } }); }) $("#btn-add-new-address").click(function (){ $.ajax({ url: "/address/add_new_address" ,type: "POST" ,data: $("#form-add-new-address").serialize() ,dataType: "JSON" ,success: function (data){ if(data.state == 200){ alert("地址保存成功"); }else{ alert("地址保存失败 "+data.message); } } ,error:function (xmh){ alert("地址保存失败"+xmh.status); } }); }); </script>
|