该系列博客主要记录笔者的开发过程,参考B站系列视频:【SpringBoot项目实战完整版】SpringBoot+MyBatis+MySQL电脑商城项目实战_哔哩哔哩_bilibili
所用的一些版本信息:
IDEA开发、JDK1.8版本以上、maven3.61版本以上,springboot,DataGrip管理数据库
商品页面(热销排行):
商品详情页面:
0 前置准备
0.1 创建表
创建商品表t_product 与t_product_category表
构建的语句文档可以在我的github上找到:
store_java_springboot/t_product.sql at main · LYJAntelope/store_java_springboot · GitHub
CREATE TABLE t_product (
id int(20) NOT NULL COMMENT '商品id',
category_id int(20) DEFAULT NULL COMMENT '分类id',
item_type varchar(100) DEFAULT NULL COMMENT '商品系列',
title varchar(100) DEFAULT NULL COMMENT '商品标题',
sell_point varchar(150) DEFAULT NULL COMMENT '商品卖点',
price bigint(20) DEFAULT NULL COMMENT '商品单价',
num int(10) DEFAULT NULL COMMENT '库存数量',
image varchar(500) DEFAULT NULL COMMENT '图片路径',
status int(1) DEFAULT '1' COMMENT '商品状态 1:上架 2:下架 3:删除',
priority int(10) DEFAULT NULL COMMENT '显示优先级',
created_time datetime DEFAULT NULL COMMENT '创建时间',
modified_time datetime DEFAULT NULL COMMENT '最后修改时间',
created_user varchar(50) DEFAULT NULL COMMENT '创建人',
modified_user varchar(50) DEFAULT NULL COMMENT '最后修改人',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE t_product_category (
id int(20) NOT NULL COMMENT '主键',
parent_id bigint(20) DEFAULT NULL COMMENT '父分类id',
name varchar(150) DEFAULT NULL COMMENT '名称',
status int(1) DEFAULT '1' COMMENT '状态 1:正常 0:删除',
sort_order int(4) DEFAULT NULL COMMENT '排序号',
is_parent int(1) DEFAULT NULL COMMENT '是否是父分类 1:是 0:否',
created_time datetime DEFAULT NULL COMMENT '创建时间',
modified_time datetime DEFAULT NULL COMMENT '最后修改时间',
created_user varchar(50) DEFAULT NULL COMMENT '创建人',
modified_user varchar(50) DEFAULT NULL COMMENT '最后修改人',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
0.2 创建实体类
创建:store\src\main\java\com\cy\store\entity\Product.java
package com.cy.store.entity;
import java.io.Serializable;
/** 商品数据的实体类 */
public class Product extends BaseEntity implements Serializable {
private Integer id;
private Integer categoryId;
private String itemType;
private String title;
private String sellPoint;
private Long price;
private Integer num;
private String image;
private Integer status;
private Integer priority;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getCategoryId() {
return categoryId;
}
public void setCategoryId(Integer categoryId) {
this.categoryId = categoryId;
}
public String getItemType() {
return itemType;
}
public void setItemType(String itemType) {
this.itemType = itemType;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getSellPoint() {
return sellPoint;
}
public void setSellPoint(String sellPoint) {
this.sellPoint = sellPoint;
}
public Long getPrice() {
return price;
}
public void setPrice(Long price) {
this.price = price;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Integer getPriority() {
return priority;
}
public void setPriority(Integer priority) {
this.priority = priority;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Product)) return false;
Product product = (Product) o;
if (getId() != null ? !getId().equals(product.getId()) : product.getId() != null) return false;
if (getCategoryId() != null ? !getCategoryId().equals(product.getCategoryId()) : product.getCategoryId() != null)
return false;
if (getItemType() != null ? !getItemType().equals(product.getItemType()) : product.getItemType() != null)
return false;
if (getTitle() != null ? !getTitle().equals(product.getTitle()) : product.getTitle() != null) return false;
if (getSellPoint() != null ? !getSellPoint().equals(product.getSellPoint()) : product.getSellPoint() != null)
return false;
if (getPrice() != null ? !getPrice().equals(product.getPrice()) : product.getPrice() != null) return false;
if (getNum() != null ? !getNum().equals(product.getNum()) : product.getNum() != null) return false;
if (getImage() != null ? !getImage().equals(product.getImage()) : product.getImage() != null) return false;
if (getStatus() != null ? !getStatus().equals(product.getStatus()) : product.getStatus() != null) return false;
return getPriority() != null ? getPriority().equals(product.getPriority()) : product.getPriority() == null;
}
@Override
public int hashCode() {
int result = getId() != null ? getId().hashCode() : 0;
result = 31 * result + (getCategoryId() != null ? getCategoryId().hashCode() : 0);
result = 31 * result + (getItemType() != null ? getItemType().hashCode() : 0);
result = 31 * result + (getTitle() != null ? getTitle().hashCode() : 0);
result = 31 * result + (getSellPoint() != null ? getSellPoint().hashCode() : 0);
result = 31 * result + (getPrice() != null ? getPrice().hashCode() : 0);
result = 31 * result + (getNum() != null ? getNum().hashCode() : 0);
result = 31 * result + (getImage() != null ? getImage().hashCode() : 0);
result = 31 * result + (getStatus() != null ? getStatus().hashCode() : 0);
result = 31 * result + (getPriority() != null ? getPriority().hashCode() : 0);
return result;
}
@Override
public String toString() {
return "Product{" +
"id=" + id +
", categoryId=" + categoryId +
", itemType='" + itemType + '\'' +
", title='" + title + '\'' +
", sellPoint='" + sellPoint + '\'' +
", price=" + price +
", num=" + num +
", image='" + image + '\'' +
", status=" + status +
", priority=" + priority +
"} " + super.toString();
}
}
1 持久层
1.1 规划SQL语句
1 查询热销商品 select
2 根据商品id查询商品
1.2 接口与方法
新建:store\src\main\java\com\cy\store\mapper\ProducterMapper.java
package com.cy.store.mapper;
import com.cy.store.entity.Product;
import java.util.List;
/** 商品mapper接口 */
public interface ProducterMapper {
/**
* 查询热销商品的前四名
* @return 热销商品前四名的集合
*/
List<Product> findHotList();
/**
* 根据商品id查询商品详情
* @param id 商品id
* @return 匹配的商品详情,如果没有匹配的数据则返回null
*/
Product findById(Integer id);
}
1.3 SQL映射
新建:store\src\main\resources\mapper\ProducterMapper.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需要映射到java中的接口,指定路径-->
<mapper namespace="com.cy.store.mapper.ProducterMapper">
<resultMap id="ProductEntityMap" type="com.cy.store.entity.Product">
<id column="id" property="id"/>
<result column="category_id" property="categoryId"/>
<result column="item_type" property="itemType"/>
<result column="sell_point" property="sellPoint"/>
<result column="created_user" property="createdUser"/>
<result column="created_time" property="createdTime"/>
<result column="modified_user" property="modifiedUser"/>
<result column="modified_time" property="modifiedTime"/>
</resultMap>
<!-- 查询热销商品的前四名:List<Product> findHostList() -->
<select id="findHotList" resultMap="ProductEntityMap">
SELECT
*
FROM
t_product
WHERE
status=1
ORDER BY
priority DESC
LIMIT 0,4
</select>
<!-- 根据商品id查询商品详情:Product findById(Integer id) -->
<select id="findById" resultMap="ProductEntityMap">
SELECT
*
FROM
t_product
WHERE
id=#{id}
</select>
</mapper>
2 业务层
2.1 规划异常
存在商品找不到的异常 ProductNotFound
2.2 接口与抽象方法
新建:store\src\main\java\com\cy\store\service\IProductService.java
package com.cy.store.service;
import com.cy.store.entity.Product;
import java.util.List;
/** 处理商品数据的业务层接口 */
public interface IProductService {
/**
* 查询热销商品的前四名
* @return 热销商品前四名的集合
*/
List<Product> findHotList();
/**
* 根据商品id查询商品详情
* @param id 商品id
* @return 匹配的商品详情,如果没有匹配的数据则返回null
*/
Product findById(Integer id);
}
2.3 抽象方法实现
新建:store\src\main\java\com\cy\store\service\impl\ProductServiceImpl.java
package com.cy.store.service.impl;
import com.cy.store.entity.Product;
import com.cy.store.mapper.ProductMapper;
import com.cy.store.service.IProductService;
import com.cy.store.service.ex.ProductNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/** 处理商品数据的业务层实现类 */
@Service
public class ProductServiceImpl implements IProductService {
@Resource
private ProductMapper productMapper;
@Override
public List<Product> findHotList() {
List<Product> list = productMapper.findHotList();
for (Product product : list) {
product.setPriority(null);
product.setCreatedUser(null);
product.setCreatedTime(null);
product.setModifiedUser(null);
product.setModifiedTime(null);
}
return list;
}
@Override
public Product findById(Integer id) {
// 根据参数id调用私有方法执行查询,获取商品数据
Product product = productMapper.findById(id);
// 判断查询结果是否为null
if (product == null) {
// 是:抛出ProductNotFoundException
throw new ProductNotFoundException("尝试访问的商品数据不存在");
}
// 将查询结果中的部分属性设置为null
product.setPriority(null);
product.setCreatedUser(null);
product.setCreatedTime(null);
product.setModifiedUser(null);
product.setModifiedTime(null);
// 返回查询结果
return product;
}
}
3 控制层
3.1 处理异常
在store\src\main\java\com\cy\store\controller\BaseController.java中处理异常
3.2 设计请求
查询热销商品列表:
URL:/products/hot_list
请求方式:GET
请求参数:无
返回值:JsonResult<List<Product>>
查询某个商品的详情:
URL:/{id}/details
请求方式:GET
请求参数:@PathVariable("id") Integer id
返回值:JsonResult<Product>
我们需要将上面两个请求添加到白名单中。
在store\src\main\java\com\cy\store\config\LoginInterceptorConfigurer.java中进行配置
3.3 处理请求
新建:store\src\main\java\com\cy\store\controller\ProductController.java
package com.cy.store.controller;
import com.cy.store.entity.Product;
import com.cy.store.service.IProductService;
import com.cy.store.util.JsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping("products")
public class ProductController extends BaseController {
@Resource
private IProductService productService;
@RequestMapping("hot_list")
public JsonResult<List<Product>> getHotList() {
List<Product> data = productService.findHotList();
return new JsonResult<List<Product>>(OK, data);
}
@GetMapping("{id}/details")
public JsonResult<Product> getById(@PathVariable("id") Integer id) {
// 调用业务对象执行获取数据
Product data = productService.findById(id);
// 返回成功和数据
return new JsonResult<Product>(OK, data);
}
}
4 前端页面
store\src\main\resources\static\web\index.html中添加:
<script type="text/javascript">
$(document).ready(function() {
showHotList();
});
function showHotList() {
$("#hot-list").empty();
$.ajax({
url: "/products/hot_list",
type: "GET",
dataType: "JSON",
success: function(json) {
let list = json.data;
console.log("count=" + list.length);
for (let i = 0; i < list.length; i++) {
console.log(list[i].title);
let html = '<div class="col-md-12">'
+ '<div class="col-md-7 text-row-2"><a href="product.html?id=#{id}">#{title}</a></div>'
+ '<div class="col-md-2">¥#{price}</div>'
+ '<div class="col-md-3"><img src="..#{image}collect.png" class="img-responsive" /></div>'
+ '</div>';
html = html.replace(/#{id}/g, list[i].id);
html = html.replace(/#{title}/g, list[i].title);
html = html.replace(/#{price}/g, list[i].price);
html = html.replace(/#{image}/g, list[i].image);
$("#hot-list").append(html);
}
}
});
}
</script>
store\src\main\resources\static\web\product.html中添加:
<script type="text/javascript">
let id = $.getUrlParam("id");
console.log("id=" + id);
$(document).ready(function() {
$.ajax({
url: "/products/" + id + "/details",
type: "GET",
dataType: "JSON",
success: function(json) {
if (json.state == 200) {
console.log("title=" + json.data.title);
$("#product-title").html(json.data.title);
$("#product-sell-point").html(json.data.sellPoint);
$("#product-price").html(json.data.price);
for (let i = 1; i <= 5; i++) {
$("#product-image-" + i + "-big").attr("src", ".." + json.data.image + i + "_big.png");
$("#product-image-" + i).attr("src", ".." + json.data.image + i + ".jpg");
}
} else if (json.state == 4006) { // 商品数据不存在的异常
location.href = "index.html";
} else {
alert("获取商品信息失败!" + json.message);
}
}
});
});
$("#btn-add-to-cart").click(function() {
$.ajax({
url: "/carts/add_to_cart",
type: "POST",
data: {
"pid": id,
"amount": $("#num").val()
},
dataType: "JSON",
success: function(json) {
if (json.state == 200) {
alert("增加成功!");
} else {
alert("增加失败!" + json.message);
}
},
error: function(xhr) {
alert("您的登录信息已经过期,请重新登录!HTTP响应码:" + xhr.status);
location.href = "login.html";
}
});
});
</script>
5 测试
结合前端页面进行测试