首先我们先来看看商品增加的思路,我们前台显示的话是这样的
首先获取商品类型,让其自动显示在这个页面,然后特别的还有需要传入详情图片,详情图片是多个,
但是我们之前已经完成了通过一个店铺的信息得到该店铺所有商品的列表,我们在js中直接调用之前的方返回的值交给前端处理即可
这里我们只需要编辑加入新商品的功能。
梳理思路:我们需要给product存入所有添加的属性并且通过权值来排序
新建product的层文件。
INSERT INTO
tb_product(product_name,product_desc,img_addr,normal_price,promotion_price,priority,create_time,
last_edit_time,enable_status,product_category_id,shop_id)
VALUES(
#{productName},#{productDesc},#{imgAddr},#{normalPrice},#{promotionPrice},#{priority},#{createTime}
,#{lastEditTime},#{enableStatus},#{productCategory.productCategoryId},#{shop.shopId}
)
package storepro.dto;
import java.io.InputStream;
/*
* 如果每次都是传入文件流和名字,那么我们要传入的参数太多,我们对文件流和名字进行封装,
* 我们要对所有需要传入文件流和文件名称的方法赋值
* */
public class ImageHolder {
private String imageName;
private InputStream image;
public ImageHolder(String imageName,InputStream image){
this.imageName=imageName;
this.image=image;
}
public String getImageName() {
return imageName;
}
public void setImageName(String imageName) {
this.imageName = imageName;
}
public InputStream getImage() {
return image;
}
public void setImage(InputStream image) {
this.image = image;
}
}
我们插入了除了product外的所有属性,当插入语句时我们传入了一个对象参数,这个对象参数里除了productId其他全有了,因为id为自增属性,不能我们设置,通过useGeneratedKeys="true" keyColumn="product_id" keyProperty="productId
这三个语句完成给传入的对象的相应属性进行赋值。
public interface ProductService {
//添加商品信息以及图片处理(这里图片当上传缩略图时上传单个图片,其他图片可以单个或多个所以用list传入)
// ImageHolder类型为缩略图
// List 为详情图,就是多个的
ProductExecution addProduct(Product product, ImageHolder imageHolder, List imageHolderList) throws ProductOperationException;
}
注意解释下这里,我们的service因为要接受图片,所以接受的参数有文件输入流,和文件名称,但是我们product方法还需要传入商品的缩略图和多个图片的详情图。
所以这样下来我们需要有五个参数,第一个prodcut,第二个和第三个分别是缩略图(单个)的名字和文件流,第四个和第五个分别是图片详情的名字和缩略图,而且是List类型的如图:
ProductExecution addProduct(Product product, InputStream prodImgIns, String prodImgName, List prodImgDetailInsList, List prodImgDetailNameList) throws ProductOperationException;
那么我们想到一个办法,把文件的输入流和名字封装起来这样就变成了三个参数
package storepro.dto;
import java.io.InputStream;
/*
* 如果每次都是传入文件流和名字,那么我们要传入的参数太多,我们对文件流和名字进行封装,
* 我们要对所有需要传入文件流和文件名称的方法赋值
* */
public class ImageHolder {
private String imageName;
private InputStream image;
public ImageHolder(String imageName,InputStream image){
this.imageName=imageName;
this.image=image;
}
public String getImageName() {
return imageName;
}
public void setImageName(String imageName) {
this.imageName = imageName;
}
public InputStream getImage() {
return image;
}
public void setImage(InputStream image) {
this.image = image;
}
}
我们在这个方法里这是封装了商品名称和商品文件流两个字段
package storepro.dto;
import storepro.entity.Product;
import storepro.entity.ProductCategory;
import storepro.enums.ProductCategoryStateEnum;
import storepro.enums.ProductStateEnum;
import java.util.List;
/**
* @ClassName: ProductExecution
* @Description: 操作Product是service的返回对象
*/
public class ProductExecution {
/**
* 操作返回的状态信息
*/
private int state;
/**
* 操作返回的状态信息描述
*/
private String stateInfo;
/**
* 操作成功的总量
*/
private int count;
/**
* 批量操作(查询商品列表)返回的Product集合
*/
private List productList;
/**
* 增删改的操作返回的商品信息
*/
private Product product;
/**
* @Title:ProductExecution
* @Description:默认构造函数
*/
public ProductExecution() {
}
/**
* @param productStateEnum
* @param productList
* @Title:ProductExecution
* @Description:批量操作成功的时候返回的ProductExecution
*/
public ProductExecution(ProductStateEnum productStateEnum, List productList, int count) {
this.state = productStateEnum.getState();
this.stateInfo = productStateEnum.getStateInfo();
this.productList = productList;
this.count = count;
}
/**
* @param productStateEnum
* @param product
* @Title:ProductExecution
* @Description:单个操作成功时返回的ProductExecution
*/
public ProductExecution(ProductStateEnum productStateEnum, Product product) {
this.state = productStateEnum.getState();
this.stateInfo = productStateEnum.getStateInfo();
this.product = product;
}
/**
* @param productStateEnum
* @Title:ProductExecution
* @Description:操作失败的时候返回的ProductExecution,仅返回状态信息即可
*/
public ProductExecution(ProductStateEnum productStateEnum) {
this.state = productStateEnum.getState();
this.stateInfo = productStateEnum.getStateInfo();
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public String getStateInfo() {
return stateInfo;
}
public void setStateInfo(String stateInfo) {
this.stateInfo = stateInfo;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public List getProductList() {
return productList;
}
public void setProductList(List productList) {
this.productList = productList;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
}
package storepro.enums;
/**
*
*
* @ClassName: ProductStateEnum
*
* @Description: 使用枚举表述常量数据字典
*/
public enum ProductStateEnum {
SUCCESS(1, "操作成功"), INNER_ERROR(-1001, "操作失败"), NULL_PARAMETER(-1002, "缺少参数"),
EMPTY(-1003,"商品为空")
;
private int state;
private String stateInfo;
/**
*
*
* @Title:ProductStateEnum
*
* @Description:私有构造函数,禁止外部初始化改变定义的常量
*
* @param state
* @param stateInfo
*/
private ProductStateEnum(int state, String stateInfo) {
this.state = state;
this.stateInfo = stateInfo;
}
/**
*
*
* @Title: getState
*
* @Description: 仅设置get方法,禁用set
*
* @return
*
* @return: int
*/
public int getState() {
return state;
}
public String getStateInfo() {
return stateInfo;
}
/**
*
*
* @Title: stateOf
*
* @Description: 定义换成pulic static 暴漏给外部,通过state获取ShopStateEnum
*
* values()获取全部的enum常量
*
* @param state
*
* @return: ShopStateEnum
*/
public static ProductStateEnum stateOf(int state) {
for (ProductStateEnum stateEnum : values()) {
if(stateEnum.getState() == state){
return stateEnum;
}
}
return null;
}
}
里面的构造函数分别有list型和单个对象型并且有状态信息等满足我们的所有要求
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductDao productDao;
@Autowired
private ProductImgDao productImgDao;
@Override
/*
* 首先我们获得前台传来的数据
* 1.获取缩略图,对缩略图进行存储,存储到product的shop_img里
* 2.将信息存入tb_product中,获取product_id
* 3.通过product获取product_id将商铺详情图处理后写入tb_produc_img中
* */
@Transactional//事务处理,因为很多步,所以要事务处理
public ProductExecution addProduct(Product product, ImageHolder imageHolder, List imageHolderList) throws ProductOperationException {
if (product != null && product.getShop() != null && product.getShop().getShopId() != null) {//如果传入的信息有效
//设置默认的属性
product.setCreateTime(new Date());
product.setLastEditTime(new Date());
product.setEnableStatus(1);//1表示商品可以显示在前端
if (imageHolder != null) {//如果商品缩略图不为空,添加到指定目录
addProductImg(product, imageHolder);//写入商品缩略图,并将缩略图的地址交给product
}
try {
int effectnum = productDao.insertProduct(product); //将product信息写入
if (effectnum <= 0) {
throw new ShopOperationException("商品创建失败");
}
} catch(Exception e) {
throw new ProductOperationException("创建商品失败"+e.toString());
}
//如果商品添加成功,我们要继续处理详情图的内容
if (imageHolderList!=null&&imageHolderList.size()>0){
addProductImgList(product,imageHolderList);//将商品详情图存入,并且防止
}
return new ProductExecution(ProductStateEnum.SUCCESS,product);
}else{
return new ProductExecution(ProductStateEnum.INNER_ERROR);
}
}
private void addProductImg(Product product, ImageHolder imageHolder) {//存入图片的方法
// 根据shopId获取图片存储的相对路径
String relativePath = PathUtil.getShopImagePath(product.getShop().getShopId());
// 添加图片到指定的目录
String relativeAddr = ImageUtil.generateThumnail(imageHolder, relativePath);
// 将relativeAddr设置给product
product.setImgAddr(relativeAddr);
}
/**
* 批量添加商品图片
*
* @param product
* @param productImgHolderList
*/
private void addProductImgList(Product product, List productImgHolderList) {
// 获取图片存储路径,这里直接存到相应店铺的文件夹下
String desc = PathUtil.getShopImagePath(product.getShop().getShopId());
List productImgList = new ArrayList<>();
// 遍历图片依次去处理,并添加进 productImg 实体类中
for (ImageHolder productImageHolder : productImgHolderList) {
String imgAddr = ImageUtil.generateNormalImg(productImageHolder, desc);
ProductImg productImg = new ProductImg();
productImg.setImgAddr(imgAddr);
productImg.setProductId(product.getProductId());
productImg.setCreateTime(new Date());
productImgList.add(productImg);
}
// 如果确定是有图片需要添加的,就执行批量添加操作
if (productImgList.size() > 0) {
try {
int effectedNum = productImgDao.batchInsertProductImg(productImgList);
if (effectedNum <= 0) {
throw new ProductOperationException("创建商品详情图片失败!");
}
} catch (Exception e) {
throw new ProductOperationException("创建商品详情图片失败," + e.toString());
}
}
}
}
public static String generateNormalImg(ImageHolder thumbnail, String targetAddr) {
// 获取不重复的随机名
String realFileName = getRandomFileName();
// 获取文件的扩展名,如:png,jpg等
String extension = getFileExtension(thumbnail.getImageName());
// 如果目标路径不存在,则自动创建
makeDidPath(targetAddr);
// 获取文件要保存的目标路径
String relativeAddr = targetAddr + realFileName + extension;
// 获取文件要保存到的目录路径
File dest = new File(PathUtil.getImgBasePath() + relativeAddr);
// 调用 Thumbnails 生成带有水印的图片
try {
Thumbnails.of(thumbnail.getImage())
.size(337, 640)
.watermark(Positions.BOTTOM_RIGHT,
ImageIO.read(new File(basePath + "qq.jpg")), 0.25f)
.outputQuality(0.f)
.toFile(dest);
} catch (IOException e) {
throw new RuntimeException("创建缩略图失败," + e.toString());
}
// 返回图片相对路径地址
return relativeAddr;
}
private static void makeDidPath(String targetAddr) {//创建目标路径所涉及到的目录
String realFileParentPath=PathUtil.getImgBasePath()+targetAddr;//获得文件要存储绝对路径
File dirPath=new File(realFileParentPath);
if (!dirPath.exists()){//不存在路径就创建出来
dirPath.mkdirs();//创建路径
}
}
所有操作结束后我们给controller返回一个product的dto对象
由于处理的是多个图片,所以接受list
INSERT INTO
tb_product_img(img_addr,img_desc,priority,create_time,product_id)
VALUES
(#{productImg.imgAddr},#{productImg.imgDesc},#{productImg.priority},
#{productImg.createTime},#{productImg.productId}
)
注意:我们传入的参数是foreach,所以要完成用foreach动态语句,这个方法便完成了,并不需要service以上的操作
private static final int IMAGEMAXCOUNT=6;
@RequestMapping(value = "/addproduct")
@ResponseBody
private Map addProduct(HttpServletRequest request) {
Map modelMap = new HashMap();
//验证码校验
if (!CodeUtil.checkVerifyCode(request)) {//当验证码错误时
modelMap.put("success", false);
modelMap.put("errMsg", "输入了错误的验证码");
return modelMap;
}
//接受前端传来的参数
ObjectMapper mapper = new ObjectMapper();
Product product = null;
String productStr = HttpServletRequestUtil.getString(request, "productStr");
MultipartHttpServletRequest multipartRequest = null;//用来获取request文件流的形式
ImageHolder thumbnail = null;//存储缩略图
List productImgList = new ArrayList();//存储详情图的集合
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext());//从request中获取文件流
try {
// 若请求中存在文件流,则取出相关的文件(包括缩略图和详情图)
if (multipartResolver.isMultipart(request)) {
multipartRequest = (MultipartHttpServletRequest) request;
//取出文件流构建缩略图
CommonsMultipartFile thumbnailFile = (CommonsMultipartFile) multipartRequest.getFile("thumbnail");
thumbnail = new ImageHolder(thumbnailFile.getOriginalFilename(), thumbnailFile.getInputStream());
//去除缩略图,最多支撑6个文件的上传
for (int i = 0; i < IMAGEMAXCOUNT; i++) {
CommonsMultipartFile thumbnailFileList = (CommonsMultipartFile) multipartRequest.getFile("productImg" + i);//从requst获得想要的数据
if (thumbnailFileList != null) {
//若去取出图片的第i个详情图片文件流不为空,则将其加入详情列表
ImageHolder productImg = new ImageHolder(thumbnailFileList.getOriginalFilename(), thumbnailFileList.getInputStream());
productImgList.add(productImg);//加入文件
} else {
//若取出第i个图片详情流为空,则终止循环
break;
}
}
} else {
//没有文件流上传
modelMap.put("success", false);
modelMap.put("errMsg", "上传文件不能为空");
return modelMap;
}
} catch (IOException e) {
modelMap.put("success", false);
modelMap.put("errMsg", e.toString());
return modelMap;
}
try {
//存入shp信息
//先从前端获得传来的数据
product = mapper.readValue(productStr, Product.class);
} catch (Exception e) {
modelMap.put("success", false);
modelMap.put("errMsg", e.toString());
return modelMap;
}
//若product里的重要信息不为空,且前面处理的缩略图和商品图不为空,我们开始添加product
if (product != null && thumbnail != null && productImgList.size() > 0) {
try {
//从session中获取shop_id
Shop current = (Shop) request.getSession().getAttribute("currentShop");//根据shop信息获取shop内容(这的信息是我们后台在选好店铺时设置的)
Shop shop = new Shop();
shop.setShopId(current.getShopId());
product.setShop(shop);
//执行添加操作
ProductExecution pe = productService.addProduct(product, thumbnail, productImgList);//加入数据库
if (pe.getState() == ProductStateEnum.SUCCESS.getState()) {
modelMap.put("success", true);
} else {
modelMap.put("success", false);
modelMap.put("errMsg", pe.getStateInfo());
}
} catch (RuntimeException e) {
modelMap.put("success", false);
modelMap.put("errMsg", e.toString());
return modelMap;
}
} else {
modelMap.put("success", false);
modelMap.put("errMsg", "请输入商品信息");
}
return modelMap;
}
}
转自:https://blog.csdn.net/yangshangwei/article/details/80863053
在完成了 实战SSM_O2O商铺_30【商品】商品添加之Controller层的实现之后,我们继续来实现View层的代码部分。
商品添加和商品编辑使用的是同一个页面,所以需要根据请求的url来判断是编辑还是新增。
按照页面原型和数据模型,商品添加页面需要加载该shopId对应的productCategory。 这个功能前面已经开发好了,直接调用即可。
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>商品操作title>
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
<link rel="shortcut icon" href="/favicon.ico">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="stylesheet"
href="//g.alicdn.com/msui/sm/0.6.2/css/sm.min.css">
<link rel="stylesheet"
href="//g.alicdn.com/msui/sm/0.6.2/css/sm-extend.min.css">
<link rel="stylesheet" href="../resources/css/shop/productmanage.css">
head>
<body>
<header class="bar bar-nav">
<h1 class="title">商品操作h1>
header>
<div class="content">
<div class="list-block">
<ul>
<li>
<div class="item-content">
<div class="item-media">
<i class="icon icon-form-name">i>
div>
<div class="item-inner">
<div class="item-title label">商品名称div>
<div class="item-input">
<input type="text" id="product-name" placeholder="商品名称">
div>
div>
div>
li>
<li>
<div class="item-content">
<div class="item-media">
<i class="icon icon-form-email">i>
div>
<div class="item-inner">
<div class="item-title label">目录div>
<div class="item-input">
<select id="product-category">
select>
div>
div>
div>
li>
<li>
<div class="item-content">
<div class="item-media">
<i class="icon icon-form-email">i>
div>
<div class="item-inner">
<div class="item-title label">优先级div>
<div class="item-input">
<input type="number" id="priority" placeholder="数字越大越排前面">
div>
div>
div>
li>
<li>
<div class="item-content">
<div class="item-media">
<i class="icon icon-form-email">i>
div>
<div class="item-inner">
<div class="item-title label">原价div>
<div class="item-input">
<input type="number" id="normal-price" placeholder="可选">
div>
div>
div>
li>
<li>
<div class="item-content">
<div class="item-media">
<i class="icon icon-form-email">i>
div>
<div class="item-inner">
<div class="item-title label">现价div>
<div class="item-input">
<input type="number" id="promotion-price" placeholder="可选">
div>
div>
div>
li>
<li>
<div class="item-content">
<div class="item-media">
<i class="icon icon-form-email">i>
div>
<div class="item-inner">
<div class="item-title label">缩略图div>
<div class="item-input">
<input type="file" id="small-img">
div>
div>
div>
li>
<li>
<div class="item-content">
<div class="item-media">
<i class="icon icon-form-email">i>
div>
<div class="item-inner detail-img-div">
<div class="item-title label">详情图片div>
<div class="item-input" id="detail-img">
<input type="file" class="detail-img">
div>
div>
div>
li>
<li>
<div class="item-content">
<div class="item-media">
<i class="icon icon-form-email">i>
div>
<div class="item-inner">
<div class="item-title label">商品描述div>
<div class="item-input">
<textarea id="product-desc" placeholder="商品描述">textarea>
div>
div>
div>
li>
<li>
<div class="item-content">
<div class="item-media">
<i class="icon icon-form-email">i>
div>
<div class="item-inner">
<label for="j_captcha" class="item-title label">验证码label> <input
id="j_captcha" name="j_captcha" type="text"
class="form-control in" placeholder="验证码" />
<div class="item-input">
<img id="captcha_img" alt="点击更换" title="点击更换"
onclick="changeVerifyCode(this)" src="../Kaptcha" />
div>
div>
div>
li>
ul>
div>
<div class="content-block">
<div class="row">
<div class="col-50">
<a href="#"
class="button button-big button-fill button-danger" id="back">返回商品管理a>
div>
<div class="col-50">
<a href="#" class="button button-big button-fill" id="submit">提交a>
div>
div>
div>
div>
<script type='text/javascript' src='//g.alicdn.com/sj/lib/zepto/zepto.min.js' charset='utf-8'>script>
<script type='text/javascript' src='//g.alicdn.com/msui/sm/0.6.2/js/sm.min.js' charset='utf-8'>script>
<script type='text/javascript' src='//g.alicdn.com/msui/sm/0.6.2/js/sm-extend.min.js' charset='utf-8'>script>
<script type='text/javascript' src='../resources/js/common/common.js' charset='utf-8'>script>
<script type='text/javascript' src='../resources/js/shop/productoperation.js' charset='utf-8'>script>
body>
html>
/**
* 因为商品的添加和编辑复用同一个页面,所以需要根据url中的商品Id来判断
*/
$(function(){
//通过url是否含有productId来判断是添加商品还是编辑
var productId = getQueryString('productId');
// 标示符 productId非空则为true即编辑,否则为添加商品
var isEdit = productId ? true : false ;
// 商品添加URL
var addProductURL = '/o2o/shopadmin/addproduct';
// 商品编辑URL TODO
var editProductURL = '';
// 获取商品初始化信息的URL 根据页面原型只需要获取productCategory即可,后台调用之前写好的路由方法即可
var initProductURL = '/o2o/shopadmin/getproductcategorybyshopId';
// 通过标示符,确定调用的方法
if(isEdit){
// 为true,则根据productId调用获取product信息的方法 TODO
getProductInfoById(productId);
}else{
// 为false,则初始化新增product页面
getProductInitInfo();
}
/**
* 始化新增product页面
*
* 根据页面原型和数据模型,需要加载该shop对应的productCategory信息(shop信息从服务端session中获取)
*/
function getProductInitInfo(){
$.getJSON(initProductURL,
function(data){
if(data.success){
// 设置product_category
var productCategoryList = data.data;
var productCategoryTempHtml = '';
productCategoryList.map(function(item, index) {
// productCategoryTempHtml += '
// + '';
productCategoryTempHtml += '
+ item.productCategoryName + '';
});
$('#product-category').html(productCategoryTempHtml);
}else{
$.toast(data.errMsg)
}
});
};
/**
* 点击控件的最后一个且图片数量小于6个的时候,生成一个选择框
*/
$('.detail-img-div').on('change', '.detail-img:last-child', function() {
if ($('.detail-img').length < 6) {
$('#detail-img').append('');
}
});
/**
* 提交按钮的响应时间,分别对商品添加和商品编辑做不同的相应
*/
$('#submit').click(
function(){
// 创建商品Json对象,并从表单对象中获取对应的属性值
var product = {};
// 如果是编辑操作,需要传入productId
if(isEdit){
product.productId = productId;
}
product.productName = $('#product-name').val();
product.productDesc = $('#product-desc').val();
// 获取商品的特定目录值
product.productCategory = {
productCategoryId : $('#product-category').find('option').not(
function() {
return !this.selected;
}).data('value')
};
product.priority = $('#priority').val();
product.normalPrice = $('#normal-price').val();
product.promotionPrice = $('#promotion-price').val();
// 生成表单对象用于接收参数并传递给后台
var formData = new FormData();
// 缩略图 (只有一张),获取缩略图的文件流
var thumbnail = $('#small-img')[0].files[0];
formData.append('thumbnail',thumbnail);
// 图片详情
$('.detail-img').map(
function(index, item) {
// 判断该控件是否已经选择了文件
if ($('.detail-img')[index].files.length > 0) {
// 将第i个文件流赋值给key为productImgi的表单键值对里
formData.append('productImg' + index,
$('.detail-img')[index].files[0]);
}
});
// 将product 转换为json ,添加到forData
formData.append('productStr', JSON.stringify(product));
// 获取表单中的验证码
var verifyCodeActual = $('#j_captcha').val();
if (!verifyCodeActual) {
$.toast('请输入验证码!');
return;
}
formData.append("verifyCodeActual", verifyCodeActual);
// 使用ajax异步提交
$.ajax({
url: isEdit?editProductURL:addProductURL,
type: 'POST' ,
data : formData,
contentType : false,
processData : false,
cache : false,
success: function(){
if (data.success) {
$.toast('提交成功!');
$('#captcha_img').click();
} else {
$.toast('提交失败!');
$('#captcha_img').click();
}
}
});
});
});
.row-product {
border: 1px solid #999;
padding: .5rem;
border-bottom: none;
}
.row-product:last-child {
border-bottom: 1px solid #999;
}
.product-name {
white-space: nowrap;
overflow-x: scroll;
}
.product-wrap a {
margin-right: 1rem;
}