前言

找完工作以后感觉一段时间失去了学习的激情,再加上毕业论文的工作,懒散拖沓了好久才开始继续我的项目学习。其实这些内容吧,你说难那是一点也不难,重要的在于处理业务的经验,防患漏洞的经验,以及隐藏在项目背后的协调沟通能力,现在我是跟着老师视频在学,一个人设计全套,没有任何沟通协调问题,一旦到公司以后可能就会大不一样了···

好吧,2018新年开始第一天写一篇学习记录算是不错的开端了~

下面开始正文部分,商品部分分为前台顾客浏览的部分和后台商家管理的部分,这里的接口就按照上一章的来一 一实现,实际商城项目会复杂很多吧,废话少说,上代码,还是以代码中的注释为主要讲解。

商品前后台controller

对了,有部分代码是“几个月”前写的了,需要关注的重点就记不清了···

有一点,关于Integer和int的使用选择,Integer不赋值前是null,int是0,所以有时候要慎用int,不能分辨是不是没有值。

ProductController.java 前台没有实现太多功能,毕竟只是教学用。

package top.winxblast.happymall.controller.portal;import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import top.winxblast.happymall.common.ServerResponse;
import top.winxblast.happymall.service.ProductService;
import top.winxblast.happymall.vo.ProductDetailVo;/*** 前台商品controller** @author winxblast* @create 2017/11/13**/
@Controller
@RequestMapping(value = "/product/")
public class ProductController {@Autowiredprivate ProductService productService;/*** 返回商品详情,与后台不同的是,这里不需要验证管理员权限,而是要验证商品上下架状态* @param productId* @return*/@RequestMapping(value = "detail.do", method = RequestMethod.GET)@ResponseBodypublic ServerResponse<ProductDetailVo> detail(Integer productId) {return productService.getProductDetail(productId);}@RequestMapping(value = "list.do", method = RequestMethod.GET)@ResponseBodypublic ServerResponse<PageInfo> list(@RequestParam(value = "keyword",required = false) String keyword,@RequestParam(value = "categoryId",required = false)Integer categoryId,@RequestParam(value = "pageNum",defaultValue = "1")int pageNum,@RequestParam(value = "pageNum",defaultValue = "10")int pageSize,@RequestParam(value = "orderBy",defaultValue = "10")String orderBy){return productService.getProductByKeywordCategory(keyword, categoryId, pageNum, pageSize, orderBy);}
}

ProductManagerController.java 这里要吐槽一下老师关于各个类、方法的命名,总感觉风格不是很统一···
这里涉及到了文件上传的功能,之前我们用的Restlet Client工具对于这个文件上传的支持并不是很好,可以自己写一下jsp进行测试。

package top.winxblast.happymall.controller.backend;import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import top.winxblast.happymall.common.Const;
import top.winxblast.happymall.common.ResponseCode;
import top.winxblast.happymall.common.ServerResponse;
import top.winxblast.happymall.pojo.Product;
import top.winxblast.happymall.pojo.User;
import top.winxblast.happymall.service.FileService;
import top.winxblast.happymall.service.ProductService;
import top.winxblast.happymall.service.UserService;
import top.winxblast.happymall.util.PropertiesUtil;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Map;/*** 商品后台管理** @author winxblast* @create 2017/11/02**/
@Controller
@RequestMapping(value = "/manage/product")
public class ProductManageController {@Autowiredprivate UserService userService;@Autowiredprivate ProductService productService;@Autowiredprivate FileService fileService;/*** 新增或更新商品* @param session* @param product* @return*/@RequestMapping(value = "save.do", method = RequestMethod.GET)@ResponseBodypublic ServerResponse productSave(HttpSession session, Product product) {User user = (User) session.getAttribute(Const.CURRENT_USER);if(user == null) {return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), "用户未登录,请登录管理员");}if(userService.checkAdminRole(user).isSuccess()) {//填充我们增加产品的业务逻辑return productService.saveOrUpdateProduct(product);} else {return ServerResponse.createByErrorMessage("无权限操作");}}/*** 商品上下架* @param session* @param productId* @param status* @return*/@RequestMapping(value = "set_sale_status.do", method = RequestMethod.GET)@ResponseBodypublic ServerResponse setSaleStatus(HttpSession session, Integer productId, Integer status) {User user = (User) session.getAttribute(Const.CURRENT_USER);if(user == null) {return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), "用户未登录,请登录管理员");}if(userService.checkAdminRole(user).isSuccess()) {//商品上下架业务逻辑return productService.setSaleStatus(productId, status);} else {return ServerResponse.createByErrorMessage("无权限操作");}}/*** 商品详情* @param session* @param productId* @return*/@RequestMapping(value = "detail.do", method = RequestMethod.GET)@ResponseBodypublic ServerResponse getDetail(HttpSession session, Integer productId) {User user = (User) session.getAttribute(Const.CURRENT_USER);if(user == null) {return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), "用户未登录,请登录管理员");}if(userService.checkAdminRole(user).isSuccess()) {//填充业务return productService.manageProductDetail(productId);} else {return ServerResponse.createByErrorMessage("无权限操作");}}/*** 后台商品列表* @param session* @param pageNum* @param pageSize* @return*/@RequestMapping(value = "list.do", method = RequestMethod.GET)@ResponseBodypublic ServerResponse getList(HttpSession session, @RequestParam(value = "pageNum",defaultValue = "1") int pageNum, @RequestParam(value = "pageSize",defaultValue = "10")int pageSize) {User user = (User) session.getAttribute(Const.CURRENT_USER);if(user == null) {return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), "用户未登录,请登录管理员");}if(userService.checkAdminRole(user).isSuccess()) {//填充业务//分页的一些说明,使用pagehelper包辅助,使用AOP技术,监听我们自己的sqlreturn productService.getProductList(pageNum, pageSize);} else {return ServerResponse.createByErrorMessage("无权限操作");}}/*** 通过商品名称和id来获取商品列表* @param session* @param productName* @param productId* @param pageNum* @param pageSize* @return*/@RequestMapping(value = "search.do")@ResponseBodypublic ServerResponse productSearch(HttpSession session, String productName, Integer productId, @RequestParam(value = "pageNum",defaultValue = "1") int pageNum, @RequestParam(value = "pageSize",defaultValue = "10")int pageSize) {User user = (User) session.getAttribute(Const.CURRENT_USER);if(user == null) {return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), "用户未登录,请登录管理员");}if(userService.checkAdminRole(user).isSuccess()) {//填充业务return productService.searchProduct(productName, productId, pageNum, pageSize);} else {return ServerResponse.createByErrorMessage("无权限操作");}}/*** 文件上传* @param session* @param file* @param request* @return*/@RequestMapping(value = "upload.do", method = RequestMethod.POST)@ResponseBodypublic ServerResponse upload(HttpSession session, @RequestParam(value = "upload_file", required = false) MultipartFile file, HttpServletRequest request) {User user = (User) session.getAttribute(Const.CURRENT_USER);if(user == null) {return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), "用户未登录,请登录管理员");}if(userService.checkAdminRole(user).isSuccess()) {//填充业务String path = request.getSession().getServletContext().getRealPath("upload");String targetFileName = fileService.upload(file,path);//根据和前端的约定,需要放回完整的图片地址String url = PropertiesUtil.getProperty("ftp.server.http.prefix") + targetFileName;Map fileMap = Maps.newHashMap();fileMap.put("uri", targetFileName);fileMap.put("url", url);return ServerResponse.createBySuccess(fileMap);} else {return ServerResponse.createByErrorMessage("无权限操作");}}/*** 富文本上传文件(图片),这里是这个意思,这里保存的不是富文本,还是图片,只不过是富文本编辑器来调用这个接口* 把富文本编辑器接收到的图片传到我们的ftp服务器中* @param session* @param file* @param request* @return*/@RequestMapping(value = "richtext_img_upload.do", method = RequestMethod.POST)@ResponseBodypublic Map richtextImgUpload(HttpSession session, @RequestParam(value = "upload_file", required = false) MultipartFile file, HttpServletRequest request, HttpServletResponse response) {Map resultMap = Maps.newHashMap();User user = (User) session.getAttribute(Const.CURRENT_USER);if(user == null) {resultMap.put("success", false);resultMap.put("msg", "用户未登录,请登录管理员");return resultMap;}//富文本中对于返回值有自己的要求,我们使用的simditor,所以按照simditor要求返回//同时simditor还要求修改servletresponse//参考网站http://simditor.tower.im/docs/doc-config.html
//        {//            "success": true/false,
//                "msg": "error message", # optional
//            "file_path": "[real file path]"
//        }if(userService.checkAdminRole(user).isSuccess()) {//填充业务String path = request.getSession().getServletContext().getRealPath("upload");String targetFileName = fileService.upload(file,path);if(StringUtils.isBlank(targetFileName)) {resultMap.put("success", false);resultMap.put("msg", "上传失败");return resultMap;} else {//根据和前端的约定,需要放回完整的图片地址String url = PropertiesUtil.getProperty("ftp.server.http.prefix") + targetFileName;resultMap.put("success", true);resultMap.put("msg", "上传成功");resultMap.put("file_path", url);//这就是修改response,算是和前端的约定response.addHeader("Access-Control-Allow-Headers", "X-File-Name");return resultMap;}} else {resultMap.put("success", false);resultMap.put("msg", "无权限操作");return resultMap;}}
}

商品service层及文件service层

到了service层就不用区分前后台了,这个只不过是controller中关于身份认证的问题,大部分service的方法,前后台都是可以通用的。

接口文件ProductService.java

package top.winxblast.happymall.service;import com.github.pagehelper.PageInfo;
import top.winxblast.happymall.common.ServerResponse;
import top.winxblast.happymall.pojo.Product;
import top.winxblast.happymall.vo.ProductDetailVo;/*** 商品相关服务** @author winxblast* @create 2017/11/02**/
public interface ProductService {ServerResponse saveOrUpdateProduct(Product product);ServerResponse<String> setSaleStatus(Integer productId, Integer status);ServerResponse<ProductDetailVo> manageProductDetail(Integer productId);ServerResponse<PageInfo> getProductList(int pageNum, int pageSize);ServerResponse<PageInfo> searchProduct(String productName, Integer productId, int pageNum, int pageSize);ServerResponse<ProductDetailVo> getProductDetail(Integer productId);ServerResponse<PageInfo> getProductByKeywordCategory(String keyword, Integer categoryId, int pageNum, int pageSize, String orderBy);
}

service接口实现类,提到这个,到现在我还不能很好的理解依赖注入对于减少耦合的帮助,还有这里面尽然不需要考虑多线程的问题,难倒都被Spring把相关细节隐藏的了么,我需要好好学习啊~
ProductServiceImpl.java

package top.winxblast.happymall.service.impl;import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.ResponseBody;
import top.winxblast.happymall.common.Const;
import top.winxblast.happymall.common.ResponseCode;
import top.winxblast.happymall.common.ServerResponse;
import top.winxblast.happymall.dao.CategoryMapper;
import top.winxblast.happymall.dao.ProductMapper;
import top.winxblast.happymall.pojo.Category;
import top.winxblast.happymall.pojo.Product;
import top.winxblast.happymall.service.CategoryService;
import top.winxblast.happymall.service.ProductService;
import top.winxblast.happymall.util.DateTimeUtil;
import top.winxblast.happymall.util.PropertiesUtil;
import top.winxblast.happymall.vo.ProductDetailVo;
import top.winxblast.happymall.vo.ProductListVo;import java.util.ArrayList;
import java.util.List;/*** 商品服务接口实现类** @author winxblast* @create 2017/11/02**/
@Service(value = "productService")
public class ProductServiceImpl implements ProductService {@Autowiredprivate ProductMapper productMapper;@Autowiredprivate CategoryMapper categoryMapper;@Autowiredprivate CategoryService categoryService;/*** 新增或者更新产品,这个在前端需要分开两个功能,在后端可以在一个方法中实现* @param product* @return*/@Overridepublic ServerResponse saveOrUpdateProduct(Product product) {if(product == null) {return ServerResponse.createByErrorMessage("新增或更新产品参数不正确");}//如果子图不为空,就取第一张图片作为主图if(StringUtils.isNotBlank(product.getSubImages())) {String[] subImageArray = product.getSubImages().split(",");if(subImageArray.length > 0) {product.setMainImage(subImageArray[0]);}}//有产品id表示是更新if(product.getId() != null) {//看到这里,感觉这里没有涉及事务管理啊,可能并发的考虑也不多,后期自己看看能不能加一些相关的内容int rowCount = productMapper.updateByPrimaryKey(product);if(rowCount > 0) {return ServerResponse.createBySuccess("更新产品成功");} else {return ServerResponse.createByErrorMessage("更新产品失败");}} else {//没有id就新增产品int rowCount = productMapper.insert(product);if(rowCount > 0) {return ServerResponse.createBySuccess("新增产品成功");} else {return ServerResponse.createByErrorMessage("新增产品失败");}}}/*** 商品上下架* @param productId* @param status* @return*/@Overridepublic ServerResponse<String> setSaleStatus(Integer productId, Integer status) {if(productId == null || status == null) {return ServerResponse.createByErrorCodeMessage(ResponseCode.ILLEGAL_ARGUMENT.getCode(), ResponseCode.ILLEGAL_ARGUMENT.getDesc());}Product product = new Product();product.setId(productId);product.setStatus(status);int rowCount = productMapper.updateByPrimaryKeySelective(product);if(rowCount > 0) {return ServerResponse.createBySuccess("修改商品销售状态成功");} else {return ServerResponse.createByErrorMessage("修改商品销售状态失败");}}/*** 获得商品详情,后台* @param productId* @return*/@Overridepublic ServerResponse<ProductDetailVo> manageProductDetail(Integer productId) {if(productId == null) {return ServerResponse.createByErrorCodeMessage(ResponseCode.ILLEGAL_ARGUMENT.getCode(), ResponseCode.ILLEGAL_ARGUMENT.getDesc());}Product product = productMapper.selectByPrimaryKey(productId);if(product == null) {return ServerResponse.createByErrorMessage("商品已下架或者删除");}//这里用vo对象--value object//业务更复杂:pojo-->bo(business object)-->vo(view object)//通过一个私有化的方法来组装这个对象ProductDetailVo productDetailVo = assembleProductDetailVo(product);return ServerResponse.createBySuccess(productDetailVo);}private ProductDetailVo assembleProductDetailVo(Product product) {ProductDetailVo productDetailVo = new ProductDetailVo();//下面我不能理解这么麻烦为什么不写到ProductDetailVo的一个构造函数中productDetailVo.setId(product.getId());productDetailVo.setCategoryId(product.getCategoryId());productDetailVo.setName(product.getName());productDetailVo.setSubtitle(product.getSubtitle());productDetailVo.setMainImage(product.getMainImage());productDetailVo.setSubImages(product.getSubImages());productDetailVo.setDetail(product.getDetail());productDetailVo.setPrice(product.getPrice());productDetailVo.setStock(product.getStock());productDetailVo.setStatus(product.getStatus());//imageHost,从配置文件中获取,为了配置和代码分离,为了热部署、配置中心等等productDetailVo.setImageHost(PropertiesUtil.getProperty("ftp.server.http.prefix","http://img.happymall.winxblast.top/"));//parentCategoryIdCategory category = categoryMapper.selectByPrimaryKey(product.getId());if(category == null) {//没有就默认根节点吧,这样也奇怪···先这么定productDetailVo.setParentCategoryId(0);} else {productDetailVo.setParentCategoryId(category.getParentId());}//createTimeproductDetailVo.setCreateTime(DateTimeUtil.dateToStr(product.getCreateTime()));//updateTimeproductDetailVo.setUpdateTime(DateTimeUtil.dateToStr(product.getUpdateTime()));return productDetailVo;}/*** 获得商品列表* @param pageNum* @param pageSize* @return*/@Overridepublic ServerResponse<PageInfo> getProductList(int pageNum, int pageSize) {//startPage-start//填充自己的sql查询逻辑//pageHelper-收尾PageHelper.startPage(pageNum, pageSize);List<Product> productList = productMapper.selectList();//由于列表也不需要太多的信息,不用把查找到的商品详情全部返回,所以也创建一个VO//还有老师这里优化的可能也不是很够,既然不需要那么多信息,那么sql查询的时候就不要查这么多信息List<ProductListVo> productListVoList = Lists.newArrayList();for(Product productItem : productList) {ProductListVo productListVo = assembleProductListVo(productItem);productListVoList.add(productListVo);}/* 这是老师的写法,我感觉直接用productListVoList构造pageinfo就行了PageInfo pageResult = new PageInfo(productList);pageResult.setList(productListVoList);*/PageInfo pageResult = new PageInfo(productListVoList);return ServerResponse.createBySuccess(pageResult);}private ProductListVo assembleProductListVo(Product product) {ProductListVo productListVo = new ProductListVo();productListVo.setId(product.getId());productListVo.setCategoryId(product.getCategoryId());productListVo.setName(product.getName());productListVo.setSubtitle(product.getSubtitle());productListVo.setMainImage(product.getMainImage());productListVo.setPrice(product.getPrice());productListVo.setStatus(product.getStatus());productListVo.setImageHost(PropertiesUtil.getProperty("ftp.server.http.prefix","http://img.happymall.winxblast.top/"));return productListVo;}/*** 通过搜索名称或者id来返回商品列表* @param productName* @param productId* @param pageNum* @param pageSize* @return*/@Overridepublic ServerResponse<PageInfo> searchProduct(String productName, Integer productId, int pageNum, int pageSize) {PageHelper.startPage(pageNum, pageSize);if(StringUtils.isNotBlank(productName)) {productName = new StringBuilder().append("%").append(productName).append("%").toString();}List<Product> productList = productMapper.selectByNameAndProductId(productName,productId);List<ProductListVo> productListVoList = Lists.newArrayList();for(Product productItem : productList) {ProductListVo productListVo = assembleProductListVo(productItem);productListVoList.add(productListVo);}PageInfo pageResult = new PageInfo(productList);pageResult.setList(productListVoList);return ServerResponse.createBySuccess(pageResult);}/*** 前台用户获取商品详情,大部分跟后台一致,只不过需要检查商品上下架状态* @param productId* @return*/@Overridepublic ServerResponse<ProductDetailVo> getProductDetail(Integer productId) {if(productId == null) {return ServerResponse.createByErrorCodeMessage(ResponseCode.ILLEGAL_ARGUMENT.getCode(), ResponseCode.ILLEGAL_ARGUMENT.getDesc());}Product product = productMapper.selectByPrimaryKey(productId);if(product == null) {return ServerResponse.createByErrorMessage("商品已下架或者删除");}if(product.getStatus() != Const.ProductStatusEnum.ON_SALE.getCode()) {return ServerResponse.createByErrorMessage("商品已下架或者删除");}//这里用vo对象--value object//业务更复杂:pojo-->bo(business object)-->vo(view object)//通过一个私有化的方法来组装这个对象ProductDetailVo productDetailVo = assembleProductDetailVo(product);return ServerResponse.createBySuccess(productDetailVo);}/*** 通过关键字和商品分类获取商品列表分页* @param keyword* @param categoryId* @param pageNum* @param pageSize* @param orderBy* @return*/@Overridepublic ServerResponse<PageInfo> getProductByKeywordCategory(String keyword, Integer categoryId,int pageNum, int pageSize, String orderBy) {if(StringUtils.isBlank(keyword) && categoryId == null) {return ServerResponse.createByErrorCodeMessage(ResponseCode.ILLEGAL_ARGUMENT.getCode(),ResponseCode.ILLEGAL_ARGUMENT.getDesc());}//这个是为了在categoryId是一个大的父分类的时候,可以把下面的小分类全部找出来放进去List<Integer> categoryIdList = new ArrayList<>();if(categoryId != null) {Category category = categoryMapper.selectByPrimaryKey(categoryId);if(category == null && StringUtils.isBlank(keyword)) {//没有该分类,并且还没有关键字,这个时候返回一个空的结果集,不报错,这个是和前端的商量结果,不一定的···PageHelper.startPage(pageNum,pageSize);List<ProductListVo> productListVoList = Lists.newArrayList();PageInfo pageInfo = new PageInfo(productListVoList);return ServerResponse.createBySuccess(pageInfo);}categoryIdList = categoryService.selectCategoryAndChildrenById(category.getId()).getData();}if(StringUtils.isNoneBlank(keyword)) {keyword = new StringBuilder().append("%").append(keyword).append("%").toString();}PageHelper.startPage(pageNum,pageSize);//排序处理if(StringUtils.isNotBlank(orderBy)) {if(Const.ProductListOrderBy.PRICE_ASC_DESC.contains(orderBy)) {String[] orderByArray = orderBy.split("_");PageHelper.orderBy(orderByArray[0]+" "+orderByArray[1]);}}List<Product> productList = productMapper.selectByNameAndCategoryIds(StringUtils.isBlank(keyword)?null:keyword,categoryIdList.size()==0?null:categoryIdList);List<ProductListVo> productListVoList = Lists.newArrayList();for(Product product : productList) {ProductListVo productListVo = assembleProductListVo(product);productListVoList.add(productListVo);}//我感觉下面两句就可以合成一句PageInfo pageInfo = new PageInfo(productList);pageInfo.setList(productListVoList);return ServerResponse.createBySuccess(pageInfo);}}

上面这个service中有不少新的东西啊,包括使用了vo,view Object, 如果业务复杂的话可能还需要bo business object,这里我是这么理解vo和product类的区别的,因为数据库中product记录了这个商品的所有细节,却不一定要全部展示出来,比如在列表页只要几个关键信息就行了,商品详情页也不会把商品的一些只需要商家知道的信息放进去,所以有了vo后可以这样理清需要的内容。

这里面使用了pagehelper来帮助我们实现分页操作,功能强大,但是细节我们自己最好也要掌握。

fileService.java这个是图片上传的服务,看着简单,还是使用了别人成熟的包(ftp),需要自己好好体会。这里我就直接把实现类放出来了,接口大家改一改就有了

package top.winxblast.happymall.service.impl;import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import top.winxblast.happymall.service.FileService;
import top.winxblast.happymall.util.FTPUtil;import java.io.File;
import java.io.IOException;
import java.util.UUID;/*** 文件服务接口实现类** @author winxblast* @create 2017/11/12**/
@Service(value = "fileService")
public class FileServiceImpl implements FileService{private Logger logger = LoggerFactory.getLogger(FileServiceImpl.class);/*** 文件上传* @param file* @param path* @return 保存时的文件名,异常是返回null*/@Overridepublic String upload(MultipartFile file, String path) {String fileName = file.getOriginalFilename();//扩展名//abc.jpgString fileExtensionName = fileName.substring(fileName.lastIndexOf(".")+1);String uploadFileName = UUID.randomUUID().toString() + "." + fileExtensionName;logger.info("开始上传文件,上传文件的文件名:{},上传路径:{},新文件名:{}",fileName,path,uploadFileName);File fileDir = new File(path);if(!fileDir.exists()) {fileDir.setWritable(true);fileDir.mkdirs();}File targetFile = new File(path, uploadFileName);try {file.transferTo(targetFile);//至此文件已经上传成功了//将targetFile上传到我们的ftp服务器上FTPUtil.upload(Lists.newArrayList(targetFile));//上传完成之后删除upload下面的文件targetFile.delete();} catch (IOException e) {logger.error("上传文件异常", e);return null;}return targetFile.getName();}}

ftp上传工具类和配置读取工具类

FTPUtil.java

package top.winxblast.happymall.util;import org.apache.commons.net.ftp.FTPClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;/*** FTP服务器工具类** @author winxblast* @create 2017/11/12**/
public class FTPUtil {public static final Logger logger = LoggerFactory.getLogger(FTPUtil.class);private static String ftpIp = PropertiesUtil.getProperty("ftp.server.ip");private static String ftpUser = PropertiesUtil.getProperty("ftp.user");private static String ftpPass = PropertiesUtil.getProperty("ftp.pass");public FTPUtil(String ip, int port, String user, String pwd) {this.ip = ip;this.port = port;this.user = user;this.pwd = pwd;}public static boolean upload(List<File> fileList) throws IOException {FTPUtil ftpUtil = new FTPUtil(ftpIp,21,ftpUser,ftpPass);logger.info("开始连接FTP服务器");//异常抛出,这样在服务层能够有相应的处理//老师在这里remotePath加了img实际上不需要这样,不然要在图片服务器前缀那里也修改一下,我这里就不加了boolean result = ftpUtil.upload("", fileList);logger.info("开始连接FTP服务器,结束上传,上传结果:{}", result);return result;}private boolean upload(String remotePath, List<File> fileList) throws IOException {boolean uploaded = true;FileInputStream fis = null;//连接FTP服务器if(connectServer(this.ip, this.port, this.user, this.pwd)) {try {//这样可以修改文件目录ftpClient.changeWorkingDirectory(remotePath);//设置缓冲区ftpClient.setBufferSize(1024);ftpClient.setControlEncoding("UTF-8");//设置为二进制编码形式可以防止乱码ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);//打开本地被动模式,详见vsftp安装ftpClient.enterLocalPassiveMode();//然后可以开始上传for(File fileItem : fileList) {fis = new FileInputStream(fileItem);ftpClient.storeFile(fileItem.getName(), fis);}} catch (IOException e) {logger.error("上传文件异常",e);uploaded = false;} finally {//要释放连接,不然会占用资源,时间长了会有问题fis.close();ftpClient.disconnect();}}return uploaded;}private boolean connectServer(String ip, int port, String user, String pwd) {ftpClient = new FTPClient();boolean isSuccess = false;try {ftpClient.connect(ip);isSuccess = ftpClient.login(user, pwd);} catch (IOException e) {logger.error("连接FTP服务器异常",e);}return isSuccess;}private String ip;private int port;private String user;private String pwd;private FTPClient ftpClient;public String getIp() {return ip;}public void setIp(String ip) {this.ip = ip;}public int getPort() {return port;}public void setPort(int port) {this.port = port;}public String getUser() {return user;}public void setUser(String user) {this.user = user;}public String getPwd() {return pwd;}public void setPwd(String pwd) {this.pwd = pwd;}public FTPClient getFtpClient() {return ftpClient;}public void setFtpClient(FTPClient ftpClient) {this.ftpClient = ftpClient;}
}

PropertiesUtil.java

package top.winxblast.happymall.util;import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Properties;/*** Created by geely* 读取配置文件的工具类,当然在这里限制了读happymall.properties这个文件*/
public class PropertiesUtil {private static Logger logger = LoggerFactory.getLogger(PropertiesUtil.class);private static Properties props;//写一点老生长谈的基础内容,一个类加载,静态块优于普通代码块,优于构造函数//且静态块只加载一次static {String fileName = "happymall.properties";props = new Properties();try {props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName),"UTF-8"));} catch (IOException e) {logger.error("配置文件读取异常",e);}}public static String getProperty(String key){String value = props.getProperty(key.trim());if(StringUtils.isBlank(value)){return null;}return value.trim();}public static String getProperty(String key,String defaultValue){String value = props.getProperty(key.trim());if(StringUtils.isBlank(value)){value = defaultValue;}return value.trim();}
}

ok,看了这么些别人写的工具类,感觉自己还有差距,可能我写出来也能用,但是肯定不够安全或考虑全面。

dao层

最后就是和数据库交互的部分了,mapper接口比较简单,xml文件注意学习if、where、foreach等标签的使用方法
ProductMapper.java

package top.winxblast.happymall.dao;import org.apache.ibatis.annotations.Param;
import top.winxblast.happymall.pojo.Product;import java.util.List;public interface ProductMapper {/*** This method was generated by MyBatis Generator.* This method corresponds to the database table happymall_product** @mbggenerated Sun Oct 08 14:03:47 CST 2017*/int deleteByPrimaryKey(Integer id);/*** This method was generated by MyBatis Generator.* This method corresponds to the database table happymall_product** @mbggenerated Sun Oct 08 14:03:47 CST 2017*/int insert(Product record);/*** This method was generated by MyBatis Generator.* This method corresponds to the database table happymall_product** @mbggenerated Sun Oct 08 14:03:47 CST 2017*/int insertSelective(Product record);/*** This method was generated by MyBatis Generator.* This method corresponds to the database table happymall_product** @mbggenerated Sun Oct 08 14:03:47 CST 2017*/Product selectByPrimaryKey(Integer id);/*** This method was generated by MyBatis Generator.* This method corresponds to the database table happymall_product** @mbggenerated Sun Oct 08 14:03:47 CST 2017*/int updateByPrimaryKeySelective(Product record);/*** This method was generated by MyBatis Generator.* This method corresponds to the database table happymall_product** @mbggenerated Sun Oct 08 14:03:47 CST 2017*/int updateByPrimaryKey(Product record);/*** 商品列表,没有任何搜索选项,分页靠插件完成* @return*/List<Product> selectList();/*** 通过商品名称或者ID查询商品列表* @param productName* @param productId* @return*/List<Product> selectByNameAndProductId(@Param("productName")String productName, @Param("productId")Integer productId);/*** 通过商品名称和产品分类来查询商品列表* @param productName* @param categoryIdList* @return*/List<Product> selectByNameAndCategoryIds(@Param("productName")String productName, @Param("categoryIdList")List<Integer> categoryIdList);
}

ProductMapper.xml 前面是mybatisplugin插件生成的,后面一些是自己添加的。

<?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" >
<mapper namespace="top.winxblast.happymall.dao.ProductMapper" ><resultMap id="BaseResultMap" type="top.winxblast.happymall.pojo.Product" ><!--WARNING - @mbggeneratedThis element is automatically generated by MyBatis Generator, do not modify.This element was generated on Sun Oct 08 14:03:47 CST 2017.--><constructor ><idArg column="id" jdbcType="INTEGER" javaType="java.lang.Integer" /><arg column="category_id" jdbcType="INTEGER" javaType="java.lang.Integer" /><arg column="name" jdbcType="VARCHAR" javaType="java.lang.String" /><arg column="subtitle" jdbcType="VARCHAR" javaType="java.lang.String" /><arg column="main_image" jdbcType="VARCHAR" javaType="java.lang.String" /><arg column="sub_images" jdbcType="VARCHAR" javaType="java.lang.String" /><arg column="detail" jdbcType="VARCHAR" javaType="java.lang.String" /><arg column="price" jdbcType="DECIMAL" javaType="java.math.BigDecimal" /><arg column="stock" jdbcType="INTEGER" javaType="java.lang.Integer" /><arg column="status" jdbcType="INTEGER" javaType="java.lang.Integer" /><arg column="create_time" jdbcType="TIMESTAMP" javaType="java.util.Date" /><arg column="update_time" jdbcType="TIMESTAMP" javaType="java.util.Date" /></constructor></resultMap><sql id="Base_Column_List" ><!--WARNING - @mbggeneratedThis element is automatically generated by MyBatis Generator, do not modify.This element was generated on Sun Oct 08 14:03:47 CST 2017.-->id, category_id, name, subtitle, main_image, sub_images, detail, price, stock, status, create_time, update_time</sql><select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" ><!--WARNING - @mbggeneratedThis element is automatically generated by MyBatis Generator, do not modify.This element was generated on Sun Oct 08 14:03:47 CST 2017.-->select <include refid="Base_Column_List" />from happymall_productwhere id = #{id,jdbcType=INTEGER}</select><delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" ><!--WARNING - @mbggeneratedThis element is automatically generated by MyBatis Generator, do not modify.This element was generated on Sun Oct 08 14:03:47 CST 2017.-->delete from happymall_productwhere id = #{id,jdbcType=INTEGER}</delete><insert id="insert" parameterType="top.winxblast.happymall.pojo.Product" ><!--WARNING - @mbggeneratedThis element is automatically generated by MyBatis Generator, do not modify.This element was generated on Sun Oct 08 14:03:47 CST 2017.-->insert into happymall_product (id, category_id, name, subtitle, main_image, sub_images, detail, price, stock, status, create_time, update_time)values (#{id,jdbcType=INTEGER}, #{categoryId,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{subtitle,jdbcType=VARCHAR}, #{mainImage,jdbcType=VARCHAR}, #{subImages,jdbcType=VARCHAR}, #{detail,jdbcType=VARCHAR}, #{price,jdbcType=DECIMAL}, #{stock,jdbcType=INTEGER}, #{status,jdbcType=INTEGER}, now(), now())</insert><insert id="insertSelective" parameterType="top.winxblast.happymall.pojo.Product" ><!--WARNING - @mbggeneratedThis element is automatically generated by MyBatis Generator, do not modify.This element was generated on Sun Oct 08 14:03:47 CST 2017.-->insert into happymall_product<trim prefix="(" suffix=")" suffixOverrides="," ><if test="id != null" >id,</if><if test="categoryId != null" >category_id,</if><if test="name != null" >name,</if><if test="subtitle != null" >subtitle,</if><if test="mainImage != null" >main_image,</if><if test="subImages != null" >sub_images,</if><if test="detail != null" >detail,</if><if test="price != null" >price,</if><if test="stock != null" >stock,</if><if test="status != null" >status,</if><if test="createTime != null" >create_time,</if><if test="updateTime != null" >update_time,</if></trim><trim prefix="values (" suffix=")" suffixOverrides="," ><if test="id != null" >#{id,jdbcType=INTEGER},</if><if test="categoryId != null" >#{categoryId,jdbcType=INTEGER},</if><if test="name != null" >#{name,jdbcType=VARCHAR},</if><if test="subtitle != null" >#{subtitle,jdbcType=VARCHAR},</if><if test="mainImage != null" >#{mainImage,jdbcType=VARCHAR},</if><if test="subImages != null" >#{subImages,jdbcType=VARCHAR},</if><if test="detail != null" >#{detail,jdbcType=VARCHAR},</if><if test="price != null" >#{price,jdbcType=DECIMAL},</if><if test="stock != null" >#{stock,jdbcType=INTEGER},</if><if test="status != null" >#{status,jdbcType=INTEGER},</if><if test="createTime != null" >now(),</if><if test="updateTime != null" >now(),</if></trim></insert><update id="updateByPrimaryKeySelective" parameterType="top.winxblast.happymall.pojo.Product" ><!--WARNING - @mbggeneratedThis element is automatically generated by MyBatis Generator, do not modify.This element was generated on Sun Oct 08 14:03:47 CST 2017.-->update happymall_product<set ><if test="categoryId != null" >category_id = #{categoryId,jdbcType=INTEGER},</if><if test="name != null" >name = #{name,jdbcType=VARCHAR},</if><if test="subtitle != null" >subtitle = #{subtitle,jdbcType=VARCHAR},</if><if test="mainImage != null" >main_image = #{mainImage,jdbcType=VARCHAR},</if><if test="subImages != null" >sub_images = #{subImages,jdbcType=VARCHAR},</if><if test="detail != null" >detail = #{detail,jdbcType=VARCHAR},</if><if test="price != null" >price = #{price,jdbcType=DECIMAL},</if><if test="stock != null" >stock = #{stock,jdbcType=INTEGER},</if><if test="status != null" >status = #{status,jdbcType=INTEGER},</if><if test="createTime != null" >create_time = #{createTime,jdbcType=TIMESTAMP},</if><if test="updateTime != null" >update_time = now(),</if></set>where id = #{id,jdbcType=INTEGER}</update><update id="updateByPrimaryKey" parameterType="top.winxblast.happymall.pojo.Product" ><!--WARNING - @mbggeneratedThis element is automatically generated by MyBatis Generator, do not modify.This element was generated on Sun Oct 08 14:03:47 CST 2017.-->update happymall_productset category_id = #{categoryId,jdbcType=INTEGER},name = #{name,jdbcType=VARCHAR},subtitle = #{subtitle,jdbcType=VARCHAR},main_image = #{mainImage,jdbcType=VARCHAR},sub_images = #{subImages,jdbcType=VARCHAR},detail = #{detail,jdbcType=VARCHAR},price = #{price,jdbcType=DECIMAL},stock = #{stock,jdbcType=INTEGER},status = #{status,jdbcType=INTEGER},create_time = #{createTime,jdbcType=TIMESTAMP},update_time = now()where id = #{id,jdbcType=INTEGER}</update><select id="selectList" resultMap="BaseResultMap">SELECT<include refid="Base_Column_List"/>FROM happymall_productORDER BY id ASC</select><select id="selectByNameAndProductId" resultMap="BaseResultMap" parameterType="map">SELECT<include refid="Base_Column_List"/>FROM happymall_product<where><if test="productName != null">AND name LIKE #{productName}</if><if test="productId != null">AND id = #{productId}</if></where></select><select id="selectByNameAndCategoryIds" resultMap="BaseResultMap" parameterType="map">SELECT<include refid="Base_Column_List"></include>FROM happymall_productWHERE status = 1<if test="productName != null">AND name LIKE  #{productName}</if><if test="categoryIdList != null">AND category_id IN<foreach item="item" index="index" open="(" separator="," close=")" collection="categoryIdList">#{item}</foreach></if></select>
</mapper>

测试接口

最后就是我们的测试了,这个之前也做过好几次了,没有太多的区别了,这里给出测试图片上传的jsp
index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<body>
<h2>Hello World!</h2>springMVC上传文件
<form name="form1" action="/manage/product/upload.do" method="post" enctype="multipart/form-data"><input type="file" name="upload_file"><input type="submit" value="SpringMVC上传文件">
</form>
<br>
<br>
富文本图片上传
<form name="form1" action="/manage/product/richtext_img_upload.do" method="post" enctype="multipart/form-data"><input type="file" name="upload_file"><input type="submit" value="富文本图片上传">
</form></body>
</html>

下面给出几个测试的截图,这里访问的地址还可以进一步优化,向着更restful的模式改进,有些接口的get或者post方法就要修改了。

product有这些接口测试

search.do

list.do

接口结果都是按照我们预想的来实现的,就不一一列出了。

一点插曲

中间想升级一下IDEA的版本,结果就 石皮 解 失败了,只好退回上一个版本2017.2···这么贵,没有公司买给我可怎么玩···然后就遇到了IDEA报错,编译时各种类找不到,但是明明都在的啊,网上查解决方案,最后我这是清除maven打包解决的(mvn clean package);也有通过清除IDEA缓存解决的,在IDEA中file–>invalidate caches / Restart。

【实战】7-2 商品管理模块开发测试相关推荐

  1. 15、【 商品管理模块开发】——后台获取商品详情功能开发及PropertiesUtil配置工具,DateTimeUtil时间处理工具开发...

    1.后台获取商品详情接口: 在上一篇文章所新建的ProudctManageController类中新建下面方法: *Controller: //获取商品详情接口@RequestMapping(&quo ...

  2. 17、【 商品管理模块开发】——后台商品图片的springmvc和富文本上传以及ftp文件服务器的开发...

    1.FTP文件服务器的搭建: 软件下载:ftpserver: image.png 浏览器访问:ftp://127.0.0.1/ image.png 点击任意一个文件,就可以看到我们图片啦,前提是前面指 ...

  3. 70-项目实战后续(课程管理模块)

    项目实战后续(课程管理模块) 项目介绍: 前面我们完成了初步的项目,接下来实现更高等级的作用(基于框架的项目编写) 页面原型展示: 技术选型: 前端技术选型: 后端技术选型: 项目开发环境 : 开发工 ...

  4. 电商系统开发实战-用户微服务基础模块开发

    电商系统开发实战-用户微服务基础模块开发 1.用户微服务项目开发之收货地址查询接口开发 1.1 配置文件配置 application.yml server:port: 9001spring:appli ...

  5. JAVA财务保证金管理模块开发-王大师王文峰开发(项目现场驻场开发)

    本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,<Java王大师王天师>作者 公众号:山峯草堂,非技术多篇文章,专注于天道酬勤的 Java 开发问题.中国国学.传统文化和代 ...

  6. Vue实战之 9.商品管理 -- 商品列表

    1. 商品管理概述 商品管理模块用于维护电商平台的商品信息,包括商品的类型.参数.详情等.通过商品管理模块可以实现商品的添加.修改.展示和删除等功能. 2. 商品列表 实现商品列表布局效果 调用后台接 ...

  7. mmall 学习笔记--分类管理模块,商品管理模块,购物车模块,收货地址模块,支付模块,订单管理模块,云服务器线上部署,自动发布,

    ()数据库配置 常见语句 Create table 'my_table'( int id not null auto_increment ) () 建表的时候出现text,bigInt,decimal ...

  8. 拉勾教育后台管理系统(SSM)(课程管理模块开发)【学习笔记】

    文章目录 1.项目架构 1.1.项目介绍 1.2.技术选型 1.2.1.前端技术选型 1.2.2.后端技术选型 1.3.项目开发环境 2.Maven进阶使用(Maven聚合工程) 2.1.maven的 ...

  9. 电商平台搭建--订单管理模块开发(一)

    Hi,大家好,我是Steafan!项目开发到这里,基本上已经进行了90%了.项目中最关键的也是最难开发的就是今天我要向大家介绍的订单管理模块了.在实际的项目开发中,订单管理模块和支付模块往往是并行开发 ...

最新文章

  1. clone git 修改保存路径_Git和Github详细入门教程(别再跟我说你不会Git和Github)
  2. php如何保存服务器会话,如何用PHP在服务器上创建会话?
  3. 抽象类 VS 接口 (3)
  4. IIS 6.0支持.SHTML
  5. HTTP 错误 404.2 - Not Found 由于 Web 服务器上的“ISAPI 和 CGI 限制”列表设置,无法提供您请求的页面...
  6. ML.NET Cookbook:(3)如何从CSV加载包含多个列的数据?
  7. 【转】c#中@的3种作用
  8. ASP.NET面试题 (转)
  9. shell脚本 猜数字游戏并计数比较次数
  10. 备份网站服务器文件路径,网站数据自动备份方法
  11. Spring+Spring Boot+Mybatis框架注解解析
  12. python staticsmodels用法_python自动化之models 进阶操作二
  13. linux Centos系统下mysql主从配置
  14. 图像分辨率与长度单位转换
  15. 多次进行hdfs namenode -format命令时,启动DataNode自动死亡的原因
  16. csgo查询服务器延迟,《csgo》网络延迟怎么查看 网络延迟ping查看方法分享
  17. 3dmax动画模型导入unity 帧率问题
  18. kal虚拟机统下安装open-vmware-tools
  19. 《Linux命令行与shell脚本编程大全》(第三版)读书笔记
  20. 高德地图(AMap)JavaScript API的使用

热门文章

  1. BF算法(Java)
  2. 【旧资料整理】解决firefox3迅雷插件右键查看页面源代码无效问题
  3. 游歌科技助力润邦重机智能物联抓料机成功交付
  4. 将MATLAB的figure窗体嵌入到C#窗体应用的(panel)中
  5. 什么是固态继电器?与机械继电器的区别是什么?
  6. 康博m2000显卡_NVIDIA Quadro M2000M性能跑分评测 | ZMMOO
  7. java毕业设计选题基于javaweb实现的政府机关公文|文件收发管理系统
  8. 计算机无法识别读卡器怎么办,usb读卡器读不出来怎么办,教您解决的办法
  9. oracle972,配置Oracle共享服务器
  10. java.io.IOException: offset 0相关问题研究