电商生鲜网站开发(四)——后台开发:商品模块-图片上传/多条件拼接sql
增加商品
上传图片
更新商品
删除商品
批量上下架
图片上传功能
文件名UUID
通用唯一识别码(Universally Unique Identifier)
防止重名、防止发图
生成规则:日期和时间、MAC地址、HashCode、随机数
配置application.properties
上传文件的路径,根据部署情况自行修改 该值注入到Constan类中
file.upload.dir=/Users/cat/Documents/project/java/online_mall/mall-prepare-static
Constant常量类
package com.learn2333.mall.common;
import com.google.common.collect.Sets;
import com.learn2333.mall.exception.MallException;
import com.learn2333.mall.exception.MallExceptionEnum;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Set;
/**
* 描述: 常量值
*/
@Component
public class Constant {
public static final String MALL_USER = "mall_user";
public static final String SALT = "learn2333";
public static String FILE_UPLOAD_DIR;
//因为是static无法直接注入,使用set方法为静态变量注入赋值
@Value("${file.upload.dir}")
public void setFileUploadDir(String fileUploadDir) {
FILE_UPLOAD_DIR = fileUploadDir;
}
controller
@PostMapping("/admin/upload/file")
public ApiRestResponse upload(HttpServletRequest httpServletRequest,
@RequestParam("file") MultipartFile file) {
String fileName = file.getOriginalFilename();
String suffixName = fileName.substring(fileName.lastIndexOf(".")); //后缀
//生成文件名称UUID
UUID uuid = UUID.randomUUID();
String newFileName = uuid.toString() + suffixName;
//创建文件
File fileDirectory = new File(Constant.FILE_UPLOAD_DIR);
File destFile = new File(Constant.FILE_UPLOAD_DIR + newFileName);
//如果文件夹不存在
if (!fileDirectory.exists()) {
if (!fileDirectory.mkdir()) {//创建文件夹
throw new MallException(MallExceptionEnum.MKDIR_FAILED);
}
}
//文件写入
try {
file.transferTo(destFile);
} catch (IOException e) {
e.printStackTrace();
}
//返回文件地址
try {
return ApiRestResponse
.success(getHost(new URI(httpServletRequest.getRequestURL() + "")) + "/images/"
+ newFileName);
} catch (URISyntaxException e) {
return ApiRestResponse.error(MallExceptionEnum.UPLOAD_FAILED);
}
}
/**
* 获取ip和端口号
* @param uri
* @return
*/
private URI getHost(URI uri) {
URI effectiveURI;
try {
effectiveURI = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(),
null, null, null);
} catch (URISyntaxException e) {
effectiveURI = null;
}
return effectiveURI;
}
自定义静态资源映射目录
上传图片后回显
配置SpringBootWebMvcConfig
静态资源到本地目录到映射
package com.learn2333.mall.config;
import com.learn2333.mall.common.Constant;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 描述: 配置Swagger地址映射
*/
@Configuration
public class MallWebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//图片目录映射
registry.addResourceHandler("/images/**").addResourceLocations("file:" + Constant.FILE_UPLOAD_DIR);
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
商品批量上下架
MyBatis遍历List
where语句拼接
<update id="batchUpdateSellStatus">
update mall_product
set status = #{sellStatus}
where id in
<foreach collection="ids" open="(" close=")" item="id" separator=",">
#{id}
</foreach>
</update>
后台管理员商品列表接口
调用分页PageHelper,跟商品分类模块类似
前台用户商品列表接口
商品列表:搜索功能
使用入参判空、加%通配符、like关键字sql拼接
传入ProductListReq实体
package com.learn2333.mall.model.request;
import java.util.Date;
public class ProductListReq {
private String keyword; //关键字
private Integer categoryId; //分类id
private String orderBy; //排序方式
//分页
private Integer pageNum = 1;
private Integer pageSize = 10;
get;set;
}
ProjectController
@ApiOperation("商品列表")
@GetMapping("product/list")
public ApiRestResponse list(ProductListReq productListReq){
PageInfo list = productService.list(productListReq);
return ApiRestResponse.success(list);
}
对与这种条件比较多的,拼接麻烦的查询,可以建一个query,也方便后续的修改
ProductListQuery
package com.learn2333.mall.model.query;
import java.util.List;
/**
* 描述: 查询商品列表的Query
*/
public class ProductListQuery {
private String keyword;
//查询一个目录下的商品,不一定是只有当前目录下,还包含子目录,子子目录下的商品;因此之前传入一个目录的id现在还要获取它子目录的所有id
private List<Integer> categoryIds;
get;set;
}
productServiceImpl
@Override
public PageInfo list(ProductListReq productListReq) {
//构建Query对象
ProductListQuery productListQuery = new ProductListQuery();
//搜索处理
if (!StringUtils.isEmpty(productListReq.getKeyword())) {
String keyword = new StringBuilder().append("%").append(productListReq.getKeyword())
.append("%").toString();
productListQuery.setKeyword(keyword);
}
//目录处理:如果查某个目录下的商品,不仅是需要查出该目录下的,还要把所有子目录的所有商品都查出来,所以要拿到一个目录id的List
if (productListReq.getCategoryId() != null) {
//复用之前写过的传入目录id查询子目录方法listCategoryForCustomer
List<CategoryVO> categoryVOList = categoryService.listCategoryForCustomer(productListReq.getCategoryId());
//将得到的树形目录结构categoryVOList展开
ArrayList<Integer> categoryIds = new ArrayList<>();
categoryIds.add(productListReq.getCategoryId());
//用下面定义的方法来处理子目录的遍历,
//通过下面的函数处理就可以将categoryVOList中子目录所有id放入categoryIds,因为传入的是List引用类型
getCategoryIds(categoryVOList, categoryIds);
productListQuery.setCategoryIds(categoryIds);
}
//排序处理
String orderBy = productListReq.getOrderBy(); //为了系统安全,不能传入什么就直接按照什么排序,而是要拿到前台传入的排序规则
if (Constant.ProductListOrderBy.PRICE_ASC_DESC.contains(orderBy)) { //contains判断是否包含
PageHelper.startPage(productListReq.getPageNum(), productListReq.getPageSize(), orderBy);
} else {
PageHelper.startPage(productListReq.getPageNum(), productListReq.getPageSize());
}
List<Product> productList = productMapper.selectList(productListQuery);
PageInfo pageInfo = new PageInfo(productList);
return pageInfo;
}
//递归遍历子目录
private void getCategoryIds(List<CategoryVO> categoryVOList, ArrayList<Integer> categoryIds) {
for (int i = 0; i < categoryVOList.size(); i++) {
CategoryVO categoryVO = categoryVOList.get(i);
if (categoryVO != null) {
categoryIds.add(categoryVO.getId());
getCategoryIds(categoryVO.getChildCategory(), categoryIds);
}
}
}
常量(排序方式接口)
package com.learn2333.mall.common;
import com.google.common.collect.Sets;
import com.learn2333.mall.exception.MallException;
import com.learn2333.mall.exception.MallExceptionEnum;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Set;
/**
* 描述: 常量值
*/
@Component
public class Constant {
public static final String MALL_USER = "mall_user";
public static final String SALT = "learn2333";
public static String FILE_UPLOAD_DIR;
@Value("${file.upload.dir}")
public void setFileUploadDir(String fileUploadDir) {
FILE_UPLOAD_DIR = fileUploadDir;
}
public interface ProductListOrderBy {
Set<String> PRICE_ASC_DESC = Sets.newHashSet("price desc", "price asc");
}
ProductMapper接口
List<Product> selectList(@Param("query") ProductListQuery query);
ProductMapper
<select id="selectList" resultMap="BaseResultMap"
parameterType="com.learn2333.mall.model.query.ProductListQuery">
select
<include refid="Base_Column_List"/>
from mall_product
<where>
<if test="query.keyword != null">
and name like #{query.keyword}
if>
<if test="query.categoryIds != null">
and category_id in
<foreach collection="query.categoryIds" close=")" item="item" open="(" separator=",">
#{item}
foreach>
if>
and status = 1
where>
order by update_time desc
select>
排序功能、MyBatis PageHelper、枚举:order by
总结
商品的搜索、排序、目录查询
常见问题:更新和新增不要用一个接口,虽然方便,但是从长期维护角度每一个接口要明确分工;排序的时候要用常量枚举判断,不要直接传入,否则可能被黑客利用,攻击数据库。