前言
找完工作以后感觉一段时间失去了学习的激情,再加上毕业论文的工作,懒散拖沓了好久才开始继续我的项目学习。其实这些内容吧,你说难那是一点也不难,重要的在于处理业务的经验,防患漏洞的经验,以及隐藏在项目背后的协调沟通能力,现在我是跟着老师视频在学,一个人设计全套,没有任何沟通协调问题,一旦到公司以后可能就会大不一样了···
好吧,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 {
@Autowired
private ProductService productService;
/**
* 返回商品详情,与后台不同的是,这里不需要验证管理员权限,而是要验证商品上下架状态
* @param productId
* @return
*/
@RequestMapping(value = "detail.do", method = RequestMethod.GET)
@ResponseBody
public ServerResponse<ProductDetailVo> detail(Integer productId) {
return productService.getProductDetail(productId);
}
@RequestMapping(value = "list.do", method = RequestMethod.GET)
@ResponseBody
public 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 {
@Autowired
private UserService userService;
@Autowired
private ProductService productService;
@Autowired
private FileService fileService;
/**
* 新增或更新商品
* @param session
* @param product
* @return
*/
@RequestMapping(value = "save.do", method = RequestMethod.GET)
@ResponseBody
public 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)
@ResponseBody
public 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)
@ResponseBody
public 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)
@ResponseBody
public 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技术,监听我们自己的sql
return productService.getProductList(pageNum, pageSize);
} else {
return ServerResponse.createByErrorMessage("无权限操作");
}
}
/**
* 通过商品名称和id来获取商品列表
* @param session
* @param productName
* @param productId
* @param pageNum
* @param pageSize
* @return
*/
@RequestMapping(value = "search.do")
@ResponseBody
public 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)
@ResponseBody
public 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)
@ResponseBody
public 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 {
@Autowired
private ProductMapper productMapper;
@Autowired
private CategoryMapper categoryMapper;
@Autowired
private CategoryService categoryService;
/**
* 新增或者更新产品,这个在前端需要分开两个功能,在后端可以在一个方法中实现
* @param product
* @return
*/
@Override
public 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
*/
@Override
public 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
*/
@Override
public 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/"));
//parentCategoryId
Category category = categoryMapper.selectByPrimaryKey(product.getId());
if(category == null) {
//没有就默认根节点吧,这样也奇怪···先这么定
productDetailVo.setParentCategoryId(0);
} else {
productDetailVo.setParentCategoryId(category.getParentId());
}
//createTime
productDetailVo.setCreateTime(DateTimeUtil.dateToStr(product.getCreateTime()));
//updateTime
productDetailVo.setUpdateTime(DateTimeUtil.dateToStr(product.getUpdateTime()));
return productDetailVo;
}
/**
* 获得商品列表
* @param pageNum
* @param pageSize
* @return
*/
@Override
public 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
*/
@Override
public 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
*/
@Override
public 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
*/
@Override
public 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
*/
@Override
public String upload(MultipartFile file, String path) {
String fileName = file.getOriginalFilename();
//扩展名
//abc.jpg
String 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 - @mbggenerated
This 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 - @mbggenerated
This 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 - @mbggenerated
This 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_product
where id = #{id,jdbcType=INTEGER}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" >
<!--
WARNING - @mbggenerated
This 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_product
where id = #{id,jdbcType=INTEGER}
</delete>
<insert id="insert" parameterType="top.winxblast.happymall.pojo.Product" >
<!--
WARNING - @mbggenerated
This 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 - @mbggenerated
This 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 - @mbggenerated
This 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 - @mbggenerated
This 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 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_product
ORDER 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_product
WHERE 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。