需求分析
任何一个电商系统中都有一个商品子系统,而与商品相关联都会有一个品牌信息管理,在当前业务系统设计中我们就是要对商品品牌信息进行设计和实现。
业务架构
在品牌(Brand)信息管理中就是要实现对商品品牌信息的添加,修改,查询,删除等业务,user是后台管理员,如图所示
原型设计
基于品牌业务描述,对品牌模块的业务原型进行分析和设计,如图所示
技术架构
在品牌管理模块实现郭晨,我们采用典型的C/S架构进行实现,客户端我们基于浏览器实现,服务端采用Tomcat,数据库使用MySQL,具体应用层基于MVC分成架构进行实现
技术栈选型
客户端技术:HTML,CSS,JavaScript,bootstrap
服务端技术:spring,mybatis,springboot,thymeleaf
数据库:MySql,SQL
数据库及表设计
drop database if exists dbbrand;
create database dbbrand default character set utf8;
use dbbrand;
create table tb_brand(
id bigint primary key auto_increment,
name varchar(100) not null,
remark text,
createdTime datetime not null
)engine=InnoDB;
insert into tb_brand values (null,'联想','very good',now());
insert into tb_brand values (null,'小米','very good',now());
insert into tb_brand values (null,'美的','very good',now());
insert into tb_brand values (null,'九阳','very good',now());
insert into tb_brand values (null,'TCL','very good',now());
insert into tb_brand values (null,'创维','very good',now());
insert into tb_brand values (null,'华为','very good',now());
项目环境初始化
准备操作
(1)JDK 1.8
(2)Maven 3.6.3
(3)IDEA 2020.2
(4)MySql 5.7+
初始化数据库
在cmd窗口下进行如下操作
登录mysql
mysql -uroot -proot
设置客户端编码
set names utf8;
执行sql脚本
source:d/brand.sql
创建项目Mouble
添加项目Moudle依赖
org.springframework.boot
spring-boot-starter-data-jdbc
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.3
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-thymeleaf
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
项目Mouble基础配置
打开项目Mouble配置文件applicatin.properties,并添加如下内容
server.port=80
spring.datasource.url=jdbc:mysql:///dbgoods?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
mybatis.mapper-locations=classpath:/mapper/*/*.xml
logging.level.com.cy=debug
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
启动项目并进行初步环境测试分析
品牌模块整体API设计
品牌数据的查询及呈现
业务描述
将数据库中的品牌查询出来,然后在客户端基于Html技术进行呈现,如图所示
服务端查询时序设计
领域对象(POJO)设计及实现
设置Brand对象,基于此对象封装从数据库查询到的品牌信息,代码如下
第一步:定义BrandDao接口,代码如下:
package com.cy.pj.brand.pojo;
import java.util.Date;
public class Brand {
private Integer id;
private String name;
private String logo;
private String remark;
private Date createdTime;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLogo() {
return logo;
}
public void setLogo(String logo) {
this.logo = logo;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public Date getCreatedTime() {
return createdTime;
}
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
@Override
public String toString() {
return "Brand{" +
"id=" + id +
", name='" + name + ''' +
", logo='" + logo + ''' +
", remark='" + remark + ''' +
", createdTime=" + createdTime +
'}';
}
}
数据逻辑对象(DAO)查询方法设计及实现
第一步:定义Brand接口,代码如下:
package com.cy.pj.brand.dao;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public class BrandDao {
}
第二步:在BrandDao中定义品牌查询方法
List findBrands(String name);
第三步:基于查询方法定义SQL映射(本次SQL映射基于注解方法定义),
在resources目录中创建mapper/brand目录,并在目录中添加BrandMapper.xml文件代码如下:
简单SQL语句用注解形式,复杂的SQL语句用xml文件配置
@Select("select * from tb_brand where name like concat('%',#{name},'%')")
List findBrands(String name);
其中concat为mysql中提供的字符串连接函数
当然,对于这个SQL映射也可以写映射文件(BrandMapper.xml)
第四步:对数据层的查询进行单元测试,代码如下:
package com.cy.pj.brand.dao;
import com.cy.pj.brand.pojo.Brand;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class BrandDaoTests {
@Autowired
private BrandDao brandDao;
@Test
public void testFindBrands(){
List list=brandDao.findBrands("TCL");
for (Brand b:list
) {
System.out.println(b);
}
}
}
第五步:测试过程中的BUG分析?
BindingException,如图所示
ExecutorException,如图所示:
SQL异常:检查自己的数据库语句是否正确
测试输出对象为对象地址表现形式,而不是内容?(检查toString方法是否重写)
知识点自己了解内容之/具体索引的定义/
业务逻辑对象(Service)查询方法设计及实现
业务逻辑对象负责模块的具体业务管理,例如参数校验,事务控制,权限控制,日志记录等
第一步:定义业务接口
package com.cy.pj.brand.service;
public interface BrandService {}
第二步:在BrandService接口中添加品牌查询方法
List findBrands(String name);
第三步:定义BrandService接口实现类BrandServiceImpl
package com.cy.pj.brand.service.impl;
import com.cy.pj.brand.dao.BrandDao;
import com.cy.pj.brand.pojo.Brand;
import org.slf4j.Logger; //slf4j-Simple Log Facade For Java
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BrandServiceiImpl implements BrandService {
//这里用到了门面模式--日志
private static final Logger log = //这里的实现在springboot默认选择的是logback
LoggerFactory.getLogger(BrandServiceiImpl.class);
@Autowired
private BrandDao brandDao;
@Override
public List findBrands(String name) {
long t1 = System.currentTimeMillis();
List list = brandDao.findBrands(name);
long t2 = System.currentTimeMillis();
log.info("time:{}", t2 - t1);
return list;
}
}
第四步:定义BrandService接口方法的单元测试类,并对业务进行测试分析
package com.cy.pj.brand.service;
import com.cy.pj.brand.pojo.Brand;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class BrandServiceTests {
@Autowired
private BrandService brandService;
@Test
public void testFindBrands() {
List list = brandService.findBrands("小米");
System.out.println(list.size());
//断言测试
Assertions.assertEquals(1, list.size());
System.out.println("test ok");
// for (Brand b:list){
// System.out.println(b);
// }
// list.forEach(brand->System.out.println(brand));
list.forEach(System.out::println);//jdk1.8方法引用
}
}
测试过程中的Bug分析
NoSuchBeanDefinition,如图所示:
NullPointerException,如图所示:
控制逻辑对象(Controller)查询方法设计及实现
在控制逻辑对象中主要是负责请求和响应逻辑控制,例如请求url映射,参数映射,请求方式,结果集的封装,解析,响应的设计等
第一步:定义Controller类
package com.cy.pj.brand;
import com.cy.pj.brand.pojo.Brand;
import com.cy.pj.brand.service.BrandService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
@Controller
public class controller {
@Autowired
private BrandService brandService;
@GetMapping("doFindBrands")
public String doFindBrands(@PathVariable String name, Model model){
List list=brandService.findBrands(name);
model.addAttribute("list", list);
return "brand/brand";//view name
}
}
访问网址:http:localhost:8080/brand/doFindBrands/tcl
第二步:在Controller添加处理请求的方法
package com.cy.pj.brand;
import com.cy.pj.brand.pojo.Brand;
import com.cy.pj.brand.service.BrandService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Controller
public class controller {
@Autowired
private BrandService brandService;
//http://localhost:8080/brand/doFindBrands?name=tcl
//http:localhost:8080/brand/doFindBrands/tcl //rest风格(一种软件架构编码风格)
//其语法为a/b/{c}/d/{e},在url可以定义变量,这个变量需要使用{}括起来
//rest风格可以更好实现跨平台
//@PathVariable 注解用于修饰方法参数,目的是告诉springmvc,参数的值来自于url
@GetMapping(value={"/brand/doFindBrands","/brand/doFindBrands/{name}"})
public String doFindBrands(@PathVariable(required = false) String name, Model model){
List list=brandService.findBrands(name);
model.addAttribute("list", list);
//第一个brand为目录,第二个目录为view name
return "brand/brand";
}//所有与数据相关的问题,一定要学会去跟踪你数据
//例如:
//1.客户端向服务端提交的数据,在服务端没有收到?(一定要看客户端提交数据的方式与服务端
//2.服务端向客户端响应数据时,假如客户端没有收到?(先检测服务端响应数据之前的数据时
}
其中,
(1)@GetMapping描述方法时,表示这个方法只能处理Get请求,注解内部的value属性可以指定多个url
(2)@PathVariable用于描述方法参数,表示方法参数的值可以来自url中{}内部的变量值,required=false表示参数可以不传值
rest风格(一种软件架构编码风格):其语法为a/b/{c}/d/{e},在url可以定义变量,这个变量需要使用{}括起来,rest风格可以更好实现跨平台
@PathVariable:注解用于修饰方法参数,目的是告诉springmvc,参数的值来自于url
客户端品牌列表页面设计及实现
第一步:在resources目录下新建一个目录,命名为templates.brand,代码如下
注意templates.brand中的.不能写.要用/分割,建好了之后显示.
Title
The Brand Page
id
name
createdTime
10
AAA
2020/10/11
其中,
(1)${}为thymeleaf为中的EL表达式,用于从服务端model中获取数据
(2)th:each为thymeleaf定义的自定义标签属性,用于迭代数据
(3)th:text为thymeleaf定义的自定义标签属性,用于设置文本内容
启动服务进行访问测试并对结果进行分析
启动服务,打开浏览器输入指定url,进行访问,其数据呈现过程,如图所示:
启动及运行过程中的Bug分析及解决方案
500错误:看自己的控制台报错信息
405错误:服务器与客户端请求不匹配--检查表单上方和contrller里的提交方式是否一样
,页面元素解析异常,如图所示:
模板页面找不到,如图所示:
品牌模块删除业务实现分析及实现
业务描述
在品牌列表页面中,点击当前行记录后面的删除按钮,基于当前行的记录执行id执行品牌删除操作,删除成功以后,重新刷新页面,如图所示
业务时序分析与设计
数据逻辑对象(DAO)中删除方法及实现
基于业务,在BrandDao接口中添加删除方法,代码如下:
int deleteById(Integer id);
基于方法定义SQL映射(本次直接以注解方式进行定义),代码如下:
@Delete("delete from tb_brand where id=#{id}")
int deleteById(Integer id);
在BrandTests单元测试类中添加单元测试方法,对删除操作进行测试,代码如下
@Test
public void testDeleteById() {
int rows = brandDao.deleteById(10);
System.out.println("rows=" + rows);
}
业务逻辑对象(Service)中删除方法设计及实现
第一步:在BrandService接口中添加,品牌删除的业务方法,代码如下:
int deleteById(Integer id);
第二步:在BrandServiceImpl类中添加删除业务的具体实现,代码如下:
@Override
public int deleteById(Integer id) {
//1.参数校验
//2.执行删除逻辑业务
int rows = brandDao.deleteById(id);
//3.校验结果
//4.返回结果
return rows;
}
第三步;在BrandServiceTests类中添加单元测试方法,对其删除业务做测试?
@Test
public void testDeleteById() {
int rows = brandService.deleteById(10);
System.out.println("rows=" + rows);
}
第四步:测试过程中的Bug分析?
控制逻辑对象(Controller)中删除方法设计及实现
在控制层对象中定义处理删除请求的方法,具体代码如下:
@GetMapping("/brand/doDeleteById/{id}")
public String doDeleteById(@PathVariable Integer id, Model model) {
brandService.deleteById(id);
List list = brandService.findBrands(null);
model.addAttribute("list", list);
return "/brand/brand";
}
想要优化一下,在用户点击删除的时候提示一下是否要删除,在brand.html中把删除的超链接改成button
代码如下:
在script里添加function方法
function doDeleteById(id) {
if(!confirm("您确定要删除么"))return;
location.href=`http://localhost:8080/brand/doDeleteById/${id}`;
}
Bug分析
400错误
品牌模块修改业务分析及实现
业务描述
在品牌列表页面,点击当前行的修改按钮,先基于id查询当前行记录,并把记录呈现在编辑页面,如图所示
修改时序分析及设计
基于id查询品牌信息并呈现在页面上,其时序分析如图所示
在品牌编辑页面,编辑数据,点击save按钮保存更新,其时序如图所示
数据逻辑对象(Dao)中方法设计及实现
第一步:在BrandDao中添加用于保存品牌信息的方法,代码如下:
@Select("select * from tb_brand where id=#{id}")
Brand findById(Integer id);
在BrandDao中添加基于id执行品牌更新的方法及SQL映射,代码如下:
@Update("update tb_brand set name=#{name},remark=#{remark} where id=#{id}")
int updateById(Brand brand);
业务逻辑对象(Service)中方法设计及实现
在BrandService中添加基于id查询品牌信息和更新品牌信息的方法,代码如下:
Brand findById(Integer id);
int updateById(Brand brand);
在BrandServiceImpl中基于id查询品牌信息的更新品牌信息的方法,代码如下:
@Override
public Brand findById(Integer id) {
//.....
return brandDao.findById(id);
}
@Override
public int updateById(Brand brand) {
int rows = brandDao.updateById(brand);
return rows;
}
控制逻辑对象(Controller)中方法设计及实现
在BrandController中添加基于id查询品牌信息的方法,代码如下:
@RequestMapping("brand/toUpdateById/{id}")
public String doUpdateById(@PathVariable("id") Integer id,Model model) {
Brand brand = brandService.findById(id);
model.addAttribute("brand",brand);
return "brand/brand-update";
}
在BrandController中添加更新品牌信息的方法,代码如下:
@PostMapping("/brand/doUpdateBrand")
public String doUpdateBrand(Brand brand,Model model){
// System.out.println("update.brand"+brand);
brandService.updateById(brand);
List list = brandService.findBrands(null);
model.addAttribute("list", list);
return "/brand/brand";
// return "redirect:/brand/doUpdateBrand";
}
客户端操作设计及实现
Insert title here
The Goods Update Page
启动服务进行访问测试分析
启动程序,先进入品牌列表页面,然后点击修改按钮,此时进入品牌编辑页面,如图所示
在品牌编辑页面,编辑数据以后,点击save按钮,执行更新操作
品牌模块添加业务分析及实现
总结(Summary)
本章节,主要基于学过的springboot,Hikaricp,MyBatis,Spring,Thymeleaf等技术,对商品品牌模块做了具体实现,重点掌握其基本设计及实现过程