项目实战05——店铺管理

本文详细介绍了在一个SSM项目中,如何优先开发店家管理系统,包括店铺信息的增删改查功能,以及使用Mybatis进行数据库操作和单元测试。同时,文章还涵盖了图片处理工具Thumbnailator的集成和使用,以及在服务层和控制器层的实现。在店家管理系统中,重点讨论了店铺注册和更新的实现过程,以及遇到的中文乱码问题及其解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        该SSM项目的三个子系统:前端展示系统、店家管理系统、超级管理员系统。优先级应该为店家管理系统->前端展示系统->超级管理员系统,店家是最有可能带来收益的,而且只有店家管理添加信息,前端展示系统才有数据源,而超级管理员可以用手工录入的方式替代。接下来优先对店家管理系统开发。

        店家管理系统有两个模块:店铺和商品。因为有店铺才有商品,所以我们先进行店铺的增删改查功能实现。


第一个目标,就是实现店铺信息的增删改查功能。首先就是店铺注册,要实现店铺注册就需要完成以下目标:

  • 连接数据库
  • Mybatis数据库表映射关系的配置
  • dao -> service -> controller层代码的编写,Junit的使用
  • Session,图片处理工具Thumbnailator的使用
  • suimobile前端涉及与开发

首先在dao层建立ShopDao接口:

public interface ShopDao {

    /**
     * 新增店铺
     *
     * @param shop
     * @return
     */
    int insertShop(Shop shop);

}

由于是由mybatis来实现的,所以需要在resources/mapper先生成相应的ShopDao.xml配置文件:

 <insert id="insertShop" useGeneratedKeys="true" keyColumn="shop_id"
            keyProperty="shopId">
        INSERT INTO
            tb_shop(owner_id, area_id, shop_category_id,
                    shop_name, shop_desc, shop_addr,
                    phone, shop_img, priority,
                    create_time, last_edit_time, enable_status,
                    advice)
        VALUES
        (#{owner.userId},#{area.areaId},#{shopCategory.shopCategoryId},#{shopName},
         #{shopDesc},#{shopAddr},#{phone},#{shopImg},#{priority},
         #{createTime},#{lastEditTime}, #{enableStatus},#{advice})
    </insert>

然后开始UT(单元)测试,在test/java/com/imooc/o2o/dao下创建ShopDaoTest测试类,

public class ShopDaoTest extends BaseTest {
	@Autowired
	private ShopDao shopDao;

	@Test
	public void testInsertShop() {
		Shop shop = new Shop();
		PersonInfo owner = new PersonInfo();
		Area area = new Area();
		ShopCategory shopCategory = new ShopCategory();
		owner.setUserId(1L);
		area.setAreaId(2);
		shopCategory.setShopCategoryId(1L);
		shop.setOwner(owner);
		shop.setArea(area);
		shop.setShopCategory(shopCategory);
		shop.setShopName("测试的店铺");
		shop.setShopDesc("test");
		shop.setShopAddr("test");
		shop.setPhone("test");
		shop.setShopImg("test");
		shop.setCreateTime(new Date());
		shop.setEnableStatus(1);
		shop.setAdvice("审核中");
		int effectedNum = shopDao.insertShop(shop);
		assertEquals(1, effectedNum);
	}

}

然后运行,测试成功,该功能已完成。


 接下来完成店铺更新模块,步骤同上,先在ShopDao接口中添加更新方法:

 /**
 * 更新店铺信息
 * @param shop
 * @return
 */
int updateShop(Shop shop);

然后写入相应的mapper配置,在ShopDao.xml配置文件中添加如下信息:

<update id="updateShop" parameterType="com.imooc.o2o.entity.Shop">
    update tb_shop
    <set>
        <if test="shopName != null">shop_name=#{shopName},</if>
        <if test="shopDesc != null">shop_desc=#{shopDesc},</if>
        <if test="shopAddr != null">shop_addr=#{shopAddr},</if>
        <if test="phone != null">phone=#{phone},</if>
        <if test="shopImg != null">shop_img=#{shopImg},</if>
        <if test="priority != null">priority=#{priority},</if>
        <if test="lastEditTime != null">last_edit_time=#{lastEditTime},</if>
        <if test="enableStatus != null">enable_status=#{enableStatus},</if>
        <if test="advice != null">advice=#{advice},</if>
        <if test="area != null">area_id=#{area.areaId},</if>
        <if test="shopCategory != null">shop_category_id=#{shopCategory.shopCategoryId}            
  </if>
        </set>
        where shop_id=#{shopId}
</update>

然后添加测试类,在ShopDao测试类中,添加testUpdateShop()方法,由于我们的测试类中有上一个模块testInsertShop()方法,我们在testInsertShop方法上加上一个@Ignore注解,然后就可以正常进行下一个test方法的运行,

public void testUpdateShop() {
		Shop shop = new Shop();
		shop.setShopId(1L);
		shop.setShopDesc("测试描述");
		shop.setShopAddr("测试地址");
		shop.setLastEditTime(new Date());
		int effectedNum = shopDao.updateShop(shop);
		assertEquals(1, effectedNum);
	}

控制台显示测试成功,并且数据库相应数据已经改变,该模块基本功能完成。


接下来进行Thumbnailator类库的图片处理,首先在mvnrepository.com里下载相应的jar包,然后复制粘贴到项目的pom文件里,

 <!-- 图片处理 -->
 <!-- https://mvnrepository.com/artifact/net.coobird/thumbnailator -->
 <dependency>
    <groupId>net.coobird</groupId>
    <artifactId>thumbnailator</artifactId>
    <version>0.4.8</version>
 </dependency>

然后就成功引入相关jar包。然后再util文件下创建ImageUtil.java工具类:

public class ImageUtil {
	public static void main(String[] args) throws IOException {
		String basePath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
		Thumbnails.of(new File("D:\\work\\imooc\\image\\lanqiu.jpg")).size(200, 200)
				.watermark(Positions.BOTTOM_RIGHT, ImageIO.read(new File(basePath + "/watermark.jpg")), 0.25f)
				.outputQuality(0.8f).toFile("D:\\work\\imooc\\image\\lanqiunew.jpg");
	}

}

然后我们在resource文件下添加watermark.jpg水印照片,然后运行上述方法,结果控制台报错:

显示无法读取文件,找了一下为中文乱码问题,再basePath变量下加上

basePath = URLDecoder.decode(basePath,"utf-8");

 成功解决。效果为将本来读取的照片加上watermark水印。然后在util包下再一个获取图片路径的类,PathUtil.java:

public class PathUtil {
    private static String seperator = System.getProperty("file.separator");
    public static String getImgBasePath() {
        String os = System.getProperty("os.name");
        String basePath = "";
        if(os.toLowerCase().startsWith("win")){
            basePath = "D:/projectdev/image/";
        }else {
            basePath="/home/xiangze/image/";
        }
        basePath = basePath.replace("/",seperator);
        return basePath;
    }
    public static String getShopImagePath(long shopId){
        String imagePath = "/upload/item/shop/" + shopId +"/";
        return imagePath;
    }
}

 其中的getImgBasePath()方法为获取图片路径,而getShopImagePath()为获取商品图片的路径,因为我们需要把商品图片放在各自的路径下。然后,在ImageUtil下写入其他方法,完善这个工具类,这里就不贴详细的代码了。


 在进行service层的代码编写前,我们先准备好添加店铺的返回类型,我们在dto里添加这个类型。至于为什么不直接用Shop这个实体类来返回,那是因为我们在操作Shop时,会有一个状态(成功/失败),这些都是要记录和返回给controller层处理的。

所以我们在dto下创建ShopExecution.java类:

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;
	}

    // 属性的get、set方法
    // 。。。。。。

}

其中ShopStateEnum为商铺的状态,为一个枚举类,所以在enums下创建ShopStateEnum类,

public enum ShopStateEnum {
	CHECK(0, "审核中"), OFFLINE(-1, "非法店铺"), SUCCESS(1, "操作成功"), PASS(2, "通过认证"), INNER_ERROR(-1001,
			"内部系统错误"), NULL_SHOPID(-1002, "ShopId为空"),NULL_SHOP(-1003, "shop信息为空");
	private int state;
	private String stateInfo;

	private ShopStateEnum(int state, String stateInfo) {
		this.state = state;
		this.stateInfo = stateInfo;
	}

	/**
	 * 依据传入的state返回相应的enum值
	 */
	public static ShopStateEnum stateOf(int state) {
		for (ShopStateEnum stateEnum : values()) {
			if (stateEnum.getState() == state) {
				return stateEnum;
			}
		}
		return null;
	}

    //只设置get方法,因为我们不希望有set方法改变状态值
	public int getState() {
		return state;
	}

	public String getStateInfo() {
		return stateInfo;
	}

}

 这里在准备好基础类型后,我们开始进行正式的service层编码。首先,我们需要在src/main/java/com/imooc/o2o下创建exception包,用来存放ShopOperationException.java类,用来包装由于商铺操作出错而抛出的异常。

public class ShopOperationException extends RuntimeException{
    //序列化
    private static final long serialVersionUID = 2361446884822298905L;

    public ShopOperationException(String msg) {
        super(msg);
    }
}

然后在service层下实现ShopService接口:

public interface ShopService {

	/**
	 * 注册店铺信息,包括图片处理
	 *
	 * @param shop
	 * @param shopImgInputStream
	 * @param fileName
	 * @return
	 * @throws ShopOperationException
	 */
	ShopExecution addShop(Shop shop, InputStream shopImgInputStream, String fileName) throws ShopOperationException;
}

然后在service/impl下编写ShopServiceImpl.java的实现类,

@Service
public class ShopServiceImpl implements ShopService {
    @Autowired
    private ShopDao shopDao;

    @Override
    @Transactional
    public ShopExecution addShop(Shop shop, InputStream shopImgInputSteam, String fileName)throws ShopOperationException{
        // 空值判断
        if (shop == 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 (shopImgInputSteam != null) {
                    // 存储图片
                    try {
                        addShopImg(shop, shopImgInputSteam, fileName);
                    } catch (Exception e) {
                        throw new ShopOperationException("addShopImg 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, InputStream shopImgInputStream, String fileName) {
        // 获取shop图片目录的相对值路径
        String dest = PathUtil.getShopImagePath(shop.getShopId());
        String shopImgAddr = ImageUtil.generateThumbnail(shopImgInputStream, fileName, dest);
        shop.setShopImg(shopImgAddr);
    }

}

 但是这里图片处理涉及到很多路径,应该很容易出错。接下来开始测试,在test/java/com/imooc/o2o/service下创建ShopServiceTest类,

public class ShopServiceTest extends BaseTest {
    @Autowired
    private ShopService shopService;

    @Test
    public void testAddShop() throws ShopOperationException, FileNotFoundException {
        Shop shop = new Shop();
        PersonInfo owner = new PersonInfo();
        Area area = new Area();
        ShopCategory shopCategory = new ShopCategory();
        owner.setUserId(1L);
        area.setAreaId(2);
        shopCategory.setShopCategoryId(1L);
        shop.setOwner(owner);
        shop.setArea(area);
        shop.setShopCategory(shopCategory);
        shop.setShopName("测试的店铺3");
        shop.setShopDesc("test3");
        shop.setShopAddr("test3");
        shop.setPhone("test3");
        shop.setCreateTime(new Date());
        shop.setEnableStatus(0);
        shop.setAdvice("审核中");
        File shopImg = new File("D:\\work\\imooc\\image\\lanqiu.jpg");
        InputStream is = new FileInputStream(shopImg);
        ShopExecution se = shopService.addShop(shop, is, shopImg.getName());
        assertEquals(ShopStateEnum.CHECK.getState(), se.getState());
    }
}

运行后果不其然出错了。

这个bug跟上个编写图片类的时候出现的bug是一样的,所以我们看一下是不是因为中文乱码的问题。 找不出这个bug的问题,但是本系列最主要是记录整体流程,如果能删除图片处理的就好了,可惜不能,说这个系列似乎要在这里夭折了。我想要尽快看完该项目,故没有太多时间去改bug同时去记录,所以后期用简短模式记录。


接下来是controller层,我们在web里创建shopadmin文件,在该文件下创建ShopManagementController.java,并且加入@Controller注释说明为controller层,然后用@RequestMapping指定访问路径,并且将请求封装成HttpServletRequest对象,我们在util包下创建HttpServletRequest.java。然后就可以完成controller层的设计。

接下来使用SUI mobile前端技术,在相关网站选择需要的组件,点击鼠标右键查看源码,就可以将相关代码复制到html文件中。我们在src/main/webapp/WEB-INF/html/shop下创建shopoperation.html文件,将源码复制过去即可。而该页面需要相关的css和js配置文件,所以我们可以在SUI mobile网站上找到CDN地址,替换掉源码中的css和js配置文件,就可以实现一个网站基本组件。

由于我们启动tomcat后无法直接从localhost:8080/o2o/WEB-INF/html/shop/shopoperation.html去直接访问这个页面,所以我们需要在controller层的shopadmin下创建一个ShopAdminController来完成一个注解,如下:

public class ShopAdminController {
	@RequestMapping(value = "/shopoperation")
	public String shopOperation() {
		// 转发至店铺注册/编辑页面
		return "shop/shopoperation";
	}
}

为什么我们不用指定到html文件呢,因为我们在spring-web.xml配置时已经指定了前后缀,


然后再webapp文件下创建resources文件,并且创建js/shop文件,创建shopoperation.js文件,然后进行js配置文件的编写。

然后我们需要完成商铺类别的功能,从dao层,到resources的mapper文件下的mabatis映射,再到service层的接口和实现,然后controller层定义好路由。接下来的课程都为了商铺系统的某个功能从dao层一直往下开发的这么一个流程,所以我们直接跳过,来到缓存技术,开启下一章。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值