# 【第十一章】 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所示:

圖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屬性中存放著分頁列表數據)來顯示商品列表,在最后應該有分頁標簽(請參考源代碼,示例無),如類似于“<my:page url="${ctx}/admin/goods/list.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>
<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】>【Run On Server】啟動Tomcat服務器,在瀏覽器中輸入“http://localhost:8080/pointShop/admin/goods/list.action”將顯示圖11-11界面。

圖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】>【Run On Server】啟動Tomcat服務器,在商品列表頁面單間【新增】按鈕將顯示圖11-11界面。

圖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】>【Run On Server】啟動Web服務器,在瀏覽器中輸入“http://localhost:8080/pointShop/admin/goods/list.action”,然后在指定商品后邊點擊【查看兌換碼】將顯示圖11-15界面。

圖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】>【Run On Server】啟動Tomcat服務器,在商品兌換碼列表中單擊【新增】按鈕將顯示圖11-16界面。
?
圖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> 現需積分:<b><s:property value="nowPoint"/></b><br/>
</s:iterator>
<jsp:include page="../../common/inc/footer.jsp"/>
```
右擊“pointShop”選擇【Run As】>【Run On Server】啟動Web服務器,在瀏覽器中輸入**http://localhost:8080/pointShop/goods/list.action**將顯示圖11-17界面。
?
圖11-17 前臺商品展示即購買頁面
在前臺商品展示即購買頁面中點擊購買,如果庫存中還有兌換碼,將購買成功,否則購買失敗。
**4.3、商品購買結果頁面(WEB-INF/jsp/admin/goods/buyResult.jsp)**
購買成功將通過“<s:actionmessage/>”標簽顯示成功信息并將兌換碼顯示給用戶,購買失敗將通過“<s:actionerror/>”標簽提示如積分不足或兌換碼沒有了等錯誤信息。
```
<%@ 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界面。
?
圖11-18 購買成功頁面
?
圖11-19 購買失敗頁面
到此SSH集成已經結束,集成SSH是非常簡單的,但開發流程及開發思想是關鍵。
我們整個開發過程是首先抽象和提取通用的模塊和代碼,這樣可以復用減少開發時間,其次是基于通用層開發不可預測部分(即可變部分),因為每個項目的功能是不一樣的。在開發過程中還集中將重復內容提取到一處這樣方便以后修改。
原創內容,轉載請注明私塾在線【[http://sishuok.com/forum/blogPost/list/2516.html](http://sishuok.com/forum/blogPost/list/2516.html#7241)】
- 跟我學 Spring3
- 【第二章】 IoC 之 2.1 IoC基礎 ——跟我學Spring3
- 【第二章】 IoC 之 2.2 IoC 容器基本原理 ——跟我學Spring3
- 【第二章】 IoC 之 2.3 IoC的配置使用——跟我學Spring3
- 【第三章】 DI 之 3.1 DI的配置使用 ——跟我學spring3
- 【第三章】 DI 之 3.2 循環依賴 ——跟我學spring3
- 【第三章】 DI 之 3.3 更多DI的知識 ——跟我學spring3
- 【第三章】 DI 之 3.4 Bean的作用域 ——跟我學spring3
- 【第四章】 資源 之 4.1 基礎知識 ——跟我學spring3
- 【第四章】 資源 之 4.2 內置Resource實現 ——跟我學spring3
- 【第四章】 資源 之 4.3 訪問Resource ——跟我學spring3
- 【第四章】 資源 之 4.4 Resource通配符路徑 ——跟我學spring3
- 【第五章】Spring表達式語言 之 5.1 概述 5.2 SpEL基礎 ——跟我學spring3
- 【第五章】Spring表達式語言 之 5.3 SpEL語法 ——跟我學spring3
- 【第五章】Spring表達式語言 之 5.4在Bean定義中使用EL—跟我學spring3
- 【第六章】 AOP 之 6.1 AOP基礎 ——跟我學spring3
- 【第六章】 AOP 之 6.2 AOP的HelloWorld ——跟我學spring3
- 【第六章】 AOP 之 6.3 基于Schema的AOP ——跟我學spring3
- 【第六章】 AOP 之 6.4 基于@AspectJ的AOP ——跟我學spring3
- 【第六章】 AOP 之 6.5 AspectJ切入點語法詳解 ——跟我學spring3
- 【第六章】 AOP 之 6.6 通知參數 ——跟我學spring3
- 【第六章】 AOP 之 6.7 通知順序 ——跟我學spring3
- 【第六章】 AOP 之 6.8 切面實例化模型 ——跟我學spring3
- 【第六章】 AOP 之 6.9 代理機制 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.1 概述 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.2 JDBC模板類 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.3 關系數據庫操作對象化 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.4 Spring提供的其它幫助 ——跟我學spring3【私塾在線原創】
- 【第七章】 對JDBC的支持 之 7.5 集成Spring JDBC及最佳實踐 ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.1 概述 ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.2 集成Hibernate3 ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.3 集成iBATIS ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.4 集成JPA ——跟我學spring3
- 【第九章】 Spring的事務 之 9.1 數據庫事務概述 ——跟我學spring3
- 【第九章】 Spring的事務 之 9.2 事務管理器 ——跟我學spring3
- 【第九章】 Spring的事務 之 9.3 編程式事務 ——跟我學spring3
- 【第九章】 Spring的事務 之 9.4 聲明式事務 ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.1 概述 ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.2 集成Struts1.x ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.3 集成Struts2.x ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.4 集成JSF ——跟我學spring3
- 【第十一章】 SSH集成開發積分商城 之 11.1 概述 ——跟我學spring3
- 【第十一章】 SSH集成開發積分商城 之 11.2 實現通用層 ——跟我學spring3
- 【第十一章】 SSH集成開發積分商城 之 11.3 實現積分商城層 ——跟我學spring3
- 【第十二章】零配置 之 12.1 概述 ——跟我學spring3
- 【第十二章】零配置 之 12.2 注解實現Bean依賴注入 ——跟我學spring3
- 【第十二章】零配置 之 12.3 注解實現Bean定義 ——跟我學spring3
- 【第十二章】零配置 之 12.4 基于Java類定義Bean配置元數據 ——跟我學spring3
- 【第十二章】零配置 之 12.5 綜合示例-積分商城 ——跟我學spring3
- 【第十三章】 測試 之 13.1 概述 13.2 單元測試 ——跟我學spring3
- 【第十三章】 測試 之 13.3 集成測試 ——跟我學spring3
- 跟我學 Spring MVC
- SpringMVC + spring3.1.1 + hibernate4.1.0 集成及常見問題總結
- Spring Web MVC中的頁面緩存支持 ——跟我學SpringMVC系列
- Spring3 Web MVC下的數據類型轉換(第一篇)——《跟我學Spring3 Web MVC》搶先看
- Spring3 Web MVC下的數據格式化(第二篇)——《跟我學Spring3 Web MVC》搶先看
- 第一章 Web MVC簡介 —— 跟開濤學SpringMVC
- 第二章 Spring MVC入門 —— 跟開濤學SpringMVC
- 第三章 DispatcherServlet詳解 ——跟開濤學SpringMVC
- 第四章 Controller接口控制器詳解(1)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(2)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(3)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解 (4)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(5)——跟著開濤學SpringMVC
- 跟著開濤學SpringMVC 第一章源代碼下載
- 第二章 Spring MVC入門 源代碼下載
- 第四章 Controller接口控制器詳解 源代碼下載
- 第四章 Controller接口控制器詳解(6)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(7 完)——跟著開濤學SpringMVC
- 第五章 處理器攔截器詳解——跟著開濤學SpringMVC
- 源代碼下載 第五章 處理器攔截器詳解——跟著開濤學SpringMVC
- 注解式控制器運行流程及處理器定義 第六章 注解式控制器詳解——跟著開濤學SpringMVC
- 源代碼下載 第六章 注解式控制器詳解
- SpringMVC3強大的請求映射規則詳解 第六章 注解式控制器詳解——跟著開濤學SpringMVC
- Spring MVC 3.1新特性 生產者、消費者請求限定 —— 第六章 注解式控制器詳解——跟著開濤學SpringMVC
- SpringMVC強大的數據綁定(1)——第六章 注解式控制器詳解——跟著開濤學SpringMVC
- SpringMVC強大的數據綁定(2)——第六章 注解式控制器詳解——跟著開濤學SpringMVC
- SpringMVC數據類型轉換——第七章 注解式控制器的數據驗證、類型轉換及格式化——跟著開濤學SpringMVC
- SpringMVC數據格式化——第七章 注解式控制器的數據驗證、類型轉換及格式化——跟著開濤學SpringMVC
- SpringMVC數據驗證——第七章 注解式控制器的數據驗證、類型轉換及格式化——跟著開濤學SpringMVC