Java Project

  1. 第一章 简单的 SSM 项目搭建
    1. 1.1、环境配置
    2. 1.2、创建 Maven project 的步骤
  2. 第二章 系统功能模块
    1. 2.1、模块功能说明
      1. 2.1.1、前端展示系统
      2. 2.1.2、店家系统
      3. 2.1.3、超级管理员系统
  3. 2.2、模块功能对象
    1. 2.2.1、实体类关系图
    2. 2.2.2、实体类代码
  • 2.3、代码实现
    1. 2.3.1、创建相应的类
    2. 2.3.2、创建数据库表
  • 第三章 项目配置
    1. 3.1、 项目目录说明
    2. 3.2、创建 sources 资源包
    3. 3.3、创建 package
    4. 3.4、项目配置
      1. 3.4.1、配置思路:自下而上的配置。
      2. 3.4.2、依赖管理
      3. 3.4.3、 数据库的配置操作
      4. 3.4.4、 Spring 相关配置
  • 3.5 logback 日志配置
  • 第四章 代码实现
    1. 4.1、简单的 SSM 案例-Area
      1. 4.1.1、dao 层
      2. 4.1.2、Service 层
      3. 4.1.3、web 层
  • 第五章 店铺系统的实现
    1. 5.1、店铺的注册模块
      1. 5.1.1、dao 层代码
      2. 5.1.2、service 层代码
      3. 5.1.3、 Controller 层
      4. 5.1.4、 拓展
  • 5.2 店铺信息编辑模块
    1. 5.2.1 Dao 层
    2. 5.2.2 Service 层
    3. 5.2.3 Controller 层
  • 5.3 商品类别列表展示
    1. 5.3.1 dao 层
    2. 5.3.2 service 层
    3. 5.3.3 controller 层 ProductCategoryManagementController.java
    4. 5.3.3 Controller 层
  • 5.4 批量添加商品列表
    1. 5.4.1 dao 层
    2. 5.4.2 Service 层
    3. 5.4.3 Controller 层
  • 5.5 删除商品类别
    1. 5.5.1 dao 层
    2. 5.5.2 Serivce 层
    3. 5.5.3 Controller 层
  • 6、商品管理
    1. 6.1 商品添加, 商品详情图批量添加
      1. 6.1.1
      2. 6.1.2 service 层
      3. 6.1.3 controller 层
  • 6.2 商品编辑
    1. 6.2.1、dao 层
    2. 6.2.2、Service 层
    3. 6.2.3 Controller 层 ProductCategoryManagementController.java
  • 6.3 商品列表展示
  • 第七章 首页
    1. 7.1
  • 总结
    1. SSM 框架实现功能的步骤
  • Junit
    1. Junit 测试步骤
      1. 1、定义一个基类
      2. 2、定义一个实现类
  • Junit 拓展
    1. Junit 测试回环
    2. Junit 顺序执行
  • SSM 重点知识
  • 第一章 简单的 SSM 项目搭建

    1.1、环境配置

    JDK8, Maven3.3.9, MySQL8.0.12, Tomcat8, Eclipse


    1.2、创建 Maven project 的步骤

    1. 选择 maven-archetype-webapp 类型。
    2. 输入项目名称。
    3. 引入 tomcat 的一个 jar 包。选择项目>属性>Java Build Path>Libraries>Add Library>Server Runtime>Apache Tomcat v8.0。
    4. 修改 pom.xml 的 JRE 版本为 1.8,再 updata 项目。
        <finalName>o2o</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                	<encoding>UTF-8</encoding>
                	<source>1.8</source>
                	<target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    language-xml复制代码
    1. 创建一个 maven 结构的文件夹,并修改属性。创建 src/test/resources 文件夹,并修改工程属性>Java Build Path> Source > resources 的 output 属性。
    2. 修改 Dynamic Web Module 的版本,提高性能。查看,项目属性>Project Facetes;修改, vim “projectsrc”/.settings/org.eclipse.wst.common.project.facet.core.xml;修改, jst.web 的 version。
    3. 修改 web.xml 的规范头(因为 DWM 版本提高了)。
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
        version="3.1" metadata-complete="true">
    language-xml复制代码
    1. 运行项目。在 Tomcat 中 add 项目;再 Tomcat 中 clean 项目;start 项目.
    2. 在 web.xml 指定默认的页面。
      <welcome-file-list>
      	<welcome-file>index.js</welcome-file>
      	<welcome-file>index.html</welcome-file>
      </welcome-file-list>
    language-xml复制代码

    第二章 系统功能模块

    2.1、模块功能说明

    2.1.1、前端展示系统

    1. 头条展示
    2. 店铺类别展示
    3. 区域展示
    4. 店铺(列表展示,查询,详情)
    5. 商品(列表展示,查询,详情)

    2.1.2、店家系统

    1. Local 账号维护
    2. 微信账号维护
    3. 店铺信息维护
    4. 权限验证
    5. 商品类别维护

    2.1.3、超级管理员系统

    1. 头条信息维护
    2. 店铺类别信息维护
    3. 区域信息维护
    4. 权限验证
    5. 店铺管理
    6. 用户管理

    2.2、模块功能对象

    2.2.1、实体类关系图


    2.2.2、实体类代码

    1. 区域
    2. 用户信息
    3. 微信账号
    4. 本地账号
    5. 头条
    6. 店铺类别
    7. 店铺
    8. 商品
    9. 详情图片
    10. 商品类别

    2.3、代码实现

    2.3.1、创建相应的类

    2.3.2、创建数据库表


    第三章 项目配置

    3.1、 项目目录说明

    1. src/main/java:业务代码
    2. src/main/resources:项目资源文件.例如:spring, mapper 等。
    3. src/test/java:单元测试代码
    4. src/test/resources:单元测试的配置文件:
    5. src/main/webapp:存放前端的静态资源。例如 jsp,html,js,css 等
    6. src/main/webapp/resources:静态资源文件。例如 js,css,img
    7. src/main/webapp/WEB-INF:外部浏览器无法访问的目录
    8. src/main/webapp/WEB-INF/web.xml:用于初始化配置信息。例如 welcoml page,初始化 sevlet,servlet mapper
    9. target:存放项目构建后的文件和目录,jar,war,编译的 class 文件等。

    3.2、创建 sources 资源包

    1. src/main/resources/spring:存放 spring 相关的配置信息
    2. src/main/resources/mapper:存放 dao 中每个方法对应的 service,不需要写 dao 的实现类,由 mybatis 自动实现,日志配置文件。

    3.3、创建 package

    1. com.imooc.o2o.entity:实体类包
    2. com.imooc.o2o.web:controller 层,存放 controller 控制器
    3. com.imooc.o2o.service:业务逻辑层,
    4. com.imooc.o2o.service.impl:业务逻辑层的实现
    5. com.imooc.o2o.dao:dao 层,与数据库打交道,存放数据库的操作,也可以是文件的对象操作或者缓存等。与数据相关的操作。
    6. com.imooc.o2o.dto:弥补 entity 的不足,例如 entity/Product 需要返回一个商品列表和是否返回成功地标识,那这个时候就 7. 需要 dto 进行二次封装,流入 status,productlist
    7. com.imooc.o2o.enums:枚举
    8. com.imooc.o2o.intercaptor:拦截器代码
    9. com.imooc.o2o.util:通用工具类

    3.4、项目配置

    3.4.1、配置思路:自下而上的配置。

    1. 在 pom.xml 指定项目所需要的 jar 包;
    2. 在 jdbc.properties 指定数据库的连接方式和创建 mybatis-config.xml 对 mybatis 进行配置;
    3. 创建 spring-dao.xml 将 jdbc.proerties 和 mybatis-config.xml 加载进来了并创建连接池同时配置好 mybatis 和数据库交互的方式;
    4. 创建 spring-service.xml 做事务管理,将 spring 在 dao 层配置好的 dataSource 连接池给注入到事务管理器中,便于 service 层做操作;
    5. 创建 spring-web.xmlding 定义 controller 的行为;
    6. 在 web.xml 将 spring-dispatcher 当做一个 servlet 注册到里面去来相应请求,同时将 spring-*.xml 的配置文件注册进来整合在一起。

    3.4.2、依赖管理

    1. Maven jar 链接
    2. pom.xml
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>com.imooc</groupId>
      <artifactId>o2o</artifactId>
      <packaging>war</packaging>
      <version>0.0.1-SNAPSHOT</version>
      <name>o2o Maven Webapp</name>
      <url>http://maven.apache.org</url>
      <properties>
      	<spring.version>4.3.7.RELEASE</spring.version>
      </properties>
      <dependencies>
      	<!-- 测试相关的jar包 -->
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.12</version>
          <!-- 作用于test环境 -->
          <scope>test</scope>
        </dependency>
        <!-- 日志相关的jar包 -->
    	<dependency>
    	    <groupId>ch.qos.logback</groupId>
    	    <artifactId>logback-classic</artifactId>
    	    <version>1.2.3</version>
    	</dependency>
    	<!-- Spring -->
    	<!-- 1)包含Spring 框架基本的核心工具类。Spring 其他组件都要使用到这个包里的类,是其他组件的基本核心 -->
    	<dependency>
    	    <groupId>org.springframework</groupId>
    	    <artifactId>spring-core</artifactId>
    	    <version>4.3.7.RELEASE</version>
    	    <!-- <version>${spring.version}</version> -->
    	</dependency>
    	<!-- 2)这个jar 文件是所有应用都要使用的,它包含访问配置文件,创建和管理bean 以及进行Inversion of Context
    		/ Dependency Injection(Ioc/DI)操作相关的所有类。如果应用只需基本的Ioc/DI支持,引入spring-core及spring-beans.jar文件就可以了 -->
    	<dependency>
    	    <groupId>org.springframework</groupId>
    	    <artifactId>spring-beans</artifactId>
    	    <version>${spring.version}</version>
    	</dependency>
    	<!-- 3)这个jar 文件为Spring 核心提供了大量拓展。可以找到使用Spring ApplicationContext 特效时所需的全部
    		所需的全部类,instrumentation 组件以及校验Validation 方面的相关类。 -->
    	<dependency>
    	    <groupId>org.springframework</groupId>
    	    <artifactId>spring-context</artifactId>
    	    <version>4.3.7.RELEASE</version>
    	</dependency>
    	<!-- 4)这个jar 文件包含对SPring 对JDBC 数据访问进行封装的所有类。 -->
    	<dependency>
    	    <groupId>org.springframework</groupId>
    	    <artifactId>spring-jdbc</artifactId>
    	    <version>4.3.7.RELEASE</version>
    	</dependency>
    	<!-- 5)为JDBC、Hibernate、JDO、JPA 等提供的一致性的声明式和编程式事务管理。 -->
    	<dependency>
    	    <groupId>org.springframework</groupId>
    	    <artifactId>spring-tx</artifactId>
    	    <version>4.3.7.RELEASE</version>
    	</dependency>
    	<!-- 6)Spring web 包含Web 应用开发时,用到Spring 框架时所需的核心类,包括自动载入WebApplicationContext
    		 文件上传的支持类,filter类,和大量的工作辅助类-->
    	<dependency>
    	    <groupId>org.springframework</groupId>
    	    <artifactId>spring-web</artifactId>
    	    <version>4.3.7.RELEASE</version>
    	</dependency>
    	<!-- 7)包含Spring MVC框架相关的所有类。dispathservlet -->
    	<dependency>
    	    <groupId>org.springframework</groupId>
    	    <artifactId>spring-webmvc</artifactId>
    	    <version>4.3.7.RELEASE</version>
    	</dependency>
    	<!-- 8)Spring test 对Junit 等测试框架的简单封装 -->
    	<dependency>
    	    <groupId>org.springframework</groupId>
    	    <artifactId>spring-test</artifactId>
    	    <version>4.3.7.RELEASE</version>
    	    <scope>test</scope>
    	</dependency>
    	<!-- Servlet web -->
    	<dependency>
    	    <groupId>javax.servlet</groupId>
    	    <artifactId>javax.servlet-api</artifactId>
    	    <version>3.1.0</version>
    	</dependency>
    	<!-- json解析 -->
    	<dependency>
    	    <groupId>com.fasterxml.jackson.core</groupId>
    	    <artifactId>jackson-databind</artifactId>
    	    <version>2.8.7</version>
    	</dependency>
    	<!-- Map工具类 对标准java Collection的扩展 spring-core.jar需commons-collections.jar -->
    	<dependency>
    	    <groupId>commons-collections</groupId>
    	    <artifactId>commons-collections</artifactId>
    	    <version>3.2</version>
    	</dependency>
    	<!-- DAO:MyBatis -->
    	<dependency>
    	    <groupId>org.mybatis</groupId>
    	    <artifactId>mybatis</artifactId>
    	    <version>3.4.2</version>
    	</dependency>
    	<dependency>
    	    <groupId>org.mybatis</groupId>
    	    <artifactId>mybatis-spring</artifactId>
    	    <version>1.3.1</version>
    	</dependency>
    	<!-- 数据库,支持JDBC -->
       <dependency>
    	    <groupId>mysql</groupId>
    	    <artifactId>mysql-connector-java</artifactId>
    	    <version>8.0.12</version>
    	</dependency>
    	<!-- 链接池 -->
    	<dependency>
    	    <groupId>c3p0</groupId>
    	    <artifactId>c3p0</artifactId>
    	    <version>0.9.1.2</version>
    	</dependency>
      </dependencies>
    
      <build>
        <finalName>o2o</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                <encoding>UTF-8</encoding>
                <source>1.8</source>
                <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
      </build>
    </project>
    language-xml复制代码

    3.4.3、 数据库的配置操作

    1. 创建 src/main/resources/jdbc.properties
    jdbc.driver=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/o2o?useUnicode=true&characterEncoding=utf8&verifyServerCertificate=false&useSSL=false&allowPublicKeyRetrieval=true
    jdbc.username=root
    jdbc.password="密码"
    language-properties复制代码
    1. 配置 src/main/resources/mybatis-config.xml
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    	<!-- 配置全局属性 -->
        <settings>
            <!-- 使用jdbc多的getGeneratedKeys获取数据库自增主键值 -->
            <setting name="useGeneratedKeys" value="true" />
    
            <!-- 使用列标签替换列别名 默认:true-->
            <setting name="useColumnLabel" value="true"/>
    
            <!-- 开启驼峰命名转换:Table{create_time} -> Entity{createTime}  -->
            <setting name="mapUnderscoreToCamelCase" value="true"/>
        </settings>
    </configuration>
    language-xml复制代码

    3.4.4、 Spring 相关配置

    1. Dao 层: src/main/resources/spring/spring-dao.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    	<!-- 配置整合mybatis过程 -->
    	<!-- 1.配置数据库相关参数properties的属性:${url} -->
    	<context:property-placeholder location="classpath:jdbc.properties" />
    
    	<!-- 2.数据库连接池 -->
    	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    		<!-- 配置连接池属性 -->
    		<property name="driverClass" value="${jdbc.driver}" />
    		<property name="jdbcUrl" value="${jdbc.url}" />
    		<property name="user" value="${jdbc.username}" />
    		<property name="password" value="${jdbc.password}" />
    		<!-- c3p0连接池的私有属性 -->
    		<property name="maxPoolSize" value="30" />
    		<property name="minPoolSize" value="10" />
    		<!-- 关闭连接后不自动commit -->
    		<property name="autoCommitOnClose" value="false" />
    		<!-- 获取连接超时时间 -->
    		<property name="checkoutTimeout" value="10000" />
    		<!-- 当获取连接失败重试次数 -->
    		<property name="acquireRetryAttempts" value="2" />
    	</bean>
    
    	<!-- 3.配置SqlSessionFactory对象,用于创建数据库连接池的对象 -->
    	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    		<!-- 注入数据库连接池 -->
    		<property name="dataSource" ref="dataSource" />
    		<!-- 配置MyBaties全局配置文件:mybatis-config.xml -->
    		<property name="configLocation" value="classpath:mybatis-config.xml" />
    		<!-- 扫描entity包 使用别名 -->
    		<property name="typeAliasesPackage" value="com.imooc.o2o.entity" />
    		<!-- 扫描sql配置文件:mapper需要的xml文件 -->
    		<property name="mapperLocations" value="classpath:mapper/*.xml" />
    	</bean>
    
    	<!-- 4.配置扫描Dao接口包,动态实现Dao接口,注入到spring容器中 -->
    	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    		<!-- 注入sqlSessionFactory -->
    		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    		<!-- 给出需要扫描Dao接口包 -->
    		<property name="basePackage" value="com.imooc.o2o.dao" />
    	</bean>
    </beans>
    language-xml复制代码
    1. Service 层: src/main/resources/spring/spring-service.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">
        <!-- 扫描service包下所有使用注解的类型 -->
        <context:component-scan base-package="com.imooc.o2o.service" />
    
        <!-- 配置事务管理器,事务原子性等 -->
        <bean id="transactionManager"
            class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- 注入数据库连接池 -->
            <property name="dataSource" ref="dataSource" />
        </bean>
    
        <!-- 配置基于注解的声明式事务 -->
        <tx:annotation-driven transaction-manager="transactionManager" />
    </beans>
    language-xml复制代码
    1. Web 层: src/main/resources/spring/spring-web.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:mvc="http://www.springframework.org/schema/mvc"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans
    	http://www.springframework.org/schema/beans/spring-beans.xsd
    	http://www.springframework.org/schema/context
    	http://www.springframework.org/schema/context/spring-context.xsd
    	http://www.springframework.org/schema/mvc
    	http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
    	<!-- HandlerMapping 无需配置,SpringMVC可以默认启动,DefaultAnnotationHandlerMapping annotation-driven HandlerMapping -->
    	<!-- 配置SpringMVC -->
    	<!-- 1.开启SpringMVC注解模式 -->
    	<mvc:annotation-driven/>
    
    	<!-- 2.静态资源默认servlet配置 (1)加入对静态资源的处理:js,gif,png (2)允许使用"/"做整体映射 , 不会拦截,当为静态资源。 这两个都是处理静态资源的,区别可以理解成一个是指定一个自定义的serlvet来专门处理相应的静态资源,如果不指定 会默认找default名字的servlet 而<mvc:resources>的好处可以理解成是静态资源可以在我们项目中的任意位置配置,只需要将对应的位置声明即可 -->
    	<mvc:resources mapping="/resources/**" location="/resources/"/>
    	<mvc:default-servlet-handler/>
    
    	<!-- 3.定义视图解析器 -->
    	<!-- ViewResolver:视图解析器。可以配置多个 但是一定要将这个ViewResolver(InternalResourceViewResolver) 放到最后 -->
    	<!-- 解析json格式的传参和封装数据到页面,注意spring的版本和对应的配置方式 -->
    	<!-- spring-4.2以后 -->
    	<bean id="viewResolver"
    		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    		<property name="prefix" value="/WEB-INF/html/"></property>
    		<property name="suffix" value=".html"></property>
    	</bean>
    
    	<!-- 4.扫描web相关的bean -->
    	<!-- 激活组件扫描功能,扫描aop的相关组件组件 -->
    	<context:component-scan base-package="com.imooc.o2o.web"/>
    </beans>
    language-xml复制代码
    1. 将 sping 的配置整合在一起,web.xml
     <servlet>
      	<servlet-name>spring-dispatcher</servlet-name>
      	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      	<init-param>
      		<param-name>contextConfigLocation</param-name>
      		<param-value>classpath:spring/spring-*.xml</param-value>
      	</init-param>
      </servlet>
      <servlet-mapping>
      	<servlet-name>spring-dispatcher</servlet-name>
      	<!-- 默认匹配所有的请求 -->
      	<url-pattern>/</url-pattern>
      </servlet-mapping>
    language-xml复制代码

    3.5 logback 日志配置

    1. src/main/resources/logback.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration scan="true" scanPeriod="60 seconds"
    	debug="false">
    	<!-- 定义参数常liang -->
    	<!-- TRACE < DEBUG < INFO < WARN < ERROR -->
    	<!-- 常用 DEBUG、INFO 和 ERROR 就可以了 -->
    	<property name="log.level" value="debug"/>
    	<!-- 文件保留时间 -->
    	<property name="log.maxHistory" value="30" />
    	<!-- 日志存储的位置 -->
    	<property name="log.filePath"
    		value="${catalina.base}/logs/webapps" />
    	<!-- 日志格式:时间 线程 级别 那个类输出的日志信息 换行 -->
    	<property name="log.pattern"
    		value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{55} - %msg%n"/>
    
    	<!-- 输出到控制台的。日志输出媒介,控制台输出 -->
    	<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
    		<encoder>
    			<pattern>${log.pattern}</pattern>
    		</encoder>
    	</appender>
    
    	<!-- 以下是输出日志文件的 -->
    	<!-- DEBUG -->
    	<!-- 滚动日志 -->
    	<appender name="debugAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
    		<!-- 文件路径 -->
    		<file>${log.filePath}/debug.log</file>
    		<!-- 基于时间滚动,按天 -->
    		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    			<!-- 文件名称,在log.filePath路径下生产以 debug.日期.log.gz结尾的文件 -->
    			<fileNamePattern>${log.filePath}/debug/debug.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
    			<!-- 文件最大保存数量,轮询 -->
    			<maxHistory>${log.maxHistory}</maxHistory>
    		</rollingPolicy>
    		<!-- 将日志信息转化为字符串,将字符串输出到文件里 -->
    		<encoder>
    			<pattern>${log.pattern}</pattern>
    		</encoder>
    		<!-- 过滤器:过滤不是 debug.level的日志 -->
    		<filter class="ch.qos.logback.classic.filter.LevelFilter">
    			<level>DEBUG</level>
    			<!-- 如何时debug日志就记录下来 -->
    			<onMatch>ACCEPT</onMatch>
    			<!-- 如果不是debug日志就否定掉 -->
    			<onMismatch>DENY</onMismatch>
    		</filter>
    	</appender>
    
    	<!-- INFO -->
    	<!-- 文件滚动日志 -->
    	<appender name="infoAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
    		<!-- 文件路径 -->
    		<file>${log.filePath}/info.log</file>
    		<!-- 上面写满了,就使用下面的滚动文件 -->
    		<!-- 基于时间滚动,按天 -->
    		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    			<!-- 文件名称,在log.filePath路径下生产以 debug.日期.log.gz结尾的文件 -->
    			<fileNamePattern>${log.filePath}/info/info.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
    			<!-- 文件最大保存数量,轮询 -->
    			<maxHistory>${log.maxHistory}</maxHistory>
    		</rollingPolicy>
    		<!-- 将日志信息转化为字符串,将字符串输出到文件里 -->
    		<encoder>
    			<pattern>${log.pattern}</pattern>
    		</encoder>
    		<!-- 过滤器:过滤不是 debug.level的日志 -->
    		<filter class="ch.qos.logback.classic.filter.LevelFilter">
    			<level>INFO</level>
    			<!-- 如何时debug日志就记录下来 -->
    			<onMatch>ACCEPT</onMatch>
    			<!-- 如果不是debug日志就否定掉 -->
    			<onMismatch>DENY</onMismatch>
    		</filter>
    	</appender>
    
    	<!-- ERROR -->
    	<!-- 文件滚动日志 -->
    	<appender name="errorAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
    		<!-- 文件路径 -->
    		<file>${log.filePath}/error.log</file>
    		<!-- 上面写满了,就使用下面的滚动文件 -->
    		<!-- 基于时间滚动,按天 -->
    		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    			<!-- 文件名称,在log.filePath路径下生产以 debug.日期.log.gz结尾的文件 -->
    			<fileNamePattern>${log.filePath}/error/error.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
    			<!-- 文件最大保存数量,轮询 -->
    			<maxHistory>${log.maxHistory}</maxHistory>
    		</rollingPolicy>
    		<!-- 将日志信息转化为字符串,将字符串输出到文件里 -->
    		<encoder>
    			<pattern>${log.pattern}</pattern>
    		</encoder>
    		<!-- 过滤器:过滤不是 debug.level的日志 -->
    		<filter class="ch.qos.logback.classic.filter.LevelFilter">
    			<level>ERROR</level>
    			<!-- 如何时debug日志就记录下来 -->
    			<onMatch>ACCEPT</onMatch>
    			<!-- 如果不是debug日志就否定掉 -->
    			<onMismatch>DENY</onMismatch>
    		</filter>
    	</appender>
    
    
    	<!-- 告诉logback 需要关注那个包下面的信息 只记录那个日记级别的信息 additivity(将root日志的信息也放在这个 logger 里面来)  -->
    	<logger name="com.imooc.o2o" level="${log.level}" additivity="true">
    		<!-- 绑定 appender, 往这个鞋appender输出信息 -->
    		<appender-ref ref="debugAppender"/>
    		<appender-ref ref="infoAppender"/>
    		<appender-ref ref="errorAppender"/>
    	</logger>
    
    	<!-- 根 root, 如果没有指定 level 会继承 root 的level -->
    	<root level="info">
    		<!-- 往下面的 consoleAppender 输出信息 -->
    		<appender-ref ref="consoleAppender"/>
    	</root>
    </configuration>
    复制代码

    第四章 代码实现

    4.1、简单的 SSM 案例-Area

    4.1.1、dao 层

    1. 在 dao 层创建一个 AreaDao 接口,代码如下
    package com.imooc.o2o.dao;
    
    import java.util.List;
    import com.imooc.o2o.entity.Area;
    
    public interface AreaDao {
    	/**
    	 * 列出区域列表
    	 * @return areaList
    	 */
    	List<Area> queryArea();
    }
    language-Java复制代码

    1. 在 mapper 配置,创建 AreaDao.xml, 代码如下
    <?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="com.imooc.o2o.dao.AreaDao">
    	<select id="queryArea" resultType="com.imooc.o2o.entity.Area">
    		SELECT
    		area_id,area_name,priority,create_time,last_edit_time
    		FROM tb_area
    		ORDER BY priority DESC
    	</select>
    </mapper>
    language-xml复制代码

    1. dao 层单元测试,com.imooc.o2o.dao
    • BaseTest.java 基类,初始化 spring 的配置
    package com.imooc.o2o;
    
    import org.junit.runner.RunWith;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    /**
     * 初始化spring和junit整合,junit启动时加载springIOC容器
     * @author jxh
     */
    @RunWith(SpringJUnit4ClassRunner.class)
    // 告诉Junit spring配置文件的位置
    @ContextConfiguration({"classpath:spring/spring-dao.xml", "classpath:spring/spring-service.xml"})
    public class BaseTest {
    
    }
    language-Java复制代码
    • 测试类
    package com.imooc.o2o.dao;
    
    import static org.junit.Assert.assertEquals;
    import java.util.List;
    import org.junit.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import com.imooc.o2o.BaseTest;
    import com.imooc.o2o.entity.Area;
    
    public class AreaDaoTest extends BaseTest {
    	@Autowired
    	private AreaDao areaDao;
    
    	@Test
    	public void testQueryArea() {
    		List<Area> areaList = areaDao.queryArea();
    		assertEquals(2, areaList.size());
    	}
    }
    language-Java复制代码

    4.1.2、Service 层

    1. service AreaService.java 接口类
    package com.imooc.o2o.service;
    
    import java.util.List;
    
    import com.imooc.o2o.entity.Area;
    
    public interface AreaService {
    	List<Area> getAreaList();
    }
    language-Java复制代码
    1. service.impl AreaServiceImpl.java 实现类
    package com.imooc.o2o.service.impi;
    
    import java.util.List;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import com.imooc.o2o.dao.AreaDao;
    import com.imooc.o2o.entity.Area;
    import com.imooc.o2o.service.AreaService;
    
    @Service // Spring IOC 托管
    public class AreaServiceImpl implements AreaService {
    
    	@Autowired
    	private AreaDao areaDao;
    
    	@Override
    	public List<Area> getAreaList() { // 从数据库取出数据
    		return areaDao.queryArea();
    	}
    }
    language-Java复制代码
    1. service 层单元测试
    • AreaServiceTest.java 代码
    package com.imooc.o2o.service;
    
    import static org.junit.Assert.assertEquals;
    import java.util.List;
    import org.junit.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import com.imooc.o2o.BaseTest;
    import com.imooc.o2o.entity.Area;
    
    public class AreaServiceTest extends BaseTest {
    	@Autowired
    	private AreaService areaService;
    
    	@Test
    	public void testGetArealist() {
    		List<Area> areaList = areaService.getAreaList();
    		assertEquals("西苑", areaList.get(0).getAreaName());
    	}
    }
    language-Java复制代码

    4.1.3、web 层

    1. AreaController.java
    package com.imooc.o2o.web.superadmin;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    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.ResponseBody;
    import com.imooc.o2o.entity.Area;
    import com.imooc.o2o.service.AreaService;
    
    @Controller
    @RequestMapping("/superadmin")
    public class AreaController {
    
    	@Autowired
    	private AreaService areaService;
    
    	@RequestMapping(value = "/listarea", method = RequestMethod.GET)
    	@ResponseBody
    	private Map<String, Object> listArea() {
    		Map<String, Object> modelMap = new HashMap<String, Object>();
    		List<Area> list = new ArrayList<Area>();
    		try {
    			list = areaService.getAreaList();
    			modelMap.put("rows", list);
    			modelMap.put("total", list.size());
    		} catch (Exception e) {
    			e.printStackTrace();
    			modelMap.put("success", false);
    			modelMap.put("errMsg", e.toString());
    		}
    		return modelMap;
    	}
    }
    language-Java复制代码


    第五章 店铺系统的实现

    5.1、店铺的注册模块

    5.1.1、dao 层代码

    1. ShopDao.java 接口
    package com.imooc.o2o.dao;
    
    public interface ShopDao {
    	// 新增店铺,return 0 & 1
    	int insertShop(Shop shop);
    	// 更新店铺信息
    	int updateShop(Shop shop);
    }
    language-Java复制代码
    1. mapper 的配置 ShopDao.xml
    <?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="com.imooc.o2o.dao.ShopDao">
    	<!-- 使用JDBC的getGeneratedKeys获取数据库自增主键值 -->
    	<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>
    	<!-- Mybatis支持动态SQ, set标签里面是动态的SQL语句,xxx:是实体类的成员变量; xxx_xxx:数据库表中的字段名,例如<if test="xxx != null">xxx_xxx=#{xxx}</if> -->
    	<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>
    </mapper>
    language-xml复制代码
    1. 单元测试
    package com.imooc.o2o.dao;
    
    public class ShopDaoTest extends BaseTest {
    	@Autowired
    	private ShopDao shopDao;
    	@Test
    	// @Ignore // 忽略当前方法的测试
    	public void testInsertShop() {
    		Shop shop = new Shop();
    		PersonInfo  owner = new PersonInfo();
    		Area area = new Area();
    		ShopCategory shopCategory = new ShopCategory();
    		// 由于该shop涉及其他表,所以给其他表设置了一些测试值,现在初始化这些值,才可以正常的添加店铺
    		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);
    	}
    	@Test
    	public void testUpdateShop() {
    		Shop shop = new Shop();
    		// 需要指定ID
    		shop.setShopId(1L);
    		PersonInfo  owner = new PersonInfo();
    		Area area = new Area();
    		ShopCategory shopCategory = new ShopCategory();
    		// 由于该shop涉及其他表,所以给其他表设置了一些测试值,现在初始化这些值,才可以正常的添加店铺
    		owner.setUserId(1L);
    		area.setAreaId(2);
    		shop.setShopDesc("测试描述");
    		shop.setShopAddr("测试地址");
    		shop.setLastEditTime(new Date());
    		// 影响行数
    		int effectedNum = shopDao.updateShop(shop);
    		assertEquals(1,effectedNum);
    	}
    }
    language-Java复制代码

    5.1.2、service 层代码

    1. 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);
    }
    language-Java复制代码
    1. dto,对返回值进行二次封装,添加 status 等参数
    package com.imooc.o2o.dto;
    
    public class ShopExecution {
    	private int state; // 结果状态
    	private String stateInfo; // 状态标识,接受 state的说明
    	private int count; // 店铺数量
    	private Shop shop; // 操作的shop (增删改店铺的时候用到)
    	private List<Shop> shopList; // shop列表(查询店铺列表的时候使用)
    	public ShopExecution() {}
    	// 店铺操作失败的时候使用的构造器
    	public ShopExecution(ShopStateEnum stateEnum) {
    		this.state = stateEnum.getState();
    		this.stateInfo = stateEnum.getStateInfo();
    	}
    	// 店铺操作成功时使用的构造器,同时返回单个shop
    	public ShopExecution(ShopStateEnum stateEnum, Shop shop) {
    		this.state = stateEnum.getState();
    		this.stateInfo = stateEnum.getStateInfo();
    		this.shop = shop;
    	}
    	// 店铺操作成功时使用的构造器,同时返回一个shop 列表
    	public ShopExecution(ShopStateEnum stateEnum, List<Shop> shopList) {
    		this.state = stateEnum.getState();
    		this.stateInfo = stateEnum.getStateInfo();
    		this.shopList = shopList;
    	}
    	// Getter & Setter
    }
    language-Java复制代码
    1. Service 实现类
    package com.imooc.o2o.service.impi;
    
    @Service
    public class ShopServicelmpl implements ShopService{
    	@Autowired
    	private ShopDao shopDao;
    	@Override
    	public ShopExecution addShop(Shop shop, File shopImg) {
    		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(shopImg != null) {
    					// 存储图片
    					try {
    						addShopImg(shop, shopImg);
    					} catch(Exception e) {
    						throw new ShopOperationException("添加店铺图片失败:" + 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) {
    		// TODO Auto-generated method stub
    		String dest = PathUtil.getShopImagePath(shop.getShopId());
    		String shopImgAddr = ImageUtil.generateThumbnail(shopImg, dest);
    		shop.setShopImg(shopImgAddr);
    	}
    }
    language-Java复制代码
    1. Service 层 单元测试
    package com.imooc.o2o.service;
    
    @Service
    public class ShopServiceTest extends BaseTest {
    	@Autowired
    	private ShopService shopService;
    	@Test
    	public void testAddShop() {
    		Shop shop = new Shop();
    		PersonInfo  owner = new PersonInfo();
    		Area area = new Area();
    		ShopCategory shopCategory = new ShopCategory();
    		// 由于该shop涉及其他表,所以给其他表设置了一些测试值,现在初始化这些值,才可以正常的添加店铺
    		owner.setUserId(1L);
    		area.setAreaId(2);
    		shopCategory.setShopCategoryId(1L);
    		shop.setOwner(owner);
    		shop.setArea(area);
    		shop.setShopCategory(shopCategory);
    		shop.setShopName("测试的店铺1");
    		shop.setShopDesc("test1");
    		shop.setShopAddr("test1");
    		shop.setPhone("test1");
    		shop.setCreateTime(new Date());
    		shop.setEnableStatus(ShopStateEnum.CHECK.getState()); // 0
    		shop.setAdvice("审核中");
    		File shopImg = new File("/Users/jxh/Downloads/xiaohuangren.jpeg");
    		ShopExecution se = shopService.addShop(shop,shopImg);
    		assertEquals(ShopStateEnum.CHECK.getState(), se.getState());
    	}
    }
    language-Java复制代码

    5.1.3、 Controller 层

    1. ShopManagementController.java
    package com.imooc.o2o.web.shopadmin;
    
    @Controller
    @RequestMapping("/shopadmin")
    public class ShopManagementController {
    	@Autowired
    	private ShopService shopService;
    	@RequestMapping(value="/registershop", method = RequestMethod.POST)
    	@ResponseBody
    	private Map<String, Object> registerShop(HttpServletRequest request) throws ShopOperationException, IOException {
    		Map<String, Object> modelMap = new HashMap<String, Object>();
    
    		// 验证码
    		if(!CodeUtil.checkVerifyCode(request)) {
    			modelMap.put("success", false);
    			modelMap.put("errMsg", "输入了错误的验证码");
    			return modelMap;
    		}
    		// 1.接受并转化相应的参数,思路:获取前端传过来的店铺信息,将数据转换为实体类,同时获取前端传过来的文件流,将图片存放在shopImg里面去。
    		// 使用工具类,将相应的参数转化为字符串
    		String shopStr = HttpServletRequestUtil.getString(request, "shopStr");
    		// 使用 jackson-databind库 将字符串转化为实体类,JSON to POJO and back
    		ObjectMapper mapper = new ObjectMapper();
    		Shop shop = null;
    		try {
    			shop = mapper.readValue(shopStr, Shop.class);
    		} catch(Exception e) {
    			modelMap.put("success", false);
    			modelMap.put("errMsg", e.getMessage());
    			return modelMap;
    		}
    		// 获取前端文件流,保存上传图片的文件流
    		CommonsMultipartFile shopImg = null; // 保存上传图片的文件流
    		// 从 request session 的上下文获取相关文件上传的内容
    		CommonsMultipartResolver commmonsMultipartResolver = new CommonsMultipartResolver(
    				request.getSession().getServletContext());
    		if(commmonsMultipartResolver.isMultipart(request)) { // 判断是否有上传的文件流
    			// 如果有上传的文件流,就进行转换 MultipartHttpServletRequest 类型的对象,该对象可以被spring处理文件流对象
    			MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request;
    			// 从该上传对象中提取出图片的文件流,(shopImg 是与前端协商的名称)
    			shopImg = (CommonsMultipartFile) multipartHttpServletRequest.getFile("shopImg");
    		} else { // 如果没有图片就报错,因为图片是必须的
    			modelMap.put("success", false);
    			modelMap.put("errMsg", "上传图片不能为空");
    			return modelMap;
    		}
    		// 2.注册店铺
    		if(shop != null && shopImg != null) {
    			PersonInfo owner = new PersonInfo();
    			owner.setUserId(1L);
    			shop.setOwner(owner);
    			ShopExecution se = shopService.addShop(shop, shopImg.getInputStream(), shopImg.getOriginalFilename());
    			if(se.getState() == ShopStateEnum.CHECK.getState()) {
    				modelMap.put("success", true);
    			}else {
    				modelMap.put("success", false);
    				modelMap.put("errMsg", se.getStateInfo());
    			}
    			return modelMap;
    		} else {
    			modelMap.put("success", false);
    			modelMap.put("errMsg", "请输入店铺信息");
    			return modelMap;
    		}
    	}
    }
    language-Java复制代码
    1. kaptcha 验证码的使用
    • 引入依赖
    	<!-- https://mvnrepository.com/artifact/com.github.penggle/kaptcha -->
    	<dependency>
    	    <groupId>com.github.penggle</groupId>
    	    <artifactId>kaptcha</artifactId>
    	    <version>2.3.2</version>
    	</dependency>
    language-XML复制代码
    • web.xml 配置
      <!-- 验证码配置 -->
      <servlet>
      	<servlet-name>Kaptcha</servlet-name>
      	<servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
      	<!-- 是否有边框 -->
      	<init-param>
      		<param-name>kaptcha.border</param-name>
      		<param-value>no</param-value>
      	</init-param>
      	<!-- 字体颜色 -->
      	<init-param>
      		<param-name>kaptcha.textproducer.font.color</param-name>
      		<param-value>red</param-value>
      	</init-param>
      	<!-- 字体大小 -->
      	<init-param>
      		<param-name>kaptcha.textproducer.font.size</param-name>
      		<param-value>43</param-value>
      	</init-param>
      	 <!-- 字体样式 -->
      	<init-param>
      		<param-name>kaptcha.textproducer.font.names</param-name>
      		<param-value>Arial</param-value>
      	</init-param>
      	<!-- 图片宽度 -->
      	<init-param>
      		<param-name>kaptcha.image.width</param-name>
      		<param-value>135</param-value>
      	</init-param>
      	<!-- 图片高度 -->
      	<init-param>
      		<param-name>kaptcha.image.width</param-name>
      		<param-value>50</param-value>
      	</init-param>
      	<!-- 使用那些字符生产验证码 -->
      	<init-param>
      		<param-name>kaptcha.textproducer.char.string</param-name>
      		<param-value>ACDEFHKPRSTWX345679</param-value>
      	</init-param>
      	<!-- 干扰线的颜色 -->
      	<init-param>
      		<param-name>kaptcha.noise.color</param-name>
      		<param-value>black</param-value>
      	</init-param>
      	<!-- 验证码字符个数 -->
      	<init-param>
      		<param-name>kaptcha.textproducer.char.length</param-name>
      		<param-value>4</param-value>
      	</init-param>
      </servlet>
    
      <servlet-mapping>
      	<servlet-name>Kaptcha</servlet-name>
      	<url-pattern>/Kaptcha</url-pattern>
      </servlet-mapping>
    language-xml复制代码
    • 判断验证码是否正确
    package com.imooc.o2o.util;
    import javax.servlet.http.HttpServletRequest;
    public class CodeUtil {
    	public static boolean checkVerifyCode(HttpServletRequest request) {
    		String verifyCodeExpected = (String) request.getSession().getAttribute(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);
    		String verifyCodeActual = HttpServletRequestUtil.getString(request, "verifyCodeActual");
    		if(verifyCodeActual == null || !verifyCodeActual.equals(verifyCodeExpected)) {
    			return false;
    		}
    		return true;
    	}
    }
    language-JAVA复制代码
    • 举个栗子
    @RequestMapping(value="/registershop", method = RequestMethod.POST)
    @ResponseBody
    private Map<String, Object> registerShop(HttpServletRequest request) throws ShopOperationException, IOException {
    	Map<String, Object> modelMap = new HashMap<String, Object>();
    	// 验证码
    	if(!CodeUtil.checkVerifyCode(request)) {
    		modelMap.put("success", false);
    		modelMap.put("errMsg", "输入了错误的验证码");
    		return modelMap;
    	}
    language-Java复制代码
    <div class="item-inner">
      <label for="j_captcha" class="item-title label">验证码</label>
      <input
        id="j_captcha"
        name="j_captcha"
        type="text"
        class="form-control in"
        placeholder="验证码"
      />
      <div class="item-input">
        <img
          id="captcha_img"
          alt="点击更换"
          title="点击更换"
          onclick="changeVerifyCode(this)"
          src="../Kaptcha"
        />
      </div>
    </div>
    language-html复制代码
    $('#submit').click(function () {
    	var formData = new FormData();
    	var verifyCodeActual = $('#j_captcha').val();
    	formData.append('verifyCodeActual', verifyCodeActual);
    language-js复制代码

    5.1.4、 拓展

    1. 工具类 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 org.springframework.web.multipart.commons.CommonsMultipartFile;
    import net.coobird.thumbnailator.Thumbnails;
    import net.coobird.thumbnailator.geometry.Positions;
    import net.coobird.thumbnailator.name.Rename;
    
    /**
     * 该工具类用于处理预先定义好的图片
     * @author jxh
     */
    public class ImageUtil {
    	// 获取classpath的绝对值路径
    	private static String basePath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
    	private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyMMddHHmmss");
    	private static final Random r = new Random();
    	// 缩略图
    	public static String generateThumbnail(File thumbnail, String targetAddr) {
    		// 自定义图片名称,由于用户可能有重复的图片名称
    		String realFileName = getRandomFileName();
    		// 获取图片的后缀
    		String extension = getFileExtension(thumbnail);
    		System.out.print("=================================" + extension);
    		// 用于处理路径不存在的情况
    		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 + "/watermark.png")), 0.25f)
    			.outputQuality(0.8f).toFile(dest); // 压缩和输出路径
    		} catch(IOException e) {
    			e.printStackTrace();
    		}
    		return relativeAddr;
    	}
    	/**
    	 * 创建目标路径所涉及的目录,即 /home/work/hjxstart/xxx.jpg, 那么 home work hjxstart 这三个文件夹都得自动创建
    	 * @param targetAddr
    	 */
    	private static void makeDirPath(String targetAddr) {
    		// TODO Auto-generated method stub
    		String realFileParentPath = PathUtil.getImgBasePath() + targetAddr;
    		File dirPath = new File(realFileParentPath);
    		if(!dirPath.exists()) {
    			dirPath.mkdirs();
    		}
    	}
    	/**
    	 * 获取输入文件流的拓展名,获取最后一个.的字符
    	 * @param thumbnail
    	 * @return
    	 */
    	private static String getFileExtension(File file) {
    	    String name = file.getName();
    	    int lastIndexOf = name.lastIndexOf(".");
    	    if (lastIndexOf == -1) {
    	        return ""; // empty extension
    	    }
    	    return name.substring(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 {
    		// 获取classpath的绝对值路径,由于这个方法是通过线程去执行的,可以通过线程逆推到类加载器,从而从类加载器得到资源路径
    		String basePath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
    		Thumbnails.of(new File("/Users/jxh/Downloads/src.jpeg")) // 需要处理的文件
    		.size(200, 200) // 处理后的文件大小
    		.watermark(Positions.BOTTOM_RIGHT, ImageIO.read(new File(basePath + "/hjxstart.png")), 0.25f) // 添
    		.outputQuality(0.8f).toFile("/Users/jxh/Downloads/det.jpeg");
    	}
    }
    language-Java复制代码
    1. 工具类 PathUtil。用于处理在不同系统下的路径问题。
    package com.imooc.o2o.util;
    
    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 = "/Users/jxh/Downloads/image/";
    		}
    		basePath = basePath.replace("/", seperator);
    		return basePath;
    	}
    	public static String getShopImagePath(long shopId) {
    		String imagePath = "upload/item/shop/" + shopId + "/";
    		return imagePath.replace("/", seperator);
    	}
    }
    language-Java复制代码
    1. Session,图片处理工具Thumbnailator的使用
    • 导入依赖
    	<!-- 图片处理 -->
    	<!-- https://mvnrepository.com/artifact/net.coobird/thumbnailator -->
    	<dependency>
    	    <groupId>net.coobird</groupId>
    	    <artifactId>thumbnailator</artifactId>
    	    <version>0.4.8</version>
    	</dependency>
    language-xml复制代码
    • 简单的例子
    public static void main(String[] args) throws IOException {
    	// 获取class path的绝对值路径,由于这个方法是通过线程去执行的,可以通过线程逆推到类加载器,从而从类加载器得到资源路径
    	String basePath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
    	Thumbnails.of(new File("/Users/jxh/Downloads/src.jpeg"))
    	.size(200, 200).watermark(Positions.BOTTOM_RIGHT, ImageIO.read(new File(basePath + "/hjxstart.png")), 0.25f)
    	.outputQuality(0.8f).toFile("/Users/jxh/Downloads/det.jpeg");
    }
    language-Java复制代码

    5.2 店铺信息编辑模块

    目标:实现单个店铺信息的获取,实现对店铺信息进行修改。

    5.2.1 Dao 层

    1. ShopDao.java 接口
    	// 查询店铺接口,根据 shopId 进行查询
    	Shop queryByShopId(long shopId);
    	// 更新店铺接口,
    	int updateShop(Shop shop);
    language-Java复制代码
    1. shopDao.xml
    <?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="com.imooc.o2o.dao.ShopDao">
    	<!-- 符合对象。扩展外键的值。涉及其他表的数据,比如原表只包含了其他ID,但是想要其他表的name。所有就要定义新的返回类型 -->
    	<resultMap type="com.imooc.o2o.entity.Shop" id="shopMap">
    		<id column="shop_id" property="shopId" />
    		<result column="shop_name" property="shopName" />
    		<result column="shop_desc" property="shopDesc" />
    		<result column="shop_addr" property="shopAddr" />
    		<result column="phone" property="phone" />
    		<result column="shop_img" property="shopImg" />
    		<result column="priority" property="priority" />
    		<result column="create_time" property="createTime" />
    		<result column="last_edit_time" property="lastEditTime" />
    		<result column="enable_status" property="enableStatus" />
    		<result column="advice" property="advice" />
    		<!-- 复合数据类型 -->
    		<association property="area" column="area_id"
    			javaType="com.imooc.o2o.entity.Area">
    			<id column="area_id" property="areaId" />
    			<result column="area_name" property="areaName" />
    		</association>
    		<association property="shopCategory"
    			column="shop_category_id"
    			javaType="com.imooc.o2o.entity.ShopCategory">
    			<id column="shop_category_id" property="shopCategoryId" />
    			<result column="shop_category_name"
    				property="shopCategoryName" />
    		</association>
    		<association property="owner" column="user_id"
    			javaType="com.imooc.o2o.entity.PersonInfo">
    			<id column="user_id" property="userId" />
    			<result column="name" property="name" />
    		</association>
    	</resultMap>
    
    	<select id="queryByShopId" resultMap="shopMap"
    		parameterType="Long">
    		SELECT
    		s.shop_id,
    		s.shop_name,
    		s.shop_desc,
    		s.shop_addr,
    		s.phone,
    		s.shop_img,
    		s.priority,
    		s.create_time,
    		s.last_edit_time,
    		s.enable_status,
    		s.advice,
    		a.area_id,
    		a.area_name,
    		sc.shop_category_id,
    		sc.shop_category_name
    		FROM
    		tb_shop s,
    		tb_area a,
    		tb_shop_category sc
    		WHERE
    		s.area_id=a.area_id
    		AND
    		s.shop_category_id = sc.shop_category_id
    		AND
    		s.shop_id = #{shopId}
    	</select>
    </mapper>
    language-xml复制代码
    1. 单元测试
    	@Test
    	public void testQueryShopList() {
    		Shop shopCondition = new Shop();
    		PersonInfo owner = new PersonInfo();
    		owner.setUserId(1L);
    		shopCondition.setOwner(owner);
    		List<Shop> shopList = shopDao.queryShopList(shopCondition, 0, 5);
    		System.out.println("店铺列表的大小:" + shopList.size());
    	}
    language-Java复制代码

    5.2.2 Service 层

    1. ShopService.java 接口
    	// 通过店铺ID获取店铺信息
    	Shop getByShopId(long shopId);
    	// 更新店铺信息,包括对图片的处理,ShopExecution 封装了状态信息和店铺信息,对店铺的操作都会返回这个类
    	ShopExecution modifyShop(Shop shop, InputStream shopImgInputStream, String fileName) throws ShopOperationException;
    language-Java复制代码
    1. ShopServiceImpl.java 实现
    	// 返回shop
    	@Override
    	public Shop getByShopId(long shopId) {
    		// TODO Auto-generated method stub
    		return shopDao.queryByShopId(shopId);
    	}
    
    	@Override
    	public ShopExecution modifyShop(Shop shop, InputStream shopImgInputStream, String fileName)
    			throws ShopOperationException {
    
    		try {
    			// 1.判断是否要修改图片,使用新的图片,删除就的图片 deleteFileOrPath
    			if(shop == null || shop.getShopId() ==  null && fileName != null && !"".equals(fileName)) {
    				return new ShopExecution(ShopStateEnum.NULL_SHOP);
    			} else {
    				 if(shopImgInputStream != null) {
    					 Shop tempShop = shopDao.queryByShopId(shop.getShopId());
    					 if(tempShop.getShopImg() != null) {
    						 ImageUtil.deleteFileOrPath(tempShop.getShopImg());
    					 }
    					 addShopImg(shop, shopImgInputStream, fileName);
    				 }
    			}
    			// 2.更新店铺信息
    			shop.setLastEditTime(new Date());
    			int effectedNum = shopDao.updateShop(shop);
    			if(effectedNum <= 0) {
    				return new ShopExecution(ShopStateEnum.INNER_ERROR);
    			}else {
    				shop = shopDao.queryByShopId(shop.getShopId());
    				return new ShopExecution(ShopStateEnum.SUCCESS, shop);
    			}
    
    		} catch(Exception e) {
    			throw new ShopOperationException("modifyShop error" + e.getMessage());
    		}
    	}
    language-Java复制代码
    1. 单元测试
    	@Test
    	@Ignore
    	public void testModifyShop() throws FileNotFoundException {
    		Shop shop = new Shop();
    		shop.setShopId(1L);
    		shop.setShopName("修改后的店铺名称");
    		File shopImg = new File("/Users/jxh/Downloads/src.jpeg");
    		InputStream is = new FileInputStream(shopImg);
    		ShopExecution shopExecution =  shopService.modifyShop(shop, is, "src.jpeg");
    		System.out.println("新的图片地址为:" + shopExecution.getShop().getShopImg());
    	}
    language-java复制代码
    1. 拓展
    • ImageUtil.java 删除就图片文件或路径。生成新的图片路径
    	/**
    	 * storePath是文件的路径还是目录的路径,
    	 * 如果storePath是文件路径则删除改文件
    	 * 如果storePath是目录路径则删除改目录下的所有文件
    	 */
    	public static void deleteFileOrPath(String storePath) {
    		File fileOrPath = new File(PathUtil.getImgBasePath() + storePath);
    		if(fileOrPath.exists()) {
    			if(fileOrPath.isDirectory()) {
    				File files[] = fileOrPath.listFiles();
    				for(int i = 0; i < files.length; i++) {
    					files[i].delete();
    				}
    			}
    			fileOrPath.delete();
    		}
    	}
    language-java复制代码

    5.2.3 Controller 层

    1. afsd

    5.3 商品类别列表展示

    5.3.1 dao 层

    1. ProductCategoryDao.java 接口
    import com.imooc.o2o.entity.ProductCategory;
    
    public interface ProductCategoryDao {
    	/**
    	 * 通过shop id 查询店铺商品类别
    	 * @param shopId
    	 * @return
    	 */
    	List<ProductCategory> queryProductCategoryList(long shopId);
    }
    language-java复制代码
    1. Mapper 用于实现 dao 接口的 ProductCategoryDao.xml
    <?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="com.imooc.o2o.dao.ProductCategoryDao">
    	<!-- 目的:为dao接口方法题哦功能sql语句配置 -->
    	<select id="queryProductCategory" resultType="com.imooc.o2o.entity.ProductCategory" parameterType="Long">
    		SELECT
    		product_category_id,product_category_name,priority,create_time,shop_id
    		FROM tb_product_category
    		WHERE
    		shop_id = #{shopId}
    		ORDER BY priority DESC
    	</select>
    </mapper>
    language-xml复制代码
    1. 单元测试 ProductCategoryDaoTest.java
    public class ProductCategoryDaoTest extends BaseTest {
    	@Autowired
    	private ProductCategoryDao productCategoryDao;
    
    	@Test
    	public void testProductCategoryDao() {
    		long shopId = 1;
    		List<ProductCategory> productCategoryList = productCategoryDao.queryProductCategoryList(shopId);
    		System.out.println("该店铺自定义类别数为:" + productCategoryList.size());
    	}
    }
    language-java复制代码

    5.3.2 service 层

    1. 接口
    import com.imooc.o2o.entity.ProductCategory;
    public interface ProductCategoryService {
    	// 查询指定某个店铺下的所有商品类别信息
    	List<ProductCategory> getProductCategoryList(long shopId);
    }
    language-java复制代码
    1. 实现类
    @Service
    public class ProductCategoryServiceImpl implements ProductCategoryService {
    	@Autowired
    	private ProductCategoryDao productCategoryDao;
    
    	@Override
    	public List<ProductCategory> getProductCategoryList(long shopId) {
    		return productCategoryDao.queryProductCategoryList(shopId);
    	}
    }
    language-java复制代码
    1. 单元测试

    5.3.3 controller 层 ProductCategoryManagementController.java

    import com.imooc.o2o.dao.Result;
    import com.imooc.o2o.entity.ProductCategory;
    import com.imooc.o2o.entity.Shop;
    import com.imooc.o2o.enums.ProductCategoryStateEnum;
    import com.imooc.o2o.service.ProductCategoryService;
    
    @Controller
    @RequestMapping("/shopadmin")
    public class ProductCategoryManagementController {
    	@Autowired
    	private ProductCategoryService productCategoryService;
    
    	@RequestMapping(value = "/getproductcategorylist", method=RequestMethod.GET)
    	@ResponseBody
    	private Result<List<ProductCategory>> getProductCategoryList(HttpServletRequest request) { // 返回一个对象,所有使用Result也可以
    
    		Shop currentShop = (Shop) request.getSession().getAttribute("currentShop");
    		List<ProductCategory> list = null;
    		if(currentShop != null && currentShop.getShopId() > 0) { // 如果为空就是店铺没有操作的权限
    			list = productCategoryService.getProductCategoryList(currentShop.getShopId());
    			return new Result<List<ProductCategory>>(true, list);
    		} else {
    			ProductCategoryStateEnum ps = ProductCategoryStateEnum.INNER_ERROR;
    			return new Result<List<ProductCategory>>(false, ps.getState(), ps.getStateInfo());
    		}
    	}
    }
    language-java复制代码

    5.3.3 Controller 层

    @Controller
    @RequestMapping("/shopadmin")
    public class ProductCategoryManagementController {
    	@Autowired
    	private ProductCategoryService productCategoryService;
    
    	@RequestMapping(value = "/getproductcategorylist", method=RequestMethod.GET)
    	@ResponseBody
    	private Result<List<ProductCategory>> getProductCategoryList(HttpServletRequest request) { // 返回一个对象,所有使用Result也可以
    
    		Shop currentShop = (Shop) request.getSession().getAttribute("currentShop");
    		List<ProductCategory> list = null;
    		if(currentShop != null && currentShop.getShopId() > 0) { // 如果为空就是店铺没有操作的权限
    			list = productCategoryService.getProductCategoryList(currentShop.getShopId());
    			return new Result<List<ProductCategory>>(true, list);
    		} else {
    			ProductCategoryStateEnum ps = ProductCategoryStateEnum.INNER_ERROR;
    			return new Result<List<ProductCategory>>(false, ps.getState(), ps.getStateInfo());
    		}
    	}
    }
    language-java复制代码

    5.4 批量添加商品列表

    5.4.1 dao 层

    1. ProductCategoryDao.java 接口
    public interface ProductCategoryDao {
    	// 批量新增商品类标
    	int batchInsertProductCategory(List<ProductCategory> productCategoryList);
    }
    language-java复制代码
    1. mapper ProductCategoryDao.xml
    <?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="com.imooc.o2o.dao.ProductCategoryDao">
    	<insert id="batchInsertProductCategory" parameterType="java.util.List">
    		INSERT INTO
    		tb_product_category(product_category_name, priority, create_time, shop_id)
    		VALUES
    		<!-- collection:遍历 list。separator分隔符:value(xxx),(xxx)  -->
    		<foreach collection="list" item="productCategory" index="index" separator=",">
    			(
    				#{productCategory.productCategoryName},
    				#{productCategory.priority},
    				#{productCategory.createTime},
    				#{productCategory.shopId}
    			)
    		</foreach>
    	</insert>
    </mapper>
    language-xml复制代码
    1. 单元测试 ProductCategoryDaoTest.java
    	@Test
    	public void testBatchInsertProductCategory() {
    		ProductCategory productCategory = new ProductCategory();
    		productCategory.setProductCategoryName("商品类别1");
    		productCategory.setPriority(1);
    		productCategory.setCreateTime(new Date());
    		productCategory.setShopId(1L);
    
    		ProductCategory productCategory2 = new ProductCategory();
    		productCategory2.setProductCategoryName("商品类别2");
    		productCategory2.setPriority(2);
    		productCategory2.setCreateTime(new Date());
    		productCategory2.setShopId(1L);
    
    		List<ProductCategory> productCategoryList = new ArrayList<ProductCategory>();
    		productCategoryList.add(productCategory);
    		productCategoryList.add(productCategory2);
    		int effectedNum = productCategoryDao.batchInsertProductCategory(productCategoryList);
    		assertEquals(2, effectedNum);
    
    	}
    language-java复制代码

    5.4.2 Service 层

    1. 接口 ProductCategoryService.java
    import com.imooc.o2o.dto.ProductCategoryExecution;
    import com.imooc.o2o.entity.ProductCategory;
    import com.imooc.o2o.exceptions.ProductCategoryOperationException;
    public interface ProductCategoryService {
    	// 查询指定某个店铺下的所有商品类别信息
    	List<ProductCategory> getProductCategoryList(long shopId);
    
    	ProductCategoryExecution batchAddProductCategory(List<ProductCategory> productCategoryList) throws ProductCategoryOperationException;
    }
    language-java复制代码
    1. 实现类 ProductCategoryServiceImpl.java
    public class ProductCategoryServiceImpl implements ProductCategoryService {
    	@Autowired
    	private ProductCategoryDao productCategoryDao;
    	@Override
    	public ProductCategoryExecution batchAddProductCategory(List<ProductCategory> productCategoryList)
    			throws ProductCategoryOperationException {
    		// TODO Auto-generated method stub
    		if(productCategoryList != null && productCategoryList.size() > 0) { // 空值判断
    			try {
    				int effectedNum = productCategoryDao.batchInsertProductCategory(productCategoryList);
    				if(effectedNum <= 0) { // 添加成功
    					throw new ProductCategoryOperationException("店铺类别创建失败");
    				} else { // 添加失败
    					return new ProductCategoryExecution(ProductCategoryStateEnum.SUCCESS);
    				}
    			}catch(Exception e) {
    				throw new ProductCategoryOperationException("batchAddProductCategory error: " + e.getMessage());
    			}
    		}else {
    			return new ProductCategoryExecution(ProductCategoryStateEnum.EMPTY_LIST);
    		}
    	}
    }
    language-java复制代码

    5.4.3 Controller 层

    	@RequestMapping(value = "/addproductcategorys", method = RequestMethod.POST)
    	@ResponseBody
    	private Map<String, Object> addProductCategorys(@RequestBody List<ProductCategory> productCategoryList,
    			HttpServletRequest request) {
    		Map<String, Object> modelMap = new HashMap<String, Object>();
    		Shop currentShop = (Shop) request.getSession().getAttribute("currentShop");
    		for (ProductCategory pc : productCategoryList) {
    			pc.setShopId(currentShop.getShopId());
    		}
    		if (productCategoryList != null && productCategoryList.size() > 0) {
    			try {
    				ProductCategoryExecution pe = productCategoryService.batchAddProductCategory(productCategoryList);
    				if (pe.getState() == ProductCategoryStateEnum.SUCCESS.getState()) {
    					modelMap.put("success", true);
    				} else {
    					modelMap.put("success", false);
    					modelMap.put("errMsg", pe.getStateInfo());
    				}
    			} catch (ProductCategoryOperationException e) {
    				modelMap.put("success", false);
    				modelMap.put("errMsg", e.toString());
    				return modelMap;
    			}
    		} else {
    			modelMap.put("success", false);
    			modelMap.put("errMsg", "请至少输入一个商品类别");
    		}
    		return modelMap;
    	}
    language-java复制代码

    5.5 删除商品类别

    5.5.1 dao 层

    1. 接口 ProductCategoryDao.java
    public interface ProductCategoryDao {
    	// 删除指定商品类别, 如果有两个参数,mybatis认不出来,必须使用@param注解来标识
    	// 加多参数 shopId 是为了防止不是本店铺的对商品列表的操作
    	int deleteProductCategory(@Param("productCategoryId") long productCategoryId, @Param("shopId") long shopId);
    }
    language-java复制代码
    1. mapper ProductCategory.xml
    	<delete id="deleteProductCategory">
    		DELETE FROM
    		tb_product_category
    		WHERE
    		product_category_id = #{productCategoryId}
    		AND shop_id = #{shopId}
    	</delete>
    language-xml复制代码
    1. 单元测试 ProductCategoryDaoTest.java
    	// junit回环,测试添加测试数据,和删除测试数据。对数据库没有影响。推荐设计 junit 回环
    	@Test
    	public void testCDeleteProductCategory() throws Exception {
    		long shopId = 1;
    		List<ProductCategory> productCategoryList = productCategoryDao.queryProductCategoryList(shopId);
    		for(ProductCategory pc : productCategoryList) {
    			if("商品类别1".equals(pc.getProductCategoryName()) || "商品类别2".equals(pc.getProductCategoryName())) {
    				int effectedNum = productCategoryDao.deleteProductCategory(pc.getProductCategoryId(), shopId);
    				assertEquals(1, effectedNum);
    			}
    		}
    	}
    language-java复制代码

    5.5.2 Serivce 层

    1. 接口 ProductCategoryService.java
    	// 将此类别下的商品里的类别id置为空,再删除掉该商品类别
    	ProductCategoryExecution deleteProductCategory(long productCategoryId, long shopId) throws ProductCategoryOperationException;
    language-java复制代码
    1. 实现类 ProductCategoryServiceImpl.java
    	// 删除商品类别
    	@Override
    	@Transactional //事务管理注解:该方法进行有2步操作:删除商品类别,将此商品类别下的商品id置为空,同时操作完成后再提交。
    	public ProductCategoryExecution deleteProductCategory(long productCategoryId, long shopId)
    			throws ProductCategoryOperationException {
    		try {
    			int effectedNum = productCategoryDao.deleteProductCategory(productCategoryId, shopId);
    			if(effectedNum <= 0) {
    				throw new ProductCategoryOperationException("商品类别删除失败");
    			}else {
    				return new ProductCategoryExecution(ProductCategoryStateEnum.SUCCESS);
    			}
    		} catch(Exception e) {
    			throw new ProductCategoryOperationException("deleteProductCategory error: " + e.getMessage());
    		}
    	}
    language-java复制代码

    5.5.3 Controller 层

    	@RequestMapping(value = "/removeproductcategorys", method = RequestMethod.POST)
    	@ResponseBody
    	private Map<String, Object> removeProductCategorys(Long productCategoryId, HttpServletRequest request) {
    		Map<String, Object> modelMap = new HashMap<String, Object>();
    
    		if (productCategoryId != null && productCategoryId > 0) {
    			try {
    				Shop currentShop = (Shop) request.getSession().getAttribute("currentShop");
    				ProductCategoryExecution pe = productCategoryService.deleteProductCategory(productCategoryId, currentShop.getShopId());
    				if (pe.getState() == ProductCategoryStateEnum.SUCCESS.getState()) {
    					modelMap.put("success", true);
    				} else {
    					modelMap.put("success", false);
    					modelMap.put("errMsg", pe.getStateInfo());
    				}
    			} catch (ProductCategoryOperationException e) {
    				modelMap.put("success", false);
    				modelMap.put("errMsg", e.toString());
    				return modelMap;
    			}
    		} else {
    			modelMap.put("success", false);
    			modelMap.put("errMsg", "请至少选择一个商品类别");
    		}
    		return modelMap;
    	}
    language-java复制代码

    6、商品管理

    6.1 商品添加, 商品详情图批量添加

    实现商品添加,掌握批量图片添加

    6.1.1

    1. 接口 ProductImgDao.java & ProductDao.java
    public interface ProductImgDao {
    	// 批量添加商品详情图片
    	int batchInsertProductImg(List<ProductImg> productImgList);
    }
    
    public interface ProductDao {
    	// 添加商品
    	int insertProduct(Product product);
    }
    language-java复制代码
    1. mapper ProductImgDao.xml & ProductDao.xml
    <?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="com.imooc.o2o.dao.ProductImgDao">
    	<!-- 参数类型是List -->
    	<insert id="batchInsertProductImg"
    		parameterType="java.util.List">
    		INSERT INTO
    		tb_product_img(img_addr, img_desc, priority, create_time,
    		product_id)
    		VALUES
    		<foreach collection="list" item="productImg" index="index"
    			separator=",">
    			(
    			#{productImg.imgAddr},
    			#{productImg.imgDesc},
    			#{productImg.priority},
    			#{productImg.createTime},
    			#{productImg.productId}
    			)
    		</foreach>
    	</insert>
    </mapper>
    
    <?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="com.imooc.o2o.dao.ProductDao">
    	<insert id="insertProduct" parameterType="com.imooc.o2o.entity.Product" useGeneratedKeys="true" keyProperty="productId" keyColumn="product_id">
    		INSERT INTO
    		tb_product(product_name, product_desc, img_addr, normal_price, promotion_price, priority, create_time, last_edit_time,
    		enable_status, product_category_id, shop_id)
    		VALUES
    		(#{productName}, #{productDesc}, #{imgAddr}, #{normalPrice}, #{promotionPrice}, #{priority}, #{createTime}, #{lastEditTime},
    		#{enableStatus}, #{productCategory.productCategoryId}, #{shop.shopId})
    	</insert>
    </mapper>
    language-xml复制代码
    1. 单元测试 ProductImgDaoTest.java & ProductDaoTest.java
    @FixMethodOrder(MethodSorters.NAME_ASCENDING) // 按照名字顺序执行测试方法
    public class ProductImgDaoTest extends BaseTest{
    
    	@Autowired
    	private ProductImgDao productImgDao;
    
    	@Test
    	public void testABatchInsertProductImgDao() throws Exception {
    		// productId 为1的商品里添加两个详情图记录
    		ProductImg productImg1= new ProductImg();
    		productImg1.setImgAddr("图片1");
    		productImg1.setImgDesc("测试图片1");
    		productImg1.setPriority(1);
    		productImg1.setCreateTime(new Date());
    		productImg1.setProductId(1L);
    
    		ProductImg productImg2= new ProductImg();
    		productImg2.setImgAddr("图片2");
    		productImg2.setImgDesc("测试图片2");
    		productImg2.setPriority(1);
    		productImg2.setCreateTime(new Date());
    		productImg2.setProductId(1L);
    
    		List<ProductImg> productImgList = new ArrayList<ProductImg>();
    		productImgList.add(productImg1);
    		productImgList.add(productImg2);
    		int effectedNum = productImgDao.batchInsertProductImg(productImgList);
    		assertEquals(2, effectedNum);
    	}
    }
    
    FixMethodOrder(MethodSorters.NAME_ASCENDING) // 按测试方法名字的顺序执行测试
    public class ProductDaoTest extends BaseTest {
    
    	@Autowired
    	private ProductDao productDao;
    	@Autowired
    	private ProductImgDao productImgDao;
    
    	@Test
    	public void testAInsertProduct() throws Exception{
    		Shop shop1 = new Shop();
    		shop1.setShopId(1L);
    		ProductCategory pc1 = new ProductCategory();
    		pc1.setProductCategoryId(1L);
    		// 初始化三个商品实例并添加进shopId为1的店铺里,同时商品类别Id也为1
    		Product product1 = new Product();
    		product1.setProductName("测试1");
    		product1.setProductDesc("测试Desc1");
    		product1.setImgAddr("test1");
    		product1.setPriority(1);
    		product1.setEnableStatus(1);
    		product1.setCreateTime(new Date());
    		product1.setLastEditTime(new Date());
    		product1.setShop(shop1);
    		product1.setProductCategory(pc1);
    
    		Product product2 = new Product();
    		product2.setProductName("测试2");
    		product2.setProductDesc("测试Desc2");
    		product2.setImgAddr("test2");
    		product2.setPriority(2);
    		product2.setEnableStatus(0);
    		product2.setCreateTime(new Date());
    		product2.setLastEditTime(new Date());
    		product2.setShop(shop1);
    		product2.setProductCategory(pc1);
    
    		Product product3 = new Product();
    		product3.setProductName("test3");
    		product3.setProductDesc("测试Desc3");
    		product3.setImgAddr("test3");
    		product3.setPriority(3);
    		product3.setEnableStatus(1);
    		product3.setCreateTime(new Date());
    		product3.setLastEditTime(new Date());
    		product3.setShop(shop1);
    		product3.setProductCategory(pc1);
    
    		// 判断添加是否成功
    		int effectedNum = productDao.insertProduct(product1);
    		assertEquals(1, effectedNum);
    		effectedNum = productDao.insertProduct(product2);
    		assertEquals(1, effectedNum);
    		effectedNum = productDao.insertProduct(product3);
    		assertEquals(1, effectedNum);
    	}
    }
    language-java复制代码

    6.1.2 service 层

    1. 接口 ProductService.java
    public interface ProductService {
    	// 添加商品信息以及图片处理。思路:1.处理缩略图;2.处理商品详情图片;3.添加商品信息
    	//ProductExecution addProduct(Product product, InputStream thumbnail, String thumbnailName, List<InputStream> productImgList, List<String> productImgNameList) throws ProductOperationException;
    	// 封装 图片和图片名
    	ProductExecution addProduct(Product product, ImageHolder thumbnail, List<ImageHolder> productImgList) throws ProductOperationException;
    }
    language-java复制代码
    1. 实现类
    @Service
    public class ProductServiceImpl implements ProductService{
    
    	@Autowired
    	private ProductDao productDao;
    	@Autowired
    	private ProductImgDao productImgDao;
    	/**
    	 * 添加缩略图(和店铺注册一样)
    	 * @param product
    	 * @param thumbnail
    	 */
    	private void addThumbnail(Product product, ImageHolder thumbnail) {
    		String dest = PathUtil.getShopImagePath(product.getShop().getShopId());
    		String thumbnailAddr = ImageUtil.generateThumbnail(thumbnail, dest);
    		product.setImgAddr(thumbnailAddr);
    	}
    	/**
    	 * 批量添加图片
    	 * @param product
    	 * @param productImgHolder
    	 */
    	private void addProductImgList(Product product, List<ImageHolder> productImgHolderList) {
    		// 获取图片存储路径,这里直接存放到相应店铺的文件夹地下
    		String dest = PathUtil.getShopImagePath(product.getShop().getShopId());
    		List<ProductImg> productImgList = new ArrayList<ProductImg>();
    		// 遍历图片一次去处理,并添加进 productImg 实体类里
    		for(ImageHolder productImgHolder: productImgHolderList) {
    			String imgAddr = ImageUtil.generateNormalImg(productImgHolder, dest);
    			ProductImg productImg = new ProductImg();
    			productImg.setImgAddr(imgAddr);
    			productImg.setProductId(product.getProductId());
    			productImg.setCreateTime(new Date());
    			productImgList.add(productImg);
    		}
    		// 如果确实是有图片需要添加的,就执行批量添加操作
    		if(productImgList.size() > 0) {
    			try {
    				int effectedNum = productImgDao.batchInsertProductImg(productImgList);
    				if(effectedNum <= 0) {
    					throw new ProductOperationException("创建商品详情图片失败");
    				}
    			} catch(Exception e) {
    				throw new ProductOperationException("创建商品详情图片失败:" + e.toString());
    			}
    		}
    	}
    
    	@Override
    	@Transactional // 通过spring的事务管理,去管理以下4个步骤,其中任何一步出错了就会回滚数据
    	/**
    	 * 方法步骤:
    	 * 1. 处理缩略图,获取缩略图相对路径并赋值给product(和注册店铺差不多)
    	 * 2. 往 tb_product 写入商品信息,成功获取 productId(因为在mapper 使用了 useGeneratedKeys="true")
    	 * 3. 结合 productId 批量处理商品详情图
    	 * 4. 将商品详情列表批量插入 tb_product_img中
    	 */
    	public ProductExecution addProduct(Product product, ImageHolder thumbnail, List<ImageHolder> productImgHolderList)
    			throws ProductOperationException {
    		// 空值判断
    		if(product != null && product.getShop() != null && product.getShop().getShopId() != null) {
    			// 给商品设置上默认属性
    			product.setCreateTime(new Date());
    			product.setLastEditTime(new Date());
    			// 默认为上架的状态
    			product.setEnableStatus(1);
    			// 若商品缩略图不为空则添加
    			if(thumbnail != null) {
    				addThumbnail(product, thumbnail);
    			}
    			try {
    				// 创建商品信息
    				int effectedNum = productDao.insertProduct(product);
    				if(effectedNum <= 0) {
    					throw new ProductOperationException("创建商品失败");
    				}
    			} catch(Exception e) {
    				throw new ProductOperationException("创建商品失败:" + e.toString());
    			}
    			// 若商品详情图不为空则添加
    			if(productImgHolderList != null && productImgHolderList.size() > 0) {
    				addProductImgList(product, productImgHolderList);
    			}
    			return new ProductExecution(ProductStateEnum.SUCCESS, product);
    		} else {
    			// 传参为空则返回空值错误信息
    			return new ProductExecution(ProductStateEnum.EMPTY_LIST);
    		}
    	}
    
    }
    language-java复制代码
    1. 单元测试
    public class ProductServiceTest extends BaseTest {
    
    	@Autowired
    	private ProductService productService;
    
    	@Test
    	public void testAddProduct() throws ShopOperationException, FileNotFoundException{
    		// 创建shopId 为1且productCategoryId为1的商品实例并给其成员变量赋值
    		Product product = new Product();
    		Shop shop = new Shop();
    		shop.setShopId(1L);
    		ProductCategory pc = new ProductCategory();
    		pc.setProductCategoryId(1L);
    		product.setShop(shop);
    		product.setProductCategory(pc);
    		product.setProductName("测试商品1");
    		product.setProductDesc("测试商品1");
    		product.setPriority(20);
    		product.setCreateTime(new Date());
    		product.setEnableStatus(ProductStateEnum.SUCCESS.getState());
    		// 创建缩略图文件流
    		File thumbnailFile =new File("/Users/jxh/Downloads/src.jpeg");
    		InputStream is = new FileInputStream(thumbnailFile);
    		ImageHolder thumbnail = new ImageHolder(thumbnailFile.getName(), is);
    		// 创建两个商品详情图片文件流并将他们添加到详情图列表中
    		File productImg1 = new File("/Users/jxh/Downloads/src.jpeg");
    		InputStream is1 = new FileInputStream(productImg1);
    		File productImg2 = new File("/Users/jxh/Downloads/src2.jpeg");
    		InputStream is2 = new FileInputStream(productImg1);
    		List<ImageHolder> productImgList = new ArrayList<ImageHolder>();
    		productImgList.add(new ImageHolder(productImg1.getName(),is1));
    		productImgList.add(new ImageHolder(productImg2.getName(),is2));
    		// 添加商品并验证
    		ProductExecution pe = productService.addProduct(product, thumbnail, productImgList);
    		assertEquals(ProductStateEnum.SUCCESS.getState(), pe.getState());
    
    	}
    }
    language-java复制代码

    6.1.3 controller 层

    @Controller
    @RequestMapping("/shopadmin")
    public class ProductManagementController {
    	@Autowired
    	private ProductService productService;
    
    	// 支持上次商品详情图的最大数量
    	private static final int IMAGEMAXCOUNT = 6;
    
    	@RequestMapping(value = "/addproduct", method = RequestMethod.POST)
    	@ResponseBody
    	private Map<String, Object> addProduct(HttpServletRequest request) {
    		Map<String, Object> modelMap = new HashMap<String, Object>();
    		// 验证码校验
    		if(!CodeUtil.checkVerifyCode(request)) {
    			modelMap.put("success", false);
    			modelMap.put("errMsg", "输入了错误的验证码");
    			return modelMap;
    		}
    		// 接受前端参数的变量的初始化,包括商品,缩略图,详情图列表实体类
    		ObjectMapper mapper = new ObjectMapper();
    		Product product = null;
    		String productStr = HttpServletRequestUtil.getString(request, "productStr");
    		MultipartHttpServletRequest multipartRequest = null;
    		ImageHolder thumbnail = null;
    		List<ImageHolder> productImgList = new ArrayList<ImageHolder>();
    		CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext());
    		try {
    			// 若请求中存在文件流,则取出相关的文件(包括缩略图和详情图)
    			if(multipartResolver.isMultipart(request)) {
    				multipartRequest = (MultipartHttpServletRequest) request;
    				// 取出缩略图并构建 ImageHolder对象
    				CommonsMultipartFile thumbnailFile = (CommonsMultipartFile) multipartRequest.getFile("thumbnail");
    				thumbnail = new ImageHolder(thumbnailFile.getOriginalFilename(), thumbnailFile.getInputStream());
    				// 取出详情图列表并构建List<ImageHolder>列表对象,最多支持六张图片上传
    				for(int i = 0; i < IMAGEMAXCOUNT; i++) {
    					CommonsMultipartFile productImgFile = (CommonsMultipartFile) multipartRequest.getFile("productImg" + i);
    					if(productImgFile != null) {
    						// 若取出的第i个详情图片文件流不为空,则将其加入详情图列表
    						ImageHolder productImg = new ImageHolder(productImgFile.getOriginalFilename(), productImgFile.getInputStream());
    						productImgList.add(productImg);
    					}else {
    						// 若取出的第i个详情图片文件流为空,则终止循环
    						break;
    					}
    				}
    			}else {
    				modelMap.put("success", false);
    				modelMap.put("errMsg", "上传图片不能为空");
    				return modelMap;
    			}
    
    		} catch(Exception e) {
    			modelMap.put("success", false);
    			modelMap.put("errMsg", e.toString());
    			return modelMap;
    		}
    
    		try {
    			// 尝试获取前端传过来的表单string流并将其转换成 Product实体类
    			product = mapper.readValue(productStr, Product.class);
    		} catch(Exception e) {
    			modelMap.put("success", false);
    			modelMap.put("errMsg", e.toString());
    			return modelMap;
    		}
    		// 若Product信息,缩略图以及详情图聊表为空,则开始进行商品添加操作
    		if(product != null && thumbnail != null && productImgList.size() > 0) {
    			try {
    				// 从session 中获取当前店铺的Id 并赋值给 product, 减少对前端数据的依赖
    				Shop currentShop = (Shop) request.getSession().getAttribute("currentShop");
    				Shop shop = new Shop();
    				shop.setShopId(currentShop.getShopId());
    				product.setShop(shop);
    				// 执行添加操作
    				ProductExecution pe = productService.addProduct(product, thumbnail, productImgList);
    				if(pe.getState() == ProductStateEnum.SUCCESS.getState()) {
    					modelMap.put("success", true);
    				}else {
    					modelMap.put("success", false);
    					modelMap.put("errMsg", pe.getStateInfo());
    				}
    			} catch(ProductOperationException e) {
    				modelMap.put("success", false);
    				modelMap.put("errMsg", e.toString());
    				return modelMap;
    			}
    		} else {
    			modelMap.put("success", false);
    			modelMap.put("errMsg", "请输入商品信息");
    		}
    
    		return modelMap;
    	}
    }
    language-java复制代码

    6.2 商品编辑

    目标:实现商品的编辑功能(可以借鉴商品的添加和店铺的编辑);明确如何处理已存在的图片(如果有上传新的缩略图或详情图,就把之前的图片删除掉)。
    思路:获取商品信息,传入新的图片就把之前的图片删除,提交按钮修改商品信息。

    6.2.1、dao 层

    1. 接口 ProductDoa.java; ProductImgDao.java

    获取商品信息:Product queryProductById(long productId),
    修改商品信息:int updateProduct(Product product);

    删除商品图片:int deleteProductImgByProductId(long productId); 2. mapper ProductDao.xml & ProductImgDao.xml

    <?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="com.imooc.o2o.dao.ProductDao">
    	<!-- 复合数据类型(返回商品的信息和商品详情图的信息) 接口:Product queryProductById(long productId) -->
    	<resultMap type="com.imooc.o2o.entity.Product"
    		id="productMap">
    		<id column="product_id" property="productId" />
    		<result column="product_name" property="productName" />
    		<result column="product_desc" property="productDesc" />
    		<result column="img_addr" property="imgAddr" />
    		<result column="normal_price" property="normalPrice" />
    		<result column="promotion_price" property="promotionPrice" />
    		<result column="priority" property="priority" />
    		<result column="create_time" property="createTime" />
    		<result column="last_edit_time" property="lastEditTime" />
    		<result column="enable_status" property="enableStatus" />
    		<!--assocication可以指定联合的JavaBean对象; property="role"指定哪个属性是联合的对象; javaType:指定这个属性对象的类型 -->
    		<association column="product_category_id"
    			property="productCategory"
    			javaType="com.imooc.o2o.entity.ProductCategory">
    			<id column="product_category_id" property="productCategoryId" />
    			<result column="product_category_name"
    				property="productCategoryName" />
    		</association>
    		<association property="shop" column="shpo_id"
    			javaType="com.imooc.o2o.entity.Shop">
    			<id column="shop_id" property="shopId" />
    			<result column="owner_id" property="ownerId" />
    			<result column="shop_name" property="shopName" />
    		</association>
    		<!-- 一对多的关系 List<ProductImgList> -->
    		<collection property="productImgList" column="product_id"
    			ofType="com.imooc.o2o.entity.ProductImg">
    			<id column="product_img_id" property="productImgId" />
    			<result column="detail_img" property="imgAddr" />
    			<result column="img_desc" property="imgDesc" />
    			<result column="priority" property="priority" />
    			<result column="create_time" property="createTime" />
    			<result column="product_id" property="productId" />
    		</collection>
    	</resultMap>
    
    	<!-- 根据 product id 查询商品信息 -->
    	<select id="queryProductById" resultMap="productMap"
    		parameterType="Long">
    		SELECT
    		p.product_id,
    		p.product_name,
    		p.product_desc,
    		p.img_addr,
    		p.normal_price,
    		p.promotion_price,
    		p.priority,
    		p.create_time,
    		p.last_edit_time,
    		p.enable_status,
    		p.product_category_id,
    		p.shop_id,
    		pm.product_img_id,
    		pm.img_addr AS detail_img,
    		pm.img_desc,
    		pm.priority,
    		pm.create_time
    		FROM
    		tb_product p
    		LEFT JOIN
    		tb_product_img pm
    		ON
    		p.product_id=pm.product_id
    		WHERE
    		p.product_id=#{productId}
    		ORDER BY
    		pm.priority DESC
    	</select>
    	<!-- 更新店铺的信息 -->
    	<update id="updateProduct" parameterType="com.imooc.o2o.entity.Product" keyProperty="product_id" useGeneratedKeys="true">
    		UPDATE tb_product
    		<set>
    			<if test="productName != null">product_name=#{productName},</if>
    			<if test="productDesc != null">product_desc=#{productDesc}</if>
    			<if test="imgAddr != null">img_addr=#{imgAddr},</if>
    			<if test="normalPrice != null">normal_price=#{normalPrice},</if>
    			<if test="promotionPrice != null">promotion_price=#{promotionPrice},</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="productCategory != null and productCategory.productCategoryId != null">
    				product_category_id=#{productCategory.productCategoryId}
    			</if>
    		</set>
    		WHERE product_id=#{productId} AND shop_id=#{shop.shopId}
    	</update>
    </mapper>
    
    <?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="com.imooc.o2o.dao.ProductImgDao">
    	<!-- 根据 product id 删除商品 -->
    	<delete id="deleteProductImgByProductId">
    		DELETE FROM
    		tb_product_img
    		WHERE
    		product_id=#{productId}
    	</delete>
    </mapper>
    language-xml复制代码
    1. JUnit 单元测试
    	<!-- PorductTest.java -->
    	@Test
    	public void testCQueryPorductByProductId() throws Exception{
    		long productId = 1;
    		// 初始化两个商品详情图实例作为 productId 为 1 的商品的详情图片
    		// 批量插入到商品详情图中
    		ProductImg productImg1 = new ProductImg();
    		productImg1.setImgAddr("图片1");
    		productImg1.setImgDesc("测试图片1");
    		productImg1.setPriority(1);
    		productImg1.setCreateTime(new Date());
    		productImg1.setProductId(productId);
    
    		ProductImg productImg2 = new ProductImg();
    		productImg2.setImgAddr("图片1");
    		productImg2.setImgDesc("测试图片1");
    		productImg2.setPriority(1);
    		productImg2.setCreateTime(new Date());
    		productImg2.setProductId(productId);
    		List<ProductImg> productImgList = new ArrayList<ProductImg>();
    		productImgList.add(productImg1);
    		productImgList.add(productImg2);
    		int effectedNum = productImgDao.batchInsertProductImg(productImgList);
    		assertEquals(2, effectedNum);
    		// 查询productId 为1的商品信息并校验返回的详情图实例列表size 是否为2
    		Product product = productDao.queryProductById(productId);
    		assertEquals(2, product.getProductImgList().size());
    		// 删除新增的这两个商品详情图实例
    		effectedNum = productImgDao.deleteProductImgByProductId(productId);
    		assertEquals(2, effectedNum);
    	}
    
    	@Test
    	public void testDUpdateProduct() throws Exception{
    		Product product = new Product();
    		ProductCategory pc = new ProductCategory();
    		Shop shop = new Shop();
    		shop.setShopId(1L);
    		pc.setProductCategoryId(2L);
    		product.setProductId(1L);
    		product.setShop(shop);
    		product.setProductName("第二个产品");
    		product.setProductCategory(pc);
    		// 修改 productId 为1的商品的名称
    		// 以及商品类别并校验影响的行数是否为 1
    		int effectedNum = productDao.updateProduct(product);
    		assertEquals(1, effectedNum);
    	}
    	<!-- ProductImgDaoTest.java -->
    	@Test
    	@Ignore
    	public void testCDeleteProductImgByProductId() throws Exception {
    		// 删除新增的两条商品详情图片记录
    		long productId = 1;
    		int effectedNum = productImgDao.deleteProductImgByProductId(productId);
    		assertEquals(2, effectedNum);
    	}
    language-java复制代码

    6.2.2、Service 层

    1. 接口 ProductService.java
    // 通过商品 Id 查询唯一的商品信息(商品编辑)
    	Product getProductById(long productId);
    	// 修改商品信息以及图片处理(商品编辑)
    	ProductExecution modifyProduct(Product product, ImageHolder thumbnail, List<ImageHolder> productImgList) throws ProductOperationException;
    language-java复制代码
    1. 实现类 ProductServiceImpl.java
    	// 商品编辑,获取商品信息
    	@Override
    	public Product getProductById(long productId) {
    		return productDao.queryProductById(productId);
    	}
    
    	// 商品编辑,修改商品信息
    	@Override
    	@Transactional
    	/**
    	 * 思路:1.若缩略图参数有值,则处理缩略图,若原先存在缩略图则先删除再添加新图,之后获取缩略图相对路径并赋值给product
    	 * 		2.若商品详情图列表参数有值,对商品详情图片列表进行同样的操作
    	 * 		3.将tb_product_img下面的该商品原先的商品详情图记录全部清除
    	 * 		4.更新tb_product_img 以及 tb_product的信息
    	 */
    	public ProductExecution modifyProduct(Product product, ImageHolder thumbnail, List<ImageHolder> productImgHolderList)
    			throws ProductOperationException {
    		// 空值判断
    		if(product != null && product.getShop() != null && product.getShop().getShopId() != null) {
    			// 给商品设置上默认属性
    			product.setLastEditTime(new Date());
    			// 若商品缩略图不为空且原有缩略图不为空则删除原有缩略图并添加
    			if(thumbnail != null) {
    				// 先获取一遍原有信息,因为原来的信息里有图片地址
    				Product tempProduct = productDao.queryProductById(product.getProductId());
    				if(tempProduct.getImgAddr() != null) {
    					ImageUtil.deleteFileOrPath(tempProduct.getImgAddr());
    				}
    				addThumbnail(product, thumbnail);
    			}
    			// 如果有新存入的商品详情图,则将原来的删除,并添加新的图片
    			if(productImgHolderList != null && productImgHolderList.size() > 0) {
    				deleteProductImgList(product.getProductId());
    				addProductImgList(product, productImgHolderList);
    			}
    			try {
    				// 更新商品信息
    				int effentedNum = productDao.updateProduct(product);
    				if(effentedNum <= 0) {
    					throw new ProductOperationException("更新商品信息失败");
    				}
    				return new ProductExecution(ProductStateEnum.SUCCESS, product);
    			} catch(Exception e) {
    				throw new ProductOperationException("更新商品信息失败:" + e.toString());
    			}
    		}else {
    			return new ProductExecution(ProductStateEnum.EMPTY_LIST);
    		}
    	}
    
    	// 根据 product id 删除商品详情图
    	private void deleteProductImgList(Long productId) {
    		// 根据 productId 获取原来的图片
    		List<ProductImg> productImgList = productImgDao.queryProductImgList(productId);
    		// 删除原来的图片
    		for(ProductImg productImg : productImgList) {
    			ImageUtil.deleteFileOrPath(productImg.getImgAddr());
    		}
    		// 删除数据库里原有图片的信息
    		productImgDao.deleteProductImgByProductId(productId);
    language-java复制代码
    1. 测试 ProductServiceTest.java
    	@Test
    	public void testModifyProduct() throws ShopOperationException, FileNotFoundException {
    		// 创建shopId 为1 且productCategoryId 为 1的商品实例并给其成员变量赋值
    		Product product = new Product();
    		Shop shop = new Shop();
    		shop.setShopId(1L);
    		ProductCategory pc = new ProductCategory();
    		pc.setProductCategoryId(1L);
    		product.setProductId(1L);
    		product.setShop(shop);
    		product.setProductCategory(pc);
    		product.setProductName("正式的商品Test");
    		product.setProductDesc("正式的商品Test");
    		// 创建缩略图文件流
    		File thumbnailFile = new File("/Users/jxh/Downloads/src.jpeg");
    		InputStream is = new FileInputStream(thumbnailFile);
    		ImageHolder thumbnail = new ImageHolder(thumbnailFile.getName(), is);
    		// 创建两个商品详情图文件流并将他们添加到详情图列表中
    		File productImg1 = new File("/Users/jxh/Downloads/src.jpeg");
    		InputStream is1 = new FileInputStream(productImg1);
    		File productImg2 = new File("/Users/jxh/Downloads/src2.jpeg");
    		InputStream is2 = new FileInputStream(productImg2);
    		List<ImageHolder> productImgList = new ArrayList<ImageHolder>();
    		productImgList.add(new ImageHolder(productImg1.getName(), is1));
    		productImgList.add(new ImageHolder(productImg2.getName(), is2));
    		// 添加商品并验证
    		ProductExecution pe = productService.modifyProduct(product, thumbnail, productImgList);
    		assertEquals(ProductStateEnum.SUCCESS.getState(), pe.getState());
    	}
    language-java复制代码

    6.2.3 Controller 层 ProductCategoryManagementController.java

    // 商品编辑
    	@RequestMapping(value="/modifyproduct", method=RequestMethod.POST)
    	@ResponseBody
    	private Map<String, Object> modifyProduct(HttpServletRequest request) {
    		Map<String, Object> modelMap = new HashMap<String, Object>();
    		//是商品编辑时候嗲用还是上下架操作的时候调用
    		//若为前者则进行验证码判断,后者则跳过验证码判断
    		boolean statusChange = HttpServletRequestUtil.getBoolean(request, "statusChange");
    		// 验证码判断
    		if(!statusChange && !CodeUtil.checkVerifyCode(request)) {
    			modelMap.put("success", false);
    			modelMap.put("errMsg", "输入了错误的验证码");
    			return modelMap;
    		}
    		// 接受前端参数的变量的初始化,包括商品,缩略图,详情图列表实体类
    		ObjectMapper mapper = new ObjectMapper();
    		Product product = null;
    		ImageHolder thumbnail = null;
    		List<ImageHolder> productImgList = new ArrayList<ImageHolder>();
    		CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext());
    		// 若请求中存在文件流,则取相关的文件(包括缩略图和详情图)
    		try {
    			if(multipartResolver.isMultipart(request)) {
    				handleImage(request, productImgList);
    			}
    		} catch(Exception e) {
    			modelMap.put("success", false);
    			modelMap.put("errMsg", "上传图片不能为空");
    			return modelMap;
    		}
    		try {
    			String productStr = HttpServletRequestUtil.getString(request, "productStr");
    			// 尝试获取前端传过来的表单string流并将其转成Product实体类
    			product = mapper.readValue(productStr, Product.class);
    		}catch(Exception e) {
    			modelMap.put("success", false);
    			modelMap.put("errMsg", e.toString());
    			return modelMap;
    		}
    		// 非空判断
    		if(product != null) {
    			try {
    				// 从session中获取当前店铺的Id并赋值给product, 减少对前端数据的依赖
    				Shop currentShop = (Shop) request.getSession().getAttribute("currentShop");
    				Shop shop = new Shop();
    				shop.setShopId(currentShop.getShopId());
    				product.setShop(shop);
    				// 开始进行商品信息变更操作
    				ProductExecution pe = productService.modifyProduct(product, thumbnail, productImgList);
    				if(pe.getState() == ProductStateEnum.SUCCESS.getState()) {
    					modelMap.put("success", true);
    				}else {
    					modelMap.put("success", false);
    					modelMap.put("errMsg", pe.getStateInfo());
    				}
    			} catch(RuntimeException e) {
    				modelMap.put("success", false);
    				modelMap.put("errMsg", e.toString());
    				return modelMap;
    			}
    		} else {
    			modelMap.put("success", false);
    			modelMap.put("errMsg", "请输入商品信息");
    		}
    		return modelMap;
    	}
    
    	private void handleImage(HttpServletRequest request, List<ImageHolder> productImgList) throws IOException {
    		ImageHolder thumbnail;
    		MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
    		// 取出缩略图并构建ImageHolder对象
    		CommonsMultipartFile thumbnailFile = (CommonsMultipartFile) multipartRequest.getFile("thumbnailFile");
    		if(thumbnailFile != null) {
    			thumbnail = new ImageHolder(thumbnailFile.getOriginalFilename(), thumbnailFile.getInputStream());
    		}
    		// 取出详情图列表并构建List<ImageHolder>列表对象,最多支持六张图片上传
    		for(int i = 0; i < IMAGEMAXCOUNT; i++) {
    			CommonsMultipartFile productImgFile = (CommonsMultipartFile) multipartRequest.getFile("productImg" + i);
    			if(productImgFile != null) {
    				// 若取出的第i个详情图片文件流不为空,则将其加入详情图列表
    				ImageHolder productImg = new ImageHolder(productImgFile.getOriginalFilename(), productImgFile.getInputStream());
    				productImgList.add(productImg);
    			}else {
    				// 若取出的第i个详情图片文件流为空,则终止循环
    				break;
    			}
    		}
    	}
    language-java复制代码

    6.3 商品列表展示

    第七章 首页

    目标: 头条读取以及滚动播放;一级类别列表的获取

    7.1


    总结

    SSM 框架实现功能的步骤

    1. 在 dao 层
    • 创建出相应的接口
    • 定义相关的方法
    1. 在 mapper 中
    • 在 resources/mapper 下面创建出与 dao 层一一对应的 xml 文件
    • 在 xml 文件中定义出方法所需要的 SQL
    1. 在 service 层
    • 实现类继承接口,包含类注解(@Service),
    • 使用注解(@Autowired)将 dao 层接口注入进来
    • 调用 dao 层的方法,将我们需要的数据返回到 Controller 去
    1. 在 controller 层
    • 包含有注解(@Controller,@RequestMapping(“/url”))的类
    • 使用注解(@Autowired)将 dao 层接口注入进来
    • 创建包含有注解(@RequestMapping, @ResponseBody)的方法。
    • 调用 dao 层接口的方法获取数据后,直接将数据返回前台。

    Junit

    Junit 测试步骤

    1、定义一个基类

    1. 一个包含类注解(@RunWith(SpringJunit4ClassRunner.class))的类,指定使用那个类来单元测试
    2. 一个包含类注解(@ContextConfiguration({“1.xml”,”2.xml”}))的类,指定 junit spring 配置文件的文件,1.xml 为 junit 的 dao 层做服务的,2.xml 为 service 单元测试做服务配置的。

    2、定义一个实现类

    1. 定义一个方法,包含注解(@Test)的测试方法。

    Junit 拓展

    Junit 测试回环

    测试回环是通过测试进行的数据插入,再通过测试进行数据的删除,在测试中推荐使用,这样对数据库基本没影响

    Junit 顺序执行

    做单元测试是,默认是不一定安装顺序执行测试的,但是有时候需要按顺序的执行测试,所以该注解是安装名字的顺序来执行测试。可在测试方法名字添加顺序:testAxxx, testBxxx


    SSM 重点知识

    1. SpringMVC: DispatcherServlet
    2. Spring: IOC 和 AOP
    • IOC
    • Aop
      例如,增删改查的操作,都需要做权限验证,我们并不希望权限验证的代码杂糅在我们增删改查的方法里,我们可以通过 AOP 在程序运行时候动态的将我们权限代码植入到我们的增删改查方法的前面,以完成权限的验证。AOP 的实现方式,就是动态代理。动态代理有 JDK 和 CJL 两种方式实现。
    1. MyBatis:ORM
    • ORM 就是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。
    • 关系映射的实现方式。


    转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以邮件至 hjxstart@126.com