springboot电脑商城项目 6.新增收货地址7. 省市区展示


6. 新增收货地址

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

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

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

package com.jiabowen.store.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
/**收货地址实体类*/
public class Address extends BaseEntity {
    private Integer aid;
    private Integer uid;
    private String name;
    private String provinceName;
    private String provinceCode;
    private String cityName;
    private String cityCode;
    private String areaName;
    private String areaCode;
    private String zip;
    private String address;
    private String phone;
    private String tel;
    private String tag;
    private Integer isDefault;
}

3.新增收货地址-持久层

3.1 各功能的开发顺序

当前收货地址功能模块:列表显示、修改、删除、设置默认、新增收货地址。开发顺序:新增收获地址-列表展示-设置默认收获地址-修改收货地址

3.2规划需要执行的SQL语句

1.对应插入语句:

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

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

select count(*) from t_address where uid = ?

3.3 接口与抽象方法

创建一个接口Address,在这个接口中来定义上面两个Sql语句抽象方法映射。

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

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

3.4 配置SQL映射

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

<?xml version="1.0" encoding="UTF-8" ?>



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

    

测试mapper层代码

@Test
public void insert(){
    Address address = new Address();
    address.setUid(29);
    address.setName("张三");
    address.setPhone("11122233344");
    System.out.println(addressMapper.insert(address));
}

@Test
public void countByUid(){
    Integer count = addressMapper.countByUid(29);
    System.out.println(count);
}

4. 新增收货地址-业务层

4.1 规划异常

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

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

public class AddressCountLimitException extends ServiceException {
    //...
}

4.2 接口与抽象方法

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

/**
 * 收货地址业务层接口
 */
public interface IAddressService {
    void addNewAddress(Integer uid, String username, Address address);
}

2.创建一个AddressServiceImpl实现类,去实现接口中抽象方法

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("用户收货地址超出上限");
        }
        //设置uid isdefault
        address.setUid(uid);
        Integer isdefault = count == 0 ? 1 : 0;//1表示默认,0表示不是默认
        address.setIsDefault(isdefault);
        //补全日志
        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("插入用户的收货地址产生未知异常");
        }
    }
}

配置文件

#Spring读取配置文件中的数据:${user.address.max-count}
user.address.max-count=20

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

@SpringBootTest
@RunWith(SpringRunner.class)
public class AddressServiceTest {
    @Autowired
    IAddressService addressService;
    @Test
    public void addNewAddress() {
        Address address = new Address();
        address.setPhone("11111111111");
        address.setName("张张");
        addressService.addNewAddress(50,"李四",address);
    }
}

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

5.1 处理异常

业务层抛出了对应的收货地址总数超标异常,需要在baseController中进行处理

else if (e instanceof AddressCountLimitException) {
    result.setState(4003);
    result.setMessage("用户的收货地址数量超出限制异常");
}

5.2 设计请求

/addresses/add_new_address
post
Address address,Httpsession session
JsonResult

5.3 处理请求

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

package com.jiabowen.store.controller;

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

import javax.servlet.http.HttpSession;

@RequestMapping("address")
@RestController
public class AddressController extends BaseController {

    @Autowired
    private IAddressService addressService;

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

测试接口:

  • 先登录

  • 登陆后访问:http://localhost:8080/address/add_new_address?name=tom&phone=11222333444

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


7 获取省市区列表

1.获取省市区列表

CREATE TABLE t_dict_district (
  id INT(11) NOT NULL AUTO_INCREMENT,
  parent VARCHAR(6) DEFAULT NULL,
  CODE VARCHAR(6) DEFAULT NULL,
  NAME VARCHAR(16) DEFAULT NULL,
  PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

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

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

public class District extends BaseEntity {
    private Integer id;
    private String parent;
    private String code;
    private String name;
}

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

查询语句:

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

asc:升序排列

抽象方法定义:DistrictMapper接口

package com.jiabowen.store.mapper;

import com.jiabowen.store.entity.District;

import java.util.List;

public interface DistrictMapper {

    /**
     * 根据父代号查询区域信息
     */
    List findByParent(Integer parent);

}

接口映射

<?xml version="1.0" encoding="UTF-8" ?>


    

测试持久层

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

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

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

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

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

@Service
public class DistrictService implements IDistrictService {

    @Autowired
    private DistrictMapper districtMapper;

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

3.进行单元测试

@SpringBootTest
@RunWith(SpringRunner.class)
public class DistrictServiceTest {

    @Autowired
    private IDistrictService districtService;

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

5 获取省市区列表-控制层

5.1 设计请求

/districts
GET
String parent
JsonResult>

5.2 实现请求

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

@RequestMapping("districts")
@RestController
public class DistrictController extends BaseController {

    @Autowired
    private IDistrictService districtService;

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

将districts请求添加白名单中。

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

直接请求服务器,访问:localhost:8080/districts?parent=86

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

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


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

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

获取省市区的名称

1 获取省市区名称

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

select * from t_dist_districts where code = ?

2.在DistrictMapper接口定义出来

String findNameByCode(String code);

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


4.单元测试方法

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

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

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

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

String getNameByCode(String code);

3.在子类中进行实现

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

4.测试

注:超过8行以上代码都要进行独立的测试

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

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

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

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

String provinceNameByCode = districtService.getNameByCode(address.getProvinceCode());
String cityNameByCode = districtService.getNameByCode(address.getCityCode());
String areaNameByCode = districtService.getNameByCode(address.getAreaCode());
address.setProvinceName(provinceNameByCode);
address.setCityName(cityNameByCode);
address.setAreaName(areaNameByCode);
@Service
public class AddressServiceImpl implements IAddressService {

    @Autowired
    private AddressMapper addressMapper;

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

    @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("用户收货地址超出上限");
        }

        String provinceNameByCode = districtService.getNameByCode(address.getProvinceCode());
        String cityNameByCode = districtService.getNameByCode(address.getCityCode());
        String areaNameByCode = districtService.getNameByCode(address.getAreaCode());
        address.setProvinceName(provinceNameByCode);
        address.setCityName(cityNameByCode);
        address.setAreaName(areaNameByCode);

        //设置uid isdefault
        address.setUid(uid);
        Integer isdefault = count == 0 ? 1 : 0;//1表示默认,0表示不是默认
        address.setIsDefault(isdefault);
        //补全日志
        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("插入用户的收货地址产生未知异常");
        }
    }
}

4 获取省市区-前端界面

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

2.编写相关事件代码