ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
# 【第十一章】 SSH集成开发积分商城 之 11.3 实现积分商城层 ——跟我学spring3 ## 11.3  实现积分商城层 ### 11.3.1  概述 积分商城是基于通用层之上进行开发,这样我们能减少很多重复的劳动,加快项目开发进度。 ### 11.3.2 实现数据模型层 **1、商品表,**定义了如商品名称、简介、原需积分、现需积分等,其中是否发布表示只有发布(true)了的商品才会在前台删除,是否已删除表示不会物理删除,商品不应该物理删除,而是逻辑删除,版本属性用于防止并发更新。 ``` package cn.javass.point.model; /** 商品表 */ @Entity @Table(name = "tb_goods") public class GoodsModel implements java.io.Serializable { /** 主键 */ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", length = 10) private int id; /** 商品名称 */ @Column(name = "name", nullable = false, length = 100) private String name; /** 商品简介 */ @Column(name = "description", nullable = false, length = 100) private String description; /** 原需积分 */ @Column(name = "original_point", nullable = false, length = 10) private int originalPoint; /** 现需积分 */ @Column(name = "now_point", nullable = false, length = 10) private int nowPoint; /** 是否发布,只有发布的在前台显示 */ @Column(name = "published", nullable = false) private boolean published; /** 是否删除,商品不会被物理删除的 */ @Column(name = "is_delete", nullable = false) private boolean deleted; /** 版本 */ @Version @Column(name = "version", nullable = false, length = 10) private int version; //省略getter和setter、hashCode及equals,实现请参考源代码 } ``` **2、商品兑换码表,**定义了兑换码、兑换码所属商品(兑换码和商品直接是多对一关系)、购买人、购买时间、是否已经购买(防止一个兑换码多个用户兑换)、版本。 ``` package cn.javass.point.model; import java.util.Date; //省略部分import /** 商品兑换码表 */ @Entity @Table(name = "tb_goods_code") public class GoodsCodeModel implements java.io.Serializable { /** 主键 */ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", length = 10) private int id; /** 所属商品 */ @ManyToOne private GoodsModel goods; /** 兑换码*/ @Column(name = "code", nullable = false, length = 100) private String code; /** 兑换人,实际环境中应该和用户表进行对应*/ @Column(name = "username", nullable = true, length = 100) private String username; /** 兑换时间*/ @Column(name = "exchange_time") private Date exchangeTime; /** 是否已经兑换*/ @Column(name = "exchanged") private boolean exchanged = false; /** 版本 */ @Version @Column(name = "version", nullable = false, length = 10) private int version; //省略getter和setter、hashCode及equals,实现请参考源代码 } ``` 3、**商品表及商品兑换码表之间关系,**即一个商品有多个兑换码,如图11-10所示: ![](https://box.kancloud.cn/2016-05-13_5735472378a5d.JPG) 图11-10商品表及商品兑换码表之间关系 **4、 创建数据库及表结构的SQL语句文件(sql/ pointShop_schema.sql):** ``` CREATE DATABASE IF NOT EXISTS `point_shop` DEFAULT CHARACTER SET 'utf8'; USE `point_shop`; DROP TABLE IF EXISTS `tb_goods_code`; DROP TABLE IF EXISTS `tb_goods`; -- ---------------------------- -- Table structure for 商品表 -- ---------------------------- CREATE TABLE `tb_goods` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '商品id', `name` varchar(100) NOT NULL COMMENT '商品名称', `description` varchar(100) NOT NULL COMMENT '商品简介', `original_point` int(10) unsigned NOT NULL COMMENT '原需积分', `now_point` int(10) unsigned NOT NULL COMMENT '现需积分', `published` bool NOT NULL COMMENT '是否发布', `is_delete` bool NOT NULL DEFAULT false COMMENT '是否删除', `version` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '版本', PRIMARY KEY (`id`), INDEX(`name`), INDEX(`published`) )ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品表'; -- ---------------------------- -- Table structure for 商品兑换码表 -- ---------------------------- CREATE TABLE `tb_goods_code` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id', `username` varchar(100) COMMENT '兑换用户', `goods_id` int(10) unsigned NOT NULL COMMENT '所属商品id', `code` varchar(100) NOT NULL COMMENT '积分', `exchange_time` datetime COMMENT '购买时间', `exchanged` bool DEFAULT false COMMENT '是否已经兑换', `version` int(10) unsigned NOT NULL DEFAULT 0 COMMENT '版本', PRIMARY KEY (`id`), FOREIGN KEY (`goods_id`) REFERENCES `tb_goods` (`id`) ON DELETE CASCADE )ENGINE=InnoDB AUTO_INCREMENT=1000000 DEFAULT CHARSET=utf8 COMMENT='商品兑换码表'; ``` Mysql数据库引擎应该使用InnoDB,如果使用MyISM将不支持事务。 ### 11.3.3  实现数据访问层 数据访问层只涉及与底层数据库或文件系统等打交道,不会涉及业务逻辑,一定注意层次边界,不要在数据访问层实现业务逻辑。 商品模块的应该实现如下功能: * 继承通用数据访问层的CRUD功能; * 分页查询所有已发布的商品 * 统计所有已发布的商品; 商品兑换码模块的应该实现如下功能: * 继承通用数据访问层的CRUD功能; * 根据商品ID分页查询该商品的兑换码 * 根据商品ID统计该商品的兑换码记录数; * 根据商品ID获取一个还没有兑换的商品兑换码 **1、商品及商品兑换码DAO接口定义:** 商品及商品兑换码DAO接口定义直接继承IBaseDao,无需在这些接口中定义重复的CRUD方法了,并通过泛型指定数据模型类及主键类型。 ``` package cn.javass.point.dao; //省略import /** 商品模型对象的DAO接口 */ public interface IGoodsDao extends IBaseDao<GoodsModel, Integer> { /** 分页查询所有已发布的商品*/ List<GoodsModel> listAllPublished(int pn); /** 统计所有已发布的商品记录数*/ int countAllPublished(); } ``` ``` package cn.javass.point.dao; //省略import /** 商品兑换码模型对象的DAO接口 */ public interface IGoodsCodeDao extends IBaseDao<GoodsCodeModel, Integer> { /** 根据商品ID统计该商品的兑换码记录数*/ public int countAllByGoods(int goodsId); /** 根据商品ID查询该商品的兑换码列表*/ public List<GoodsCodeModel> listAllByGoods(int pn, int goodsId); /** 根据商品ID获取一个还没有兑换的商品兑换码 */ public GoodsCodeModel getOneNotExchanged(int goodsId); } ``` **2、 商品及商品兑换码DAO接口实现定义:** DAO接口实现定义都非常简单,对于CRUD实现直接从BaseHibernateDao继承即可,无需再定义重复的CRUD实现了,并通过泛型指定数据模型类及主键类型。 ``` package cn.javass.point.dao.hibernate; //省略import public class GoodsHibernateDao extends BaseHibernateDao<GoodsModel, Integer> implements IGoodsDao { @Override //覆盖掉父类的delete方法,不进行物理删除 public void delete(Integer id) { GoodsModel goods = get(id); goods.setDeleted(true); update(goods); } @Override //覆盖掉父类的getCountAllHql方法,查询不包括逻辑删除的记录 protected String getCountAllHql() { return super.getCountAllHql() + " where deleted=false"; } @Override //覆盖掉父类的getListAllHql方法,查询不包括逻辑删除的记录 protected String getListAllHql() { return super.getListAllHql() + " where deleted=false"; } @Override //统计没有被逻辑删除的且发布的商品数量 public int countAllPublished() { String hql = getCountAllHql() + " and published=true"; Number result = unique(hql); return result.intValue(); } @Override //查询没有被逻辑删除的且发布的商品 public List<GoodsModel> listAllPublished(int pn) { String hql = getListAllHql() + " and published=true"; return list(hql, pn, Constants.DEFAULT_PAGE_SIZE); } } ``` ``` package cn.javass.point.dao.hibernate; //省略import public class GoodsCodeHibernateDao extends BaseHibernateDao<GoodsCodeModel, Integer> implements IGoodsCodeDao { @Override //根据商品ID查询该商品的兑换码 public List<GoodsCodeModel> listAllByGoods(int pn, int goodsId) { final String hql = getListAllHql() + " where goods.id = ?"; return list(hql, pn, Constants.DEFAULT_PAGE_SIZE , goodsId); } @Override //根据商品ID统计该商品的兑换码数量 public int countAllByGoods(int goodsId) { final String hql = getCountAllHql() + " where goods.id = ?"; Number result = unique(hql, goodsId); return result.intValue(); } ``` **3、Spring DAO层配置文件(resources/cn/javass/point/dao/ applicationContext-hibernate.xml):** DAO配置文件中定义Hibernate的SessionFactory、事务管理器和DAO实现。 ``` <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="dataSource"/><!-- 1、指定数据源 --> <property name="annotatedClasses"> <!-- 2、指定注解类 --> <list> <value>cn.javass.point.model.GoodsModel</value> <value>cn.javass.point.model.GoodsCodeModel</value> </list> </property> <property name="hibernateProperties"><!-- 3、指定Hibernate属性 --> <props> <prop key="hibernate.dialect">${hibernate.dialect}</prop> <prop key="hibernate.show_sql">${hibernate.show_sql}</prop> <prop key="hibernate.format_sql">${hibernate.format_sql}</prop> <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop> </props> </property> </bean> <bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"/> </bean> ``` ``` <bean id="abstractDao" abstract="true" init-method="init"> <property name="sessionFactory" ref="sessionFactory"/> </bean> <bean id="goodsDao" class="cn.javass.point.dao.hibernate.GoodsHibernateDao" parent="abstractDao"/> <bean id="goodsCodeDao" class="cn.javass.point.dao.hibernate.GoodsCodeHibernateDao" parent="abstractDao"/> ``` **4、修改替换配置元数据的资源文件(resources/resources.properties),添加Hibernate属性相关:** ``` #Hibernate属性 hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect hibernate.hbm2ddl.auto=none hibernate.show_sql=false hibernate.format_sql=true ``` ### 11.3.4 实现业务逻辑层 业务逻辑层实现业务逻辑,即系统中最复杂、最核心的功能,不应该在业务逻辑层出现如数据库访问等底层代码,对于这些操作应委托给数据访问层实现,从而保证业务逻辑层的独立性和可复用性,并应该在业务逻辑层组装分页对象。 商品模块应该实现如下功能: * CURD操作,直接委托给通用业务逻辑层; * 根据页码查询所有已发布的商品的分页对象,即查询指定页的记录,这是和数据访问层不同的; 商品兑换码模块应该实现如下功能: * CURD操作,直接委托给通用业务逻辑层; * 根据页码和商品Id查询查询所有商品兑换码分页对象,即查询指定页的记录; * 新增指定商品的兑换码,用于对指定商品添加兑换码; * 购买指定商品兑换码操作,用户根据商品购买该商品的兑换码,如果指定商品的兑换码没有了将抛出没有兑换码异常NotCodeException; **1、商品及商品兑换码Service接口定义:** 接口定义时,对于CRUD直接继承IBaseService即可,无需再在这些接口中定义重复的CRUD方法了,并通过泛型指定数据模型类及数据模型的主键。 ``` package cn.javass.point.service; //省略import public interface IGoodsService extends IBaseService<GoodsModel, Integer> { /**根据页码查询所有已发布的商品的分页对象*/ Page<GoodsModel> listAllPublished(int pn); } ``` ``` package cn.javass.point.service; //省略import public interface IGoodsCodeService extends IBaseService<GoodsCodeModel, Integer> { /** 根据页码和商品Id查询查询所有商品兑换码分页对象*/ public Page<GoodsCodeModel> listAllByGoods(int pn, int goodsId); /** 新增指定商品的兑换码*/ public void save(int goodsId, String[] codes); /** 购买指定商品兑换码 */ GoodsCodeModel buy(String username, int goodsId) throws NotCodeException ; } ``` **2、NotCodeException异常定义,表示指定商品的兑换码已经全部被兑换了,没有剩余的兑换码了:** ``` package cn.javass.point.exception; /** 购买失败异常,表示没有足够的兑换码 */ public class NotCodeException extends RuntimeException { } ``` NotCodeException异常类实现RuntimeException,当需要更多信息时可以在异常中定义,异常比硬编码错误代码(如-1表示没有足够的兑换码)更好理解。 **3、商品及商品兑换码Service接口实现定义:** 接口实现时,CRUD实现直接从BaseServcice继承即可,无需再在这些专有实现中定义重复的CRUD实现了,并通过泛型指定数据模型类及数据模型的主键。 ``` package cn.javass.point.service.impl; //省略import public class GoodsServiceImpl extends BaseServiceImpl<GoodsModel, Integer> implements IGoodsService { @Override public Page<GoodsModel> listAllPublished(int pn) { int count = getGoodsDao().countAllPublished(); List<GoodsModel> items = getGoodsDao().listAllPublished(pn); return PageUtil.getPage(count, pn, items, Constants.DEFAULT_PAGE_SIZE); } IGoodsDao getGoodsDao() {//将通用DAO转型 return (IGoodsDao) getDao(); } } ``` ``` package cn.javass.point.service.impl; //省略import public class GoodsCodeServiceImpl extends BaseServiceImpl<GoodsCodeModel, Integer> implements IGoodsCodeService { private IGoodsService goodsService; public void setGoodsService(IGoodsService goodsService) {//注入IGoodsService this.goodsService = goodsService; } private IGoodsCodeDao getGoodsCodeDao() {//将注入的通用DAO转型 return (IGoodsCodeDao) getDao(); } @Override public Page<GoodsCodeModel> listAllByGoods(int pn, int goodsId) { Integer count = getGoodsCodeDao().countAllByGoods(goodsId); List<GoodsCodeModel> items = getGoodsCodeDao().listAllByGoods(pn, goodsId); return PageUtil.getPage(count, pn, items, Constants.DEFAULT_PAGE_SIZE); } @Override public void save(int goodsId, String[] codes) { GoodsModel goods = goodsService.get(goodsId); for(String code : codes) { if(StringUtils.hasText(code)) { GoodsCodeModel goodsCode = new GoodsCodeModel(); goodsCode.setCode(code); goodsCode.setGoods(goods); save(goodsCode); } } } @Override public GoodsCodeModel buy(String username, int goodsId) throws NotCodeException { //1、实际实现时要验证用户积分是否充足 //2、其他逻辑判断 //3、实际实现时要记录交易记录开始 GoodsCodeModel goodsCode = getGoodsCodeDao().getOneNotExchanged(goodsId); if(goodsCode == null) { //3、实际实现时要记录交易记录失败 throw new NotCodeException(); //目前只抛出一个异常,还可能比如并发购买情况 } goodsCode.setExchanged(true); goodsCode.setExchangeTime(new Date()); goodsCode.setUsername(username); save(goodsCode); //3、实际实现时要记录交易记录成功 return goodsCode; } } ``` save方法和buy方法实现并不是最优的,save方法中如果兑换码有上千个怎么办?这时就需要批处理了,通过批处理比如20条一提交数据库来提高性能。buy方法就要考虑多个用户同时购买同一个兑换码如何处理? 交易历史一定要记录,从交易开始到交易结束(不管成功与否)一定要记录用于当客户投诉时查询相应数据。 **4、Spring Service层配置文件(resources/cn/javass/point/service/ applicationContext-service.xml):** Service层配置文件定义了事务和Service实现。 ``` <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="save*" propagation="REQUIRED" /> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="create*" propagation="REQUIRED" /> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="del*" propagation="REQUIRED" /> <tx:method name="remove*" propagation="REQUIRED" /> <tx:method name="buy*" propagation="REQUIRED" /> <tx:method name="count*" propagation="SUPPORTS" read-only="true" /> <tx:method name="find*" propagation="SUPPORTS" read-only="true" /> <tx:method name="list*" propagation="SUPPORTS" read-only="true" /> <tx:method name="*" propagation="SUPPORTS" read-only="true" /> </tx:attributes> </tx:advice> ``` ``` <aop:config> <aop:pointcut id="txPointcut" expression="execution(* cn.javass.point.service.*.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" /> </aop:config> <bean id="goodsService" class="cn.javass.point.service.impl.GoodsServiceImpl"> <property name="dao" ref="goodsDao"/> </bean> <bean id="goodsCodeService" class="cn.javass.point.service.impl.GoodsCodeServiceImpl"> <property name="dao" ref="goodsCodeDao"/> <property name="goodsService" ref="goodsService"/> </bean> ``` ### 11.3.5 实现表现层 表现层显示页面展示和交互,应该支持多种视图技术(如JSP、Velocity),表现层实现不应该实现诸如业务逻辑层功能,只负责调用业务逻辑层查找数据模型并委托给相应的视图进行展示数据模型。 积分商城分为前台和后台,前台负责与客户进行交互,如购买商品;后台是负责商品及商品兑换码维护的,只应该管理员有权限操作。 **后台模块:** * 商品管理模块:负责商品的维护,包括列表、新增、修改、删除、查询所有商品兑换码功能; * 商品兑换码管理模块:包括列表、新增、删除所有兑换码操作; **前台模块:**只有已发布商品展示,用户购买指定商品时,如果购买成功则给用户发送兑换码,购买失败给用户错误提示。 表现层Action实现时一般使用如下规约编程: * **Action方法定义:**使用如list方法表示展示列表,doAdd方法表示去新增页面,add方法表示提交新增页面的结果并委托给Service层进行处理; * **结果定义:**如使用“list”结果表示到展示列表页面,“add”结果去新增页面等等; * **参数设置:**一般使用如“model”表示数据模型,使用“page”表示分页对象。 **1、集成Struts2和Spring配置:** **1.1、Spring Action配置文件:即Action将从Spring容器中获取,前台和后台配置文件应该分开以便好管理;** * 后台Action配置文件resources/cn/javass/web/pointShop-admin-servlet.xml; * 前台Action配置文件resources/cn/javass/web/pointShop-front-servlet.xml; **1.2、Struts配置文件定义(resources/struts.xml):** 为了提高开发效率和采用规约编程,我们将使用模式匹配通配符来定义action。对于管理后台和前台应该分开,URL模式将类似于/{module}/{action}/{method}.action: * module即模块名如admin,action即action前缀名,如后台的“GoodsAction”可以使用“goods”,method即Action中的方法名如“list”。 * 可以在Struts配置文件中使用{1}访问第一个通配符匹配的结果,以此类推; * Reuslt也采用规约编程,即只有符合规律的放置jsp文件才会匹配到,如Result为“/WEB-INF/jsp/admin/{1}/list.jsp”,而URL为/goods/list.action 结果将为“/WEB-INF/jsp/admin/goods/list.jsp”。 ``` <package name="admin" extends="custom-default" namespace="/admin"> <action name="*/*" class="/admin/{1}Action" method="{2}"> <result name="redirect" type="redirect">/admin/{1}/list.action</result> <result name="list">/WEB-INF/jsp/admin/{1}/list.jsp</result> <result name="add">/WEB-INF/jsp/admin/{1}/add.jsp</result> </action> </package> ``` 在此我们继承了“**custom-default**”包来支持action名字中允许“/”。 如“/admin/goods/list.action”将调用cn.javass.point.web.admin.action.GoodsAction的list方法。 ``` <package name="front" extends="custom-default"> <action name="*/*" class="/front/{1}Action" method="{2}"> <result name="redirect" type="redirect">/{1}/list.action</result> <result name="list">/WEB-INF/jsp/front/{1}/list.jsp</result> <result name="add">/WEB-INF/jsp/front/{1}/add.jsp</result> <result name="buyResult">/WEB-INF/jsp/front/{1}/buyResult.jsp</result> </action> </package> ``` 如“/goods/list.action”将调用cn.javass.point.web.front.action.GoodsAction的list方法。 **1.3、web.xml配置:将Spring配置文件加上;** ``` <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:applicationContext-resources.xml, classpath:cn/javass/point/dao/applicationContext-hibernate.xml, classpath:cn/javass/point/service/applicationContext-service.xml, classpath:cn/javass/point/web/pointShop-admin-servlet.xml, classpath:cn/javass/point/web/pointShop-front-servlet.xml </param-value> </context-param> ``` **2、后台商品管理模块** 商品管理模块实现商品的CRUD,本示例只演示新增,删除和更新由于篇幅问题留作练习。 **2.1、Action实现** ``` package cn.javass.point.web.admin.action; //省略import public class GoodsAction extends BaseAction { public String list() {//列表、展示所有商品(包括未发布的) getValueStack().set(PAGE, goodsService.listAll(getPn())); return LIST; } public String doAdd() {//到新增页面 goods = new GoodsModel(); getValueStack().set(MODEL, goods); return ADD; } public String add() {//保存新增模型对象 goodsService.save(goods); return REDIRECT; } //字段驱动数据填充 private int id = -1; //前台提交的商品ID private GoodsModel goods; //前台提交的商品模型对象 //省略字段驱动数据的getter和setter //依赖注入Service private IGoodsService goodsService; //省略依赖注入的getter和setter } ``` **2.2、Spring配置文件定义(resources/cn/javass/web/pointShop-admin-servlet.xml):** ``` <bean name="/admin/goodsAction" class="cn.javass.point.web.admin.action.GoodsAction" scope="prototype"> <property name="goodsService" ref="goodsService"/> </bean> ``` **2.3、JSP实现商品列表页面(WEB-INF/jsp/admin/goods/list.jsp)** 查询所有商品,通过迭代“page.items”(Page对象的items属性中存放着分页列表数据)来显示商品列表,在最后应该有分页标签(请参考源代码,示例无),如类似于“&lt;my:page url="${ctx}/admin/goods/list.action"/&gt;”来定义分页元素。 ``` <%@ page language="java" pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%> <%@ include file="../../common/inc/tld.jsp"%> <jsp:include page="../../common/inc/header.jsp"> <jsp:param name="title" value="商品管理-商品列表"/> </jsp:include> <a href="${ctx}/admin/goods/doAdd.action">新增</a><br/> <table border="1"> <tr> <th>ID</th> <th>商品名称</th> <th>商品描述</th> <th>原需积分</th> <th>现需积分</th> <th>是否已发布</th> <th></th> <th></th> <th></th> </tr> <s:iterator value="page.items"> <tr> <td><a href="${ctx}/admin/goods/toUpdate.action?id=<s:property value='id'/>"><s:property value="id"/></a></td> <td><s:property value="name"/></td> <td><s:property value="description"/></td> <td><s:property value="originalPoint"/></td> <td><s:property value="nowPoint"/></td> <td><s:property value="published"/></td> <td>更新</td> <td>删除</td> <td><a href="${ctx}/admin/goodsCode/list.action?goodsId=<s:property value='id'/>">查看Code码</a></td> </tr> </s:iterator> </table> <jsp:include page="../../common/inc/footer.jsp"/> ``` 右击“pointShop”项目选择【Run As】&gt;【Run On Server】启动Tomcat服务器,在浏览器中输入“http://localhost:8080/pointShop/admin/goods/list.action”将显示图11-11界面。 ![](https://box.kancloud.cn/2016-05-13_57354723972b8.JPG) 图11-11 后台商品列表页面 **2.4、JSP实现商品新增页面(WEB-INF/jsp/admin/goods/add.jsp)** 表单提交到/admin/goods/add.action即cn.javass.point.web.admin.action.GoodsAction的add方法。并将参数绑定到goods属性上,在此我们没有进行数据验证,在实际项目中页面中和Action中都要进行数据验证。 ``` <%@ page language="java" pageEncoding="UTF-8" contentType="text/html; charset=UTF-8" %> <%@ include file="../../common/inc/tld.jsp"%> <jsp:include page="../../common/inc/header.jsp"> <jsp:param name="title" value="商品管理-新增"/> </jsp:include> <s:fielderror cssStyle="color:red"/> <s:form action="/admin/goods/add.action" method="POST" acceptcharset="UTF-8" > <s:token/> <table border="1"> <s:hidden name="goods.id" value="%{model.id}"/> <s:hidden name="goods.version" value="%{model.version}"/> <tr> <s:textfield label="商品名称" name="goods.name" value="%{model.name}" required="true"/> </tr> <tr> <s:textarea label="商品简介" name="goods.description" value="%{model.description}" required="true" cols="20" rows="3"/> </tr> <tr> <s:textfield label="原需积分" name="goods.originalPoint" value="%{model.originalPoint}" required="true"/> </tr> <tr> <s:textfield label="现需积分" name="goods.nowPoint" value="%{model.nowPoint}" required="true"/> </tr> <tr> <s:radio label="是否发布" name="goods.published" list="#{true:'发布',false:'不发布'}" value="%{model.published}" /> </tr> <tr> <td><input name="submit" type="submit" value="新增"/></td> <td> <input name="cancel" type="button" onclick="javascript:window.location.href='${ctx}/admin/goods/list.action'" value="取消"/> </td> </tr> </table> </s:form> <jsp:include page="../../common/inc/footer.jsp"/> ``` 右击“pointShop”选择【Run As】&gt;【Run On Server】启动Tomcat服务器,在商品列表页面单间【新增】按钮将显示图11-11界面。 ![](https://box.kancloud.cn/2016-05-13_57354723add3c.JPG) 图11-12 后台商品新增页面 **3、后台兑换码管理** 提供根据商品ID查询兑换码列表及新增兑换码操作,兑换码通过文本框输入多个,使用换行分割。 **3.1、Action实现** ``` package cn.javass.point.web.admin.action; //省略import public class GoodsCodeAction extends BaseAction { public String list() { getValueStack().set(MODEL, goodsService.get(goodsId)); getValueStack().set(PAGE, goodsCodeService.listAllByGoods(getPn(), goodsId)); return LIST; } public String doAdd() { getValueStack().set(MODEL, goodsService.get(goodsId)); return ADD; } public String add() { String[] codes = splitCodes(); goodsCodeService.save(goodsId, codes); return list(); } private String[] splitCodes() {//将根据换行分割code码 if(codes == null) { return new String[0]; } return codes.split("\r"); //简单起见不考虑“\n” } //字段驱动数据填充 private int id = -1; //前台提交的商品兑换码ID private int goodsId = -1; //前台提交的商品ID private String codes;//前台提交的兑换码,由换行分割 private GoodsCodeModel goodsCode; //前台提交的商品兑换码模型对象 //省略字段驱动数据的getter和setter //依赖注入Service private IGoodsCodeService goodsCodeService; private IGoodsService goodsService; //省略依赖注入的getter和setter } ``` **3.2、Spring配置文件定义(resources/cn/javass/web/pointShop-admin-servlet.xml):** ``` <bean name="/admin/goodsCodeAction" class="cn.javass.point.web.admin.action.GoodsCodeAction" scope="prototype"> <property name="goodsService" ref="goodsService"/> <property name="goodsCodeService" ref="goodsCodeService"/> </bean> ``` **3.3、JSP实现商品兑换码列表页面(WEB-INF/jsp/admin/goodsCode/list.jsp)** 商品兑换码列表页面时将展示相应商品的兑换码。 ``` <%@ page language="java" pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%> <%@ include file="../../common/inc/tld.jsp"%> <jsp:include page="../../common/inc/header.jsp"> <jsp:param name="title" value="商品管理-商品Code码列表"/> </jsp:include> <a href="${ctx}/admin/goodsCode/doAdd.action?goodsId=${model.id}">新增</a>| <a href="${ctx}/admin/goods/list.action">返回商品列表</a><br/> <table border="1"> <tr> <th>ID</th> <th>所属商品</th> <th>兑换码</th> <th>购买人</th> <th>兑换时间</th> <th>是否已经兑换</th> <th></th> </tr> <s:iterator value="page.items"> <tr> <td><s:property value="id"/></td> <td><s:property value="goods.name"/></td> <td><s:property value="code"/></td> <td><s:property value="username"/></td> <td><s:date name="exchangeTime" format="yyyy-MM-dd"/></td> <td><s:property value="exchanged"/></td> <td>删除</td> </tr> </s:iterator> </table> <jsp:include page="../../common/inc/footer.jsp"/> ``` 右击“pointShop”选择【Run As】&gt;【Run On Server】启动Web服务器,在浏览器中输入“http://localhost:8080/pointShop/admin/goods/list.action”,然后在指定商品后边点击【查看兑换码】将显示图11-15界面。 ![](https://box.kancloud.cn/2016-05-13_57354723c6788.JPG) 图11-15 商品兑换码列表 **3.4、JSP实现商品兑换码新增页面(WEB-INF/jsp/admin/goodsCode/add.jsp)** 用于新增指定商品的兑换码。 ``` <%@ page language="java" pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%> <%@ include file="../../common/inc/tld.jsp"%> <jsp:include page="../../common/inc/header.jsp"> <jsp:param name="title" value="用户管理-新增"/> </jsp:include> <s:fielderror cssStyle="color:red"/> <s:form action="/admin/goodsCode/add.action" method="POST" acceptcharset="UTF-8"> <s:token/> <s:hidden name="goodsId" value="%{model.id}" /> <table border="1"> <tr> <s:textfield label="所属商品" name="model.name" readonly="true"/> </tr> <tr> <s:textarea label="code码" name="codes" cols="20" rows="3"/> </tr> <tr> <td><input name="submit" type="submit" value="新增"/></td> <td><input name="cancel" type="button" onclick="javascript:window.location.href='${ctx}/admin/goodsCode/list.action?goodsId=<s:property value='%{model.id}'/>'" value="取消"/></td> </tr> </table> </s:form> <jsp:include page="../../common/inc/footer.jsp"/> ``` 右击“pointShop”选择【Run As】&gt;【Run On Server】启动Tomcat服务器,在商品兑换码列表中单击【新增】按钮将显示图11-16界面。 ![](https://box.kancloud.cn/2016-05-13_5735472401042.JPG)  图11-16 兑换码新增页面 **4、前台商品展示及购买模块:** 前台商品展示提供商品展示及购买页面,购买时应考虑是否有足够兑换码等,此处错误消息使用硬编码,应该考虑使用国际化支持,请参考学习国际化。 **4.1、Action实现** ``` package cn.javass.point.web.front.action; //省略import public class GoodsAction extends BaseAction { private static final String BUY_RESULT = "buyResult"; public String list() { getValueStack().set(PAGE, goodsService.listAllPublished(getPn())); return LIST; } public String buy() { String username = "test"; GoodsCodeModel goodsCode = null; try { goodsCode = goodsCodeService.buy(username, goodsId); } catch (NotCodeException e) { this.addActionError("没有足够的兑换码了"); return BUY_RESULT; } catch (Exception e) { e.printStackTrace(); this.addActionError("未知错误"); return BUY_RESULT; } this.addActionMessage("购买成功,您的兑换码为 :"+ goodsCode.getCode()); getValueStack().set(MODEL, goodsCode); return BUY_RESULT; } //字段驱动数据填充 private int goodsId; //省略字段驱动数据的getter和setter //依赖注入Service IGoodsService goodsService; IGoodsCodeService goodsCodeService; //省略依赖注入的getter和setter } ``` 4.2**、Spring配置文件定义(resources/cn/javass/web/pointShop-front-servlet.xml):** ``` <bean name="/front/goodsAction" class="cn.javass.point.web.front.action.GoodsAction" scope="prototype"> <property name="goodsService" ref="goodsService"/> <property name="goodsCodeService" ref="goodsCodeService"/> </bean> ``` **4.3、JSP实现前台商品展示及购买页面(WEB-INF/jsp/ goods/list.jsp)** ``` <%@ page language="java" pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%> <%@ include file="../../common/inc/tld.jsp"%> <jsp:include page="../../common/inc/header.jsp"> <jsp:param name="title" value="积分商城-商品列表"/> </jsp:include> <s:iterator value="page.items" status="status"> <s:property value="#status.index + 1"/>.<s:property value="name"/> <a href="${ctx}/goods/buy.action?goodsId=<s:property value='id'/>">【购买】</a><br/> 描述:<s:property value="description"/><br/> <s>需要积分<s:property value="originalPoint"/></s>&nbsp;&nbsp;现需积分:<b><s:property value="nowPoint"/></b><br/> </s:iterator> <jsp:include page="../../common/inc/footer.jsp"/> ``` 右击“pointShop”选择【Run As】&gt;【Run On Server】启动Web服务器,在浏览器中输入**http://localhost:8080/pointShop/goods/list.action**将显示图11-17界面。 ![](https://box.kancloud.cn/2016-05-13_573547241731f.JPG)  图11-17 前台商品展示即购买页面 在前台商品展示即购买页面中点击购买,如果库存中还有兑换码,将购买成功,否则购买失败。 **4.3、商品购买结果页面(WEB-INF/jsp/admin/goods/buyResult.jsp)** 购买成功将通过“&lt;s:actionmessage/&gt;”标签显示成功信息并将兑换码显示给用户,购买失败将通过“&lt;s:actionerror/&gt;”标签提示如积分不足或兑换码没有了等错误信息。 ``` <%@ page language="java" pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%> <%@ include file="../../common/inc/tld.jsp"%> <jsp:include page="../../common/inc/header.jsp"> <jsp:param name="title" value="积分商城-购买结果"/> </jsp:include> <s:actionerror/> <s:actionmessage/> <jsp:include page="../../common/inc/footer.jsp"/> ``` 在商品展示及购买列表购买成功或失败将显示图11-18或图11-19界面。 ![](https://box.kancloud.cn/2016-05-13_573547242d151.JPG)  图11-18 购买成功页面 ![](https://box.kancloud.cn/2016-05-13_5735472441a60.JPG)  图11-19 购买失败页面 到此SSH集成已经结束,集成SSH是非常简单的,但开发流程及开发思想是关键。 我们整个开发过程是首先抽象和提取通用的模块和代码,这样可以复用减少开发时间,其次是基于通用层开发不可预测部分(即可变部分),因为每个项目的功能是不一样的。在开发过程中还集中将重复内容提取到一处这样方便以后修改。 原创内容,转载请注明私塾在线【[http://sishuok.com/forum/blogPost/list/2516.html](http://sishuok.com/forum/blogPost/list/2516.html#7241)】