店家管理系统开发
店铺注册:
image.png
自下而上的实现店铺的注册功能:
1、在Dao层下创建一个ShopDao接口
增加店铺,1为增加成功,-1为增加失败
image.png
因为Dao层的实现类是由mybatis实现的,所以我们需要在mapper文件夹下创建一个ShopDao.xml配置文件。
- namespace就是告诉mapper去扫哪个类
- id是方法名字
- useGeneratedKeys设置为true意思是一旦我们的数据添加成功,我们就会通过jdbc来获取它主键的值,然后将它插入到我们传入的实体类中,也就是这个shop对象中。为什么要设置这样的开口呢?例如当我们添加店铺信息的时候也要存储店铺的图片,为了保证店铺存储的合理性,我们需要将这个店铺的图片存储在该店铺的目录下,这就导致了每个店铺的目录的名字是不相同的,所以我们需要店铺的主键去区分店铺的名字。因此在存储图片的时候需要去获取店铺的id。也就是说一旦店铺信息添加完成,需要立即返回它的店铺id。
为什么不要等店铺信息添加完成后,在去写一个select语句去获取店铺的id呢?
1、比较麻烦,需要再写语句,2、是一旦有两个添加店铺的请求,同时的进入到这个数据库当中,这个时候就有可能搞成乱序,你取出的shopid就可能不是之前的shopid了。因此这个开useGeneratedKeys这个口子是必要的。而且这个口子也具有灵活性,比如我们只要添加店铺的信息,没有插入图片的需求了,也就是不需要获取他的shopid了,我们设置为false就行。mybatis-config.xml中也有这个useGeneratedKeys这个属性。 - KeyColumn指定数据库表的主键是哪一个。KeyProperty还有指定这个KeyColumn是跟实体类中的哪一个属性去绑定的。
-
insert into后面跟的是数据库的字段,values后面跟的是实体类的属性。
image.png
junit测试
- 首先这个shop与很多实体类都有关联,Area,PersonInfo(店主信息),ShopCategory(店铺类别),我们之前只创建了Area的两条记录,我们需要给其他两个关联类添加纪录才能去测试店铺的添加。随便添加就行。
-
开始测试
在test的dao层下创建一个ShopDaoTest类,
image.png
image.png
然后运行run test这里报错原因是shopDao没有注入进来,原因是我们的ShopDaoTest没有继承BaseTest类,继承了就行了。
更新店铺信息
在dao层的ShopDao创建更新方法
image.png
mybatis的优势动态生成sql语句
为什么要动态生成呢?假如我们某一张表里面只有两列a和b,做更新的时候有时候只需要更新a而不需要更新b,有时候只要更新b而不需要更细a,有时候两者都需要去做更新,如果不支持动态语句的话就需要写三条update语句才能满足数据的更新,如果支持动态sql就只要写一个方法就可以了。
不能更新的字段:
shop_id,owner_id(一旦你的店铺的用户确定了就不能更新了),create_time也是不变的,其他字段都是可变的。
- parameterType传入的是Shop的实例,然后判断实例的成员变量一旦是有值的,就update到对应的数据库里面去。如果没有值就不做update的。其外shop还剩下两个复合类型,一个是area,一个是shopcategory
- 注意主要if不是最后一个if都要用逗号,因为要用逗号去拼接。
-
最后还要编写一个where条件指定说我们的sql满足哪个条件后才去执行
image.png
image.png
image.png
image.png
junit测试
在shopDaoTest里
- 只更新desc和addr还有lasteditTime
-
为了不执行上面插入的测试把上面的插入的测试注释掉,也可以不注释使用@ingore
image.png
<if test="shopName!=null">shop_name=#{shopName},</if>
注意其中的shopName是成员变量,shop_name是数据库字段。
运行之后就可以看到数据库里的修改了。
Thumbnailator图片处理和封装Util
image.pngimage.png
然后在util包下创建一个ImageUtil类来封装thumbnailator方法的,
测试thumbanailator包下方法,首先在resources下存放一张watermark.jgp水印,并且在任意位置存放一张xiaohuangren的图片,目的就是要通过使用thumbanailator包下的方法来处理xiaohuangren图片的大小并且打上水印。
image.png image.png
image.png
运行之后
image.png
因为thumbnailator主要是需要告诉它输入的文件是什么以及输出的文件是什么这其中也就涉及到了地址的处理,所以需要提前编写一个地址工具类,在util下创建一个PathUtil类,这个类主要提供两类的路径,一类是根据执行环境的不同提供根路径,根路径就是我们项目的所有图片需要存放的路径,因为我们是工具类,不用去new它所以设置为静态方法,
image.png
- 为什么不把根目录设置在classpath下面而要设置在d:/projectdev/image/下呢,这样不就不用去指定绝对路径了?原因是一旦我们将图片保存在classpath下面,我们的工程重新部署的时候,那些新生成的图片文件就会被删除掉,除非你的图片一开始就是保存在classpath下面,这当然是不可能的,因为我们的程序在运行中客户会源源不断的去上传图片,所以需要将根路径设置在工程的路径以外,以防止自动删除掉。或者一般网上的做法是将这些图片保存在另一个服务器上,通过url来引入进来,
获取店铺图片的存储路径,因为这些图片存储在各自店铺的路径下,所以传入一个店铺id。
image.png
回到ImageUtil去编写缩略图方法
package com.imooc.o2o.util;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import javax.imageio.ImageIO;
import net.coobird.thumbnailator.Thumbnails;
import net.coobird.thumbnailator.geometry.Positions;
public class ImageUtil {
private static String basePath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
// 定义时间格式和随机对象
private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
private static final Random r = new Random();
// 处理缩略图也就是门面照以及商品的小图,由于这个方法处理的是用户传递过来的文件
// 也就是用户传递过来的文件流,因此传入的参数就是spring自带的文件处理对象
// 同时还需要接收一个我们将这个文件保存在哪里
public static String generateThumbnail(File thumbnail, String targetAddr) {
// 用户传递过来的文件名是随意命名的,有很多是重名的,所以我们不要用它的名字
// 用我们系统自己随机生成的文件名
String realFileName = getRandomFileName();
// 获取用户上传过来的扩展名,例如jpg,png等
String extension = getFileExtension(thumbnail);
// 随机名和扩展名就构成了新文件的新名字然后保存在targetAddr下
// 但是这个路径有时是不存在的,所以需要先创建
makeDirPath(targetAddr);
// 所以该文件的相对路径
String relativeAddr = targetAddr + realFileName + extension;
// 最后的文件绝对路径就是由根路径和相对路径来组成
File dest = new File(PathUtil.getImgBasePath() + relativeAddr);
// 开始创建缩略图,就是把用户传过来的文件变小
try {
Thumbnails.of(thumbnail).size(200, 200)
.watermark(Positions.BOTTOM_RIGHT, ImageIO.read(new File(basePath + "right.gif")), 0.25f)
.outputQuality(0.8f).toFile(dest);
} catch (IOException e) {
e.printStackTrace();
}
return targetAddr;
}
/**
* 创建目标路径所涉及到的目录,即/home/work/xiangze/xxx.jpg,那么home work xiangze 这三个文件都得自动创建
*
* @param targetAddr
*/
private static void makeDirPath(String targetAddr) {
String realFileParentPath = PathUtil.getImgBasePath() + targetAddr;
File dirPath = new File(realFileParentPath);
if (!dirPath.exists()) {
dirPath.mkdirs();
}
}
/**
* 获取输入文件流的扩张名
*
* @param thumbnail
* @return
*/
private static String getFileExtension(File thumbnail) {
String originalFileName = thumbnail.getName();
return originalFileName.substring(originalFileName.lastIndexOf("."));
}
/**
* 生成随机文件名,当前年月日小时分钟秒钟+五位随机数
*
* @return
*/
private static String getRandomFileName() {
// 获取随机的五位数
int rannum = r.nextInt(89999) + 10000;
String nowTimeStr = sDateFormat.format(new Date());
return nowTimeStr + rannum;
}
public static void main(String[] args) throws IOException {
// of传入需要处理的相关文件
// size指定图片大小,watermark添加水印,第一个参数是水印的位置(右下角)
// 第二个是水印的路经,由于我们的水印图片是保存在classpath路径下的
// 因此需要获取classpath的绝对值路径,由于我们的方法是通过线程去执行的,
// 因此我们可以通过线程去逆推到它的类加载器,从而得到资源的绝对路径
// 第三个参数为透明度0.25f
// 最后压缩80%
// 最后输出的路径
Thumbnails.of(new File("C:/Users/ljs/Pictures/Camera Roll/default.jpg")).size(200, 200)
.watermark(Positions.BOTTOM_RIGHT, ImageIO.read(new File(basePath + "right.gif")), 0.25f)
.outputQuality(0.8f).toFile("C:/Users/ljs/Pictures/Camera Roll/defaultnew.jpg");
}
}
编写好枚举类之后再回到dto的shopexecution下去编写
package com.imooc.o2o.dto;
import java.util.List;
import com.imooc.o2o.entity.Shop;
import com.imooc.o2o.enums.ShopStateEnum;
public class ShopExecution {
// 结果状态
private int state;
// 结果标识
private String stateInfo;
// 店铺的数量
private int count;
// 操作的shop(增删改查的时候要用到)
private Shop shop;
// shop列表(查询店铺的时候要用到)
private List<Shop> shopList;
// 默认构造函数
public ShopExecution() {
}
// 店铺操作失败的时候使用的构造器
public ShopExecution(ShopStateEnum stateEnum) {
this.state = stateEnum.getState();
this.stateInfo = stateEnum.getStateInfo();
}
// 店铺操作成功的时候使用的构造器
public ShopExecution(ShopStateEnum stateEnum, Shop shop) {
this.state = stateEnum.getState();
this.stateInfo = stateEnum.getStateInfo();
this.shop = shop;
}
// 店铺操作成功的时候使用的构造器
public ShopExecution(ShopStateEnum stateEnum, List<Shop> shopList) {
this.state = stateEnum.getState();
this.stateInfo = stateEnum.getStateInfo();
this.shopList = shopList;
}
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 Shop getShop() {
return shop;
}
public void setShop(Shop shop) {
this.shop = shop;
}
public List<Shop> getShopList() {
return shopList;
}
public void setShopList(List<Shop> shopList) {
this.shopList = shopList;
}
}
service层关于店铺注册的逻辑
1、service层需要事务的管理,首先是将店铺插入到数据库中,其次是返回店铺的id,然后根据id去创建出存储图片的文件夹,在这个文件夹下面去存储我们的图片,最后再把文件夹的地址更新回这条数据里面去。这样就需要四个步骤去执行,其中任何一条出错就需要回滚,所以事务就非常必要了。
2、创建ShopService的接口
package com.imooc.o2o.service;
import java.io.File;
import com.imooc.o2o.dto.ShopExecution;
import com.imooc.o2o.entity.Shop;
public interface ShopService {
ShopExecution addShop(Shop shop, File shopImg);
}
3、创建ShopServiceImpl实现接口
3.1、首先检查传入的shop是否为空,为空抛出异常,这里的NULL_SHOP在之前的枚举类没有定义,先去定义
image.png
3.2、除了判断shop是否为空还要判断shop的关联area,shopcategory等是否为空,
4、添加事务的标签
5、添加店铺的步骤,因为这些步骤有可能出错,所以写在try catch中
5.1初始化必要的参数
5.2添加店铺的信息(注入ShopDao)
5.3判断添加是否有效
6、为什么是抛出runtimeexception而不是exception原因是只有抛出runtimeexception事务才会终止并回滚,
7、添加成功后就去判断shopImg是否为空,不为空就存储到相关的目录里面,
7.1addshopimg方法需要两个参数,一个shop因为需要shopid去创建图片的目录,还有一个是shopimg这个文件流去存储到相关的目录里面。存储成功之后,addshopimg这个方法就会将生成好的图片地址给更新到shop实体类里面去。
7.2得到图片的地址
题外话:对于java的所有方法来说,它的参数都是值传递形式传递到函数里面去。对于基本类型来说它传递的是基本类型的字面量的拷贝,在函数内对这个值得改变是影响不了外面的,如果参数是引用类型的话,它传递的是参数所引用对象堆中地址值的拷贝,所以在方法内对它改变是能影响到外面的。所以shop在addshopimg方法中更新了图片的地址,我们在外面是可以取到的。
8.更新店铺的图片地址
9、实现addshopImg方法
9.1、获取shop图片目录的相对值路径
10、最后再将addShop方法返回ShopExecution(ShopStateEnum.CHECK, shop);成功的信息
package com.imooc.o2o.service.impl;
import java.io.File;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.imooc.o2o.Exceptions.ShopOperationException;
import com.imooc.o2o.dao.ShopDao;
import com.imooc.o2o.dto.ShopExecution;
import com.imooc.o2o.entity.Shop;
import com.imooc.o2o.enums.ShopStateEnum;
import com.imooc.o2o.service.ShopService;
import com.imooc.o2o.util.ImageUtil;
import com.imooc.o2o.util.PathUtil;
@Service
public class ShopServiceImpl implements ShopService {
@Autowired
private ShopDao shopDao;
@Transactional
@Override
public ShopExecution addShop(Shop shop, File shopImg) {
// 空值判断
if (shop == null) {
return new ShopExecution(ShopStateEnum.NULL_SHOP);
}
if (shop.getArea() == null) {
return new ShopExecution(ShopStateEnum.NULL_SHOP);
}
if (shop.getOwner() == null) {
return new ShopExecution(ShopStateEnum.NULL_SHOP);
}
if (shop.getShopCategory() == null) {
return new ShopExecution(ShopStateEnum.NULL_SHOP);
}
try {
// 给店铺信息赋予初始值
shop.setEnableStatus(0);
shop.setCreateTime(new Date());
shop.setLastEditTime(new Date());
// 添加店铺信息
int effectedNum = shopDao.insertShop(shop);
if (effectedNum <= 0) {
throw new ShopOperationException("店铺创建失败");
} else {
if (shopImg != null) {
// 存储图片
try {
addShopImg(shop, shopImg);
} catch (Exception e) {
throw new ShopOperationException("shopaddImg error" + e.getMessage());
}
// 更新店铺图片地址
effectedNum = shopDao.updateShop(shop);
if (effectedNum <= 0) {
throw new ShopOperationException("更新图片地址失败");
}
}
}
} catch (Exception e) {
throw new ShopOperationException("addshop error" + e.getMessage());
}
return new ShopExecution(ShopStateEnum.CHECK, shop);
}
private void addShopImg(Shop shop, File shopImg) {
// 获取shop图片目录的相对值路径
String dest = PathUtil.getShopImgPath(shop.getShopId());
String shopImgAddr = ImageUtil.generateThumbnail(shopImg, dest);
shop.setShopImg(shopImgAddr);
}
}
11这里抛出的异常都是runtimeexception,其实并不是规范的写法,规范的写法是在创建一个exceptions package里面封装的是我们业务的异常,
这样只是对Runtimeexception做了一层很薄的封装,这样子做的意义在于我们看到ShopOperationException异常的时候能够知道是跟店铺相关的。
image.png
12最后把service层的addShop方法里的RuntimeException替换成这个就行了。
对addShop进行单元测试
public class ShopDaoTest extends BaseTest {
@Autowired
private ShopDao shopDao;
@Test
@Ignore
public void testInsertShop() {
Shop shop = new Shop();
PersonInfo owner = new PersonInfo();
ShopCategory shopCategory = new ShopCategory();
Area area = new Area();
owner.setUserId(1L);
area.setAreaId(2L);
shopCategory.setShopCategoryId(1L);
shop.setArea(area);
shop.setOwner(owner);
shop.setShopCategory(shopCategory);
shop.setShopName("测试的店铺");
shop.setShopAddr("test");
shop.setShopDesc("test");
shop.setShopImg("test");
shop.setPhone("test");
shop.setAdvice("审核中");
shop.setEnableStatus(1);
shop.setCreateTime(new Date());
int effectedNum = shopDao.insertShop(shop);
assertEquals(1, effectedNum);
}
@Test
public void testUpdateShop() {
Shop shop = new Shop();
shop.setShopId(1L);
shop.setShopAddr("测试地址");
shop.setShopDesc("测试描述");
shop.setLastEditTime(new Date());
int effectedNum = shopDao.updateShop(shop);
assertEquals(1, effectedNum);
}
可以看到生成了图片数据库也添加了。
实现controller层
1、导入jar包,将实体类转换为json,将json转换为实体类
image.png
2、实现工具类,解析request里面参数。所以在util package下创建一个
image.png
package com.imooc.o2o.util;
import javax.servlet.http.HttpServletRequest;
/**
* 负责HttpServletRequest请求的参数
* @author ljs
*
*/
public class HttpServletRequestUtil {
//把请求参数里的字符串转换为整数
public static int getInt(HttpServletRequest request, String key) {
try {
return Integer.decode(request.getParameter(key));
}catch(Exception e) {
return -1;
}
}
public static Long getLong(HttpServletRequest request, String key) {
try {
return Long.valueOf(request.getParameter(key));
}catch(Exception e) {
return -1L;
}
}
public static Double getDouble(HttpServletRequest request, String key) {
try {
return Double.valueOf(request.getParameter(key));
}catch(Exception e) {
return -1d;
}
}
public static Boolean getBoolean(HttpServletRequest request, String key) {
try {
return Boolean.valueOf(request.getParameter(key));
}catch(Exception e) {
return false;
}
}
public static String getString(HttpServletRequest request, String key) {
try {
String result = request.getParameter(key);
if(result!=null) {
result = result.trim();
}
if("".equals(result)) {
result = null;
}
return result;
}catch(Exception e) {
return null;
}
}
}
3、在web层创建shopadmin package,管理后台的相关controller都在这里,
request参数代表客户端的请求。
@Controller
@RequestMapping("/shopadmin")
public class ShopManagementController {
@ResponseBody
@RequestMapping(value="/registershop", method=RequestMethod.GET)
private Map<String, Object> registerShop(HttpServletRequest request){
//返回值
Map<String, Object> modelMap = new HashMap<String, Object>();
//1、接收前端传过来的Shop字符串信息,包括店铺信息以及图片信息,并转换为shop实体类
//shopStr是跟前端约定好的key值
String shopStr = HttpServletRequestUtil.getString(request, "shopStr");
//接收完成后需要做转换
ObjectMapper mapper = new ObjectMapper();
//定义一个shop实体类去接收
Shop shop = null;
//开始转换
try {
shop = mapper.readValue(shopStr, Shop.class);
}catch(Exception e) {
//转换失败就返回modelMap,告诉前台说转换失败
modelMap.put("success", false);
modelMap.put("errMsg", e.getMessage());
return modelMap;
}
//接收图片
CommonsMultipartFile shopImg = null;
//文件上传解析器,去解析request里面的文件信息
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
//2、注册店铺
//3、返回结果
}
}
网友评论