Project(8)
1、分析项目
当需要开发某个项目时,首先,应该分析这个项目中,需要处理哪些种类的数据!例如:用户、商品、商品类别、收藏、订单、购物车、收货地址…
然后,将以上这些种类的数据的处理排个顺序,即先处理哪种数据,后处理哪种数据!通常,应该先处理基础数据,再处理所相关的数据,例如需要先处理商品数据,才可以处理订单数据,如果多种数据之间没有明显的关联,则应该先处理简单的,再处理较难的!
则以上这些数据的处理顺序应该是:用户 > 收货地址 > 商品类别 > 商品 > 收藏 > 购物车 > 订单
当确定了数据处理顺序后,就应该分析某个用户对应的功能有哪些,以“用户”数据为例,相关功能有:注册、登录、修改密码、修改资料、上传头像…
然后,还是需要确定以上功能的开发顺序,通常,遵循 “增 > 查 > 删 > 改” 的顺序,则以上功能的开发顺序应该是:注册 > 登录 > 修改密码 > 修改资料 > 上传头像。
每个功能的开发都应该遵循 创建数据表 > 创建实体类 > 持久层 > 业务层 > 控制器层 > 前端页面
一次只解决一个问题
大问题拆成小问题
2、用户 - 注册 - 创建数据表
3、用户 - 注册 - 创建实体类
4、用户 - 注册 - 持久层
a.规划SQL语句
b.接口与抽象方法
c.配置映射
5、用户 - 注册 - 业务层
业务层的基本定位
a.规划异常
b.接口与抽象方法
c.实现类与重写方法
6、用户 - 注册 - 控制器层
a.处理异常
b.设计请求
c.处理请求
7、用户 - 注册 - 前端页面
……
31、省市区数据 - 导入数据
登录 mysql 控制台,通过source 文件路径
导入
32、创建实体类
创建cn.tedu.store.entity.District
实体类,由于字典表的数据不会被修改,所以,表中并没有 4 个日志字段,则实体类中也不需要,则该实体类不需要继承自BaseEntity
,但是,仍需要实现Serializable
接口:
public class District implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String code;
private String name;
private String parent;
// get/set toString hashCode & equals
33、省市区数据 - 获取列表 - 持久层
a.规划SQL语句
在china
中记录了全国所有的省、市、区的数据,如果需要查询全国所有的省,或某个省所有市,或某个市所有区,需要执行的SQL语句是一样的,不过参数不同而已:
select * from china where parent = ?
b.接口与抽象方法
创建cn.tedu.store.mapper.DistrictMapper
接口,然后指定抽象方法:
/**
* 根据父级代号获取 全国所有省、某省所有市、某市所有区 的列表
* @param parent 父级代号
* @return 全国所有省、某省所有市、某市所有区 的列表
*/
List<District> findByParent(String parent);
c.配置映射
复制得到DistrictMapper.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">
<!-- namespace:xml文件对应哪个接口 -->
<mapper namespace="cn.tedu.store.mapper.DistrictMapper">
<!-- 根据父级代号获取 全国所有省、某省所有市、某市所有区 的列表 -->
<!-- List<District> findByParent(String parent); -->
<select id="findByParent" resultType="cn.tedu.store.entity.District">
SELECT
id, code, name, parent
FROM
t_dict_district
WHERE
parent=#{parent}
ORDER BY
id
</select>
</mapper>
注意:如果查询的结果中包含多条数据,一定要显式地指定排序规则,如果没有指定,则实际获取的数据将是没有顺序的(可能绝大部分时候会表现为按照id排序,但事实上是根本没有顺序的!)
编写并执行单元测试:
@SpringBootTest
public class DistrictMapperTests {
@Autowired
private DistrictMapper mapper;
@Test
public void testFindByParent() {
String parent = "0";
List<District> list = mapper.findByParent(parent);
System.err.println("begin:");
for (District district : list) {
System.err.println(district);
}
System.err.println("end");
}
}
34、省市区数据 - 获取列表 - 业务层
a.规划异常
无
b.接口与抽象方法
创建cn.tedu.store.service.IDistrictService
接口,并添加抽象方法:
/**
* 根据父级代号获取 全国所有省、某省所有市、某市所有区 的列表
* @param parent 父级代号
* @return 全国所有省、某省所有市、某市所有区 的列表
*/
List<District> getByParent(String parent);
c.实现类与重写方法
创建cn.tedu.store.service.impl.DistrictServiceImpl
业务层实现类,实现以上接口,添加@Service
注解,并在类中添加持久层接口对象DistrictMapper districtMapper
,并重写以上抽象方法。
复制DistrictMapper
接口中的抽象方法,并私有化实现它:
/**
* 根据父级代号获取 全国所有省、某省所有市、某市所有区 的列表
* @param parent 父级代号
* @return 全国所有省、某省所有市、某市所有区 的列表
*/
private List<District> findByParent(String parent){
return districtMapper.findByParent(parent);
}
然后,重写接口中定义的抽象方法:
/**
* 根据父级代号获取 全国所有省、某省所有市、某市所有区 的列表
* @param parent 父级代号
* @return 全国所有省、某省所有市、某市所有区 的列表
*/
public List<District> getByParent(String parent){
return findByParent(parent);
}
在src/test/java
下创建cn.tedu.store.service.DistrictServiceTests
测试类,编写并执行测试方法:
@SpringBootTest
public class DistrictServiceTests {
@Autowired
private IDistrictService service;
@Test
public void testGetByParent() {
String parent = "0";
List<District> list = service.getByParent(parent);
System.err.println("Begin:");
for(District district : list) {
System.err.println(district);
}
System.err.println("共:" + list.size() + " 条数据");
System.err.println("End.");
}
}
35、省市区数据 - 获取列表 - 控制器层
a.处理异常
无
b.设计请求
请求路径:/districts/
请求参数:String parent
请求类型:get
响应数据:JsonResult<List<District>>
是否拦截:否,不拦截,需要在登录拦截器的配置中添加白名单
/**
* 登录拦截器的配置类
* @author DELL
*
*/
public class LoginInterceptorConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 创建拦截器对象
HandlerInterceptor interceptor = new LoginInterceptor();
// 创建白名单
List<String> excludePaths = new ArrayList<String>();
excludePaths.add("/users/reg");
excludePaths.add("/users/login");
excludePaths.add("/web/register.html");
excludePaths.add("/web/login.html");
excludePaths.add("/bootstraps/**");
excludePaths.add("/css/**");
excludePaths.add("/images/**");
excludePaths.add("/js/**");
excludePaths.add("/districts/"); // ***新增的不拦截路径***
// 注册拦截器类,并设置黑白名单
registry.addInterceptor(interceptor).addPathPatterns("/**").excludePathPatterns(excludePaths);
}
}
c.处理请求
创建cn.tedu.store.controller.DistrictController
控制器类,继承自BaseController
,添加@RestController
注解和@RequestMapping("districts")
注解,在类中声明@Autowired private IDistrictService districtService
业务层对象。
然后再控制器类中,添加处理请求的方法:
@RestController
@RequestMapping("districts")
public class DistrictController extends BaseController {
@Autowired
private IDistrictService districtService;
@GetMapping("/")
public JsonResult<List<District>> getByParent(String parent){
List<District> data = districtService.getByParent(parent);
return new JsonResult<List<District>>(SUCCESS, data);
}
}
完成后,打开浏览器,输入http://localhost:8080/districts/?parent=0
进行测试!
36、省市区数据 - 获取列表 - 前端页面
“省”相关:
<script type="text/javascript">
$(document).ready(function() {
$("#btn-addnew").click(function() {
addAddress();
});
showProvinceList();
});
function showProvinceList(){
$.ajax({
"url" : "/districts/",
"data" : "parent=0",
"type" : "get",
"dataType" : "json",
"success" : function(json) {
var list = json.data;
console.log("count = " + list.length);
for (var i = 0; i < list.length; i++) {
var op = '<option value="' + list[i].code + '">' + list[i].name + '</option>';
$("#province-list").append(op);
}
}
});
}
function addAddress(){
$.ajax({
"url" : "/addresses/addnew",
"data" : $("#form-addnew").serialize(),
"type" : "post",
"dataType" : "json",
"success" : function(json) {
if (json.state == 2000) {
alert("增加新地址成功!");
} else {
alert(json.message);
}
},
"error" : function(){
alert("您的登录信息已过期!请重新登录!");
}
});
}
</script>
“市”相关:
<script type="text/javascript">
$(document).ready(function() {
$("#btn-addnew").click(function() {
addAddress();
});
showProvinceList();
$("#province-list").change(function(){
showCityList();
});
});
function addAddress(){
$.ajax({
"url" : "/addresses/addnew",
"data" : $("#form-addnew").serialize(),
"type" : "post",
"dataType" : "json",
"success" : function(json) {
if (json.state == 2000) {
alert("增加新地址成功!");
} else {
alert(json.message);
}
},
"error" : function(){
alert("您的登录信息已过期!请重新登录!");
}
});
}
function showProvinceList(){
// 自定义“---请选择---”选项,避免页面自动选择 省 却不显示 市 的情况
var defaultOp = '<option value="-1">--- 请选择 ---</option>';
$("#province-list").append(defaultOp);
$.ajax({
"url" : "/districts/",
"data" : "parent=0",
"type" : "get",
"dataType" : "json",
"success" : function(json) {
var list = json.data;
console.log("count = " + list.length);
for (var i = 0; i < list.length; i++) {
var op = '<option value="' + list[i].code + '">' + list[i].name + '</option>';
$("#province-list").append(op);
}
}
});
}
function showCityList(){
// 每次改变 省 时,要先清空 市数据
$("#city-list").empty();
// 获取选择的 省 的代号
var provinceCode = $("#province-list").val();
// 如果为 -1 ,表示选中的是 “---请选择---”,此时直接 return,不再执行后续请求
if(provinceCode == -1){
return;
}
$.ajax({
"url" : "/districts/",
"data" : "parent=" + provinceCode,
"type" : "get",
"dataType" : "json",
"success" : function(json) {
var list = json.data;
console.log("count = " + list.length);
for (var i = 0; i < list.length; i++) {
var op = '<option value="' + list[i].code + '">' + list[i].name + '</option>';
$("#city-list").append(op);
}
}
});
}
</script>
“区”相关:
优化1:
优化2:
总结:
<script type="text/javascript">
$(document).ready(function() {
$("#btn-addnew").click(function() {
addAddress();
});
showProvinceList();
$("#province-list").change(function(){
showCityList();
$("#area-list").empty();
var defaultOp = '<option value="-1">--- 请选择 ---</option>';
$("#area-list").append(defaultOp);
});
$("#city-list").change(function(){
showAreaList();
});
});
function addAddress(){
$.ajax({
"url" : "/addresses/addnew",
"data" : $("#form-addnew").serialize(),
"type" : "post",
"dataType" : "json",
"success" : function(json) {
if (json.state == 2000) {
alert("增加新地址成功!");
} else {
alert(json.message);
}
},
"error" : function(){
alert("您的登录信息已过期!请重新登录!");
}
});
}
function showProvinceList(){
// 自定义“---请选择---”选项,避免页面自动选择 省 却不显示 市 的情况
var defaultOp = '<option value="-1">--- 请选择 ---</option>';
$("#province-list").append(defaultOp);
$("#city-list").append(defaultOp);
$("#area-list").append(defaultOp);
$.ajax({
"url" : "/districts/",
"data" : "parent=0",
"type" : "get",
"dataType" : "json",
"success" : function(json) {
var list = json.data;
console.log("count = " + list.length);
for (var i = 0; i < list.length; i++) {
var op = '<option value="' + list[i].code + '">' + list[i].name + '</option>';
$("#province-list").append(op);
}
}
});
}
function showCityList(){
// 每次改变 省 时,要先清空 市数据
$("#city-list").empty();
// 市数据一清空,就显示 请选择
var defaultOp = '<option value="-1">--- 请选择 ---</option>';
$("#city-list").append(defaultOp);
// 获取选择的 省 的代号
var provinceCode = $("#province-list").val();
// 如果为 -1 ,表示选中的是 “---请选择---”,此时直接 return,不再执行后续请求
if(provinceCode == -1){
return;
}
$.ajax({
"url" : "/districts/",
"data" : "parent=" + provinceCode,
"type" : "get",
"dataType" : "json",
"success" : function(json) {
var list = json.data;
console.log("count = " + list.length);
for (var i = 0; i < list.length; i++) {
var op = '<option value="' + list[i].code + '">' + list[i].name + '</option>';
$("#city-list").append(op);
}
}
});
}
function showAreaList(){
// 每次改变 市 时,要先清空 区数据
$("#area-list").empty();
// 区 数据清空,就显示 “请选择”
var defaultOp = '<option value="-1">--- 请选择 ---</option>';
$("#area-list").append(defaultOp);
// 获取选择的 市 的代号
var cityCode = $("#city-list").val();
// 如果为 -1 ,表示选中的是 “---请选择---”,此时直接 return,不再执行后续请求
if(cityCode == -1){
return;
}
$.ajax({
"url" : "/districts/",
"data" : "parent=" + cityCode,
"type" : "get",
"dataType" : "json",
"success" : function(json) {
var list = json.data;
console.log("count = " + list.length);
for (var i = 0; i < list.length; i++) {
var op = '<option value="' + list[i].code + '">' + list[i].name + '</option>';
$("#area-list").append(op);
}
}
});
}
</script>
37、省市区数据 - 补全数据
补全数据:
一:
在DistrictMapper
接口中添加新的抽象方法District findByCode(String code)
:
/**
* 根据 code 查询地区信息
* @param code 省 / 市 / 区 的代号
* @return 地区信息
*/
District findByCode(String code);
在DistrictMapper.xml
配置文件中配置:
<!-- 根据 code 查询地区信息 -->
<!-- District findByCode(String code) -->
<select id="findByCode" resultType="cn.tedu.store.entity.District">
SELECT
name
FROM
china
WHERE
code=#{code}
</select>
在DistrictMapperTests
中编写并执行单元测试:
/**
* 测试根据 code 查询地区信息
*/
@Test
public void testFindByCode() {
String code = "130000";
District district = districtMapper.findByCode(code);
System.err.println("district = " + district);
}
二:
在IDistrictService
业务层接口中添加新抽象方法:
/**
* 根据 code 查询地区信息
* @param code
* @return 地区信息
*/
District getByCode(String code);
在DistrictServiceImpl
业务层实现类中重写以上添加的方法:
@Override
public District getByCode(String code) {
return findByCode(code);
}
// ......
// ......
/**
* 根据 code 查询地区信息
* @param code 省 / 市 / 区 的代号
* @return 地区信息
*/
private District findByCode(String code) {
return districtMapper.findByCode(code);
}
在DistrictServiceTests
中编写并执行单元测试:
@Test
public void testGetByCode() {
String code = "130000";
District district = service.getByCode(code);
System.err.println("district = " + district);
}
三:
在AddressServiceImpl
中补全数据:province_name、city_name、area_name
。
要补全这三个数据,需要用到 District
类的 code
属性。
/**
* 收货地址的业务层接口实现类
* @author DELL
*
*/
@Service
public class AddressServiceImpl implements IAddressService {
// 一个 Service 实现类可以访问自己的 Mapper、别人的 Service,不能直接访问别人的 Mapper
@Autowired
private AddressMapper addressMapper;
@Autowired
private IDistrictService districtService;
/**
* 新增收货地址
*/
@Override
public void addnew(Integer uid, String username, Address address)
throws AddressCountLimitException, InsertException {
// 根据参数 uid 查询当前用户的收货地址数量
// 判断收货地址数量是否达到上限值 ADDRESS_MAX_COUNT
// 是 -- 抛出收货地址数量超过上限异常
Integer count = addressMapper.countByUid(uid);
if(count >= ADDRESS_MAX_COUNT) {
throw new AddressCountLimitException("增加收货地址失败!收货地址数量已达上限!"
+ "最多允许增加" + ADDRESS_MAX_COUNT + "条收货地址!");
}
// 补全数据 uid
address.setUid(uid);
// TODO 补全数据:province_name、city_name、area_name
District province = districtService.getByCode(address.getProvinceCode());
District city = districtService.getByCode(address.getCityCode());
District area = districtService.getByCode(address.getAreaCode());
if(province == null) {
address.setProvinceCode(null); // 此处,将 未选择 时的值 -1 直接改为 null
}else {
address.setProvinceName(province.getName());
}
if(city == null) {
address.setCityCode(null);
}else {
address.setCityName(city.getName());
}
if(area == null) {
address.setAreaCode(null);
}else {
address.setAreaName(area.getName());
}
// 判断当前用户的收货地址数量是否为0,并决定 is_default 的值
// 补全数据:is_default
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);
// 插入收货地址数据
insert(address);
}
/**
* 新增收货地址
* @param address 地址数据对象
*/
private void insert(Address address) {
Integer rows = addressMapper.insert(address);
if(rows != 1) {
throw new InsertException("增加收货地址失败!出现未知错误,请联系管理员!");
}
}
/**
* 根据用户 uid 查询用户的收货地址数量
* @param uid 用户 uid
* @return 用户的收货地址数量
*/
private Integer countByUid(Integer uid) {
return addressMapper.countByUid(uid);
}
}