2017-8月
学到了一个java动态生成jquery easyui数据列表操作按钮的技术。故在此分享一下此项技术。
在后台管理,我们的常常有很多列表类,比如,商品列表,订单列表,分类列表,等等。做这这种操作按钮我们常常直接写死在页面,这样不利于修改,现在我们利用spring aop动态生成操作按钮。
先说一下思路:
1.生成这些按钮要在每条数据之后生成,所有我们给数据添加一个多余的属性,用于存放生成的按钮html代码。(注意下面的商品实体多了一个routerCall属性)
2.每个这样的操作按钮我们看成一个实体,如(商品列表操作按钮,订单列表操作按钮)对应的我们将这些按钮操作的方法,以及按钮显示名称,放入这个类中。
3.现在有很多列表需要这样的按钮,所以使用spring aop对这些列表请求所对应的控制层进行横切。得到列表的传入参数,以及从数据库获得的数据列表,然后遍历所有的数据列表,构造生成每条数据所对应的操作按钮html代码,并将代码添加到其routerCall属性。
4.每个按钮都有对应的请求,所以我们构造一个RouterCallController,处理每个按钮的请求,在这里用的是反射的原理对相应的方法进行调用。
下面附上相应的代码
1.数据(商品)实体
package cn.tsjcate.groupon.deal.entity;
import cn.tsjcate.framework.base.entity.BaseEntity;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
* Deal即一个单品
*/
public class Deal extends BaseEntity {
@Getter @Setter private Long areaId; // 地区ID
@Getter @Setter private String areaName; // 地区
@Getter @Setter private Long skuId; // skuID
@Getter @Setter private Integer dealClass; // 是否虚拟商品
@Getter @Setter private Long merchantId; // 商家ID
@Getter @Setter private Integer merchantSku; // 商家SKU 编号
@Getter @Setter private String dealTitle; // 商品标题
@Getter @Setter private Integer dealPrice; // 商品价格
@Getter @Setter private Integer merchantPrice; // 进货价
@Getter @Setter private Integer marketPrice; // 市场价
@Getter @Setter private Integer settlementPriceMax; // 最大可接受结算价格
@Getter @Setter private Integer settlementPrice; // 结算价
@Getter @Setter private Integer discount; // 折扣
@Getter @Setter private Integer bonusPoints; // 积分
@Getter @Setter private Integer dealType; // 商品类型
@Getter @Setter private Long imageId; // 对应商品图片
@Getter @Setter private String imageGenPath; // 对应商品图片路径
@Getter @Setter private Integer dealLevel; // 商品优先级
@Getter @Setter private Integer maxPurchaseCount; // 最大购买数量
@Getter @Setter private Integer publishStatus; // 发布状态
@Getter @Setter private Integer inventoryAmount; // 商品库存数量
@Getter @Setter private Integer vendibilityAmount; // 商品可售数量
@Getter @Setter private Integer oosStatus; // 是否售罄标识
@Getter @Setter private Date startTime; // 商品销售开始时间
@Getter @Setter private Date endTime; // 商品销售结束时间
@Getter @Setter private Date publishTime; // 商品上架时间
@Setter @Getter private String merchantCode; // 商品唯一编码
@Getter @Setter private Date createTime; // 商品创建时间
@Getter @Setter private Date updateTime; // 商品更新时间
@Getter @Setter private DealDetail dealDetail; // 商品对应描述
@Getter @Setter private Boolean downShelf; // 是否下架的标识
@Getter @Setter private Integer categoryId; // 商品类别ID
@Getter @Setter private String routerCall;
public boolean isStart() {//是否开始团购
return new Date().after(this.getStartTime());
}
public boolean isEnd() {//是否结束
return new Date().after(this.getEndTime());
}
2.按钮实体
package cn.tsjcate.admin.product.router;
import cn.tsjcate.admin.base.controller.AjaxResult;
import cn.tsjcate.admin.base.router.BaseRouter;
import cn.tsjcate.framework.base.entity.BaseEntity;
import cn.tsjcate.framework.util.DateUtil;
import cn.tsjcate.groupon.deal.constant.DealConstant;
import cn.tsjcate.groupon.deal.entity.Deal;
import cn.tsjcate.groupon.deal.service.DealService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Map;
/**
* 商品Router
*/
@Component
public class ProductRouter extends BaseRouter {
@Autowired private DealService dealService; // 商品服务
public ProductRouter() {
this.clazz = Deal.class;
super.addMethodDisplayName("oosStatusInvalid", "可售");
super.addMethodDisplayName("oosStatusValid", "已卖光");
super.addMethodDisplayName("deleteProduct", "删除");
super.addMethodDisplayName("publishProduct", "发布");
super.addMethodDisplayName("auditProduct", "审核");
super.addMethodDisplayName("cancelPublish", "取消发布");
}
@SuppressWarnings("unchecked")
@Override
public Deal loadEntity(Long id) {
Deal deal = new Deal();
deal.setId(id);
return deal;
}
/**
*
* @param entity 商品实体
* @param method 操作方法
* @return
*/
public boolean isButtonDisabled(BaseEntity entity, String method) {
Deal deal = (Deal) entity;
boolean disableFlag = false;
switch (method) {
case "oosStatusInvalid"://是否售完
if (deal.getOosStatus() == DealConstant.DEAL_OOS_STATUS_NO) {//售完
disableFlag = true;
}
break;
case "oosStatusValid"://是否卖完
if (deal.getOosStatus() == DealConstant.DEAL_OOS_STATUS_YES) {//已卖光
disableFlag = true;
}
break;
case "deleteProduct":
if (deal.getPublishStatus() == DealConstant.DEAL_PUBLISH_STATUS_DELETED) {//已删除
disableFlag = true;
}
break;
case "publishProduct":
if (deal.getPublishStatus() == DealConstant.DEAL_PUBLISH_STATUS_PUBLISH) {
disableFlag = true;
}
break;
case "auditProduct":
if (deal.getPublishStatus() == DealConstant.DEAL_PUBLISH_STATUS_AUDITED || deal.getPublishStatus() == DealConstant.DEAL_PUBLISH_STATUS_PUBLISH) {
disableFlag = true;
}
break;
case "cancelPublish":
if (deal.getPublishStatus() == DealConstant.DEAL_PUBLISH_STATUS_NEW
|| deal.getPublishStatus() == DealConstant.DEAL_PUBLISH_STATUS_AUDITED) {
disableFlag = true;
}
break;
default:
break;
}
return disableFlag;
}
/**
* 删除商品
* @param user
* @param params
* @return
*/
public AjaxResult deleteProduct(BaseEntity user, Deal deal, Map params) {
deal.setPublishStatus(DealConstant.DEAL_PUBLISH_STATUS_DELETED);
AjaxResult modifyResult = new AjaxResult();
int modifyFlag = dealService.modifyStatusById(deal);//删除商品
if (modifyFlag > -1) {
modifyResult.setStatusCode(AjaxResult.AJAX_STATUS_CODE_SUCCESS);
} else {
modifyResult.setStatusCode(AjaxResult.AJAX_STATUS_CODE_ERROR);
}
return modifyResult;
}
/**
* 发布商品
* @param user
* @param deal
* @param params
* @return
*/
public AjaxResult publishProduct(BaseEntity user, Deal deal, Map params) {
AjaxResult modifyResult = null;
try {
deal.setPublishStatus(DealConstant.DEAL_PUBLISH_STATUS_PUBLISH);
modifyResult = new AjaxResult();
Deal existInfo = dealService.getById(deal.getId());
if (null != existInfo) {
if (null != existInfo.getEndTime()) {
if (existInfo.getEndTime().compareTo(new Date()) < 0) {
deal.setEndTime(DateUtil.getSevenDaysAfterOnSale());
}
} else {
deal.setEndTime(DateUtil.getSevenDaysAfterOnSale());
}
}
int modifyFlag = dealService.modifyStatusById(deal);
if (modifyFlag > -1) {
modifyResult.setStatusCode(AjaxResult.AJAX_STATUS_CODE_SUCCESS);
} else {
modifyResult.setStatusCode(AjaxResult.AJAX_STATUS_CODE_ERROR);
}
} catch (Exception e) {
e.printStackTrace();
}
return modifyResult;
}
/**
* 取消发布商品
* @param user
* @param deal
* @param params
* @return
*/
public AjaxResult cancelPublish(BaseEntity user, Deal deal, Map params) {
// 取消发布,默认为已审核
deal.setPublishStatus(DealConstant.DEAL_PUBLISH_STATUS_AUDITED);
AjaxResult modifyResult = new AjaxResult();
int modifyFlag = dealService.modifyStatusById(deal);
if (modifyFlag > -1) {
modifyResult.setStatusCode(AjaxResult.AJAX_STATUS_CODE_SUCCESS);
} else {
modifyResult.setStatusCode(AjaxResult.AJAX_STATUS_CODE_ERROR);
}
return modifyResult;
}
/**
* 审核商品
* @param user
* @param deal
* @param params
* @return
*/
public AjaxResult auditProduct(BaseEntity user, Deal deal, Map params) {
deal.setPublishStatus(DealConstant.DEAL_PUBLISH_STATUS_AUDITED);
AjaxResult modifyResult = new AjaxResult();
int modifyFlag = dealService.modifyStatusById(deal);
if (modifyFlag > -1) {
modifyResult.setStatusCode(AjaxResult.AJAX_STATUS_CODE_SUCCESS);
} else {
modifyResult.setStatusCode(AjaxResult.AJAX_STATUS_CODE_ERROR);
}
return modifyResult;
}
/**
* 设置已卖光
* @param user
* @param deal
* @param params
* @return
*/
public AjaxResult oosStatusValid(BaseEntity user, Deal deal, Map params) {
deal.setOosStatus(DealConstant.DEAL_OOS_STATUS_YES);
AjaxResult modifyResult = new AjaxResult();
int modifyFlag = dealService.modifyOosStatusById(deal);
if (modifyFlag > -1) {
modifyResult.setStatusCode(AjaxResult.AJAX_STATUS_CODE_SUCCESS);
} else {
modifyResult.setStatusCode(AjaxResult.AJAX_STATUS_CODE_ERROR);
}
return modifyResult;
}
/**
* 可售
* @param user
* @param productInfo
* @param params
* @return
*/
public AjaxResult oosStatusInvalid(BaseEntity user, Deal productInfo, Map params) {
productInfo.setOosStatus(DealConstant.DEAL_OOS_STATUS_NO);
AjaxResult modifyResult = new AjaxResult();
int modifyFlag = dealService.modifyOosStatusById(productInfo);
if (modifyFlag > -1) {
modifyResult.setStatusCode(AjaxResult.AJAX_STATUS_CODE_SUCCESS);
} else {
modifyResult.setStatusCode(AjaxResult.AJAX_STATUS_CODE_ERROR);
}
return modifyResult;
}
}
package cn.tsjcate.admin.base.router;
import cn.tsjcate.admin.base.controller.AjaxResult;
import cn.tsjcate.framework.base.entity.BaseEntity;
import cn.tsjcate.framework.base.exception.BusinessException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
*
*/
public abstract class BaseRouter {
protected Class clazz;
private Map methodDisplayNameMap = new HashMap<>();
public boolean isAuthorizedToCall(Long userId, String method) {
return true;
}
public boolean isButtonDisabled(BaseEntity entity, String method) {
return false;
}
public String getMethodDisplayName(String method) {
return methodDisplayNameMap.get(method);
}
//添加操作按钮名称
protected void addMethodDisplayName(String method, String methodDisplayName) {
methodDisplayNameMap.put(method, methodDisplayName);
}
public abstract T loadEntity(Long entityId);
/**
* 调用具体Router的业务方法(通过反射)
* @param user 用户
* @param entity 实体
* @param method 方法(字符串)
* @param params Map参数
* @param 实体泛型
* @return
*/
public AjaxResult callMethod(BaseEntity user, T entity, String method, Map params) {
Method defineMethod; //根据方法名获得到具体Router的方法
try {
//反射得到方法对象,注:那个类调用的这个对象,getClass()就获得的是哪个对象
defineMethod = getClass().getMethod(method, new Class[]{BaseEntity.class, clazz, Map.class});
if (null != defineMethod) {
//方法调用
return (AjaxResult) defineMethod.invoke(this,user, entity, params);
}
throw new RuntimeException("No method " + method + " defined for router " + getClass().getName());
} catch (NoSuchMethodException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
} catch (InvocationTargetException e) {
e.printStackTrace();
if (e.getTargetException() instanceof BusinessException) {
throw (BusinessException)e.getTargetException();
}
throw new RuntimeException(e.getMessage());
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
}
3.前端列表对应的代码product_list.ftl及search.ftl
---------------------------------------search.ftl-------------------------------------------------------------
<#macro search2 gridId>
#macro>
----------------------------------------product_list.ftl------------------------------------------------------
<#import "/macro/search.ftl" as search_macro/>
<@search_macro.search2 gridId="productListGrid">
<@product_macro.generateProductTypeSelect name="search_dealType" defaultValue=search_dealType hasHeader=true/>
<@product_macro.generateProductStatusSelect name="search_publishStatus" defaultValue=search_publishStatus hasHeader=true/>
@search_macro.search2>
地区
Sku ID
商家
商家Sku
产品编码
名称
类型
商品类型
销售价格
发布状态
发布时间
销售库存
总库存
销售开始时间
销售结束时间
操作
4.列表请求controller:
/**
* 加载商品列表页数据
* 利用aop实现router的构造
* @param search
* @return
*/
@RequestMapping(value = "/listProduct", method = RequestMethod.POST)
public @ResponseBody PagingResult listProduct(Search search) {
PagingResult dealListPage = dealService.getDealList(search);
if (dealListPage != null && dealListPage.getRows().size() > 0) {
/*给每个商品添加图片url*/
for (Deal deal : dealListPage.getRows()) {
String imagePath = getObjectImageUrl(deal.getImageId(), DealConstant.PICTURE_MODULE_DEAL, 2);
deal.setImageGenPath(imagePath);
}
}
return dealListPage;
}
/**给查询出来的每条数据(商品)在这里是entity加上router属性
* 生成Router按钮(HTML)//
* @param pagingResult
* @param search
* @param
*/
public void generateRouterCallButtons(PagingResult pagingResult, Search search) {
/*如果没有传入参数则不执行*/
if (!search.containsRouterCall()) {
return;
}
//依照routername获得router实例
BaseRouter router = getRouter(search.getRouterName());
if (null == router) {
return;
}
String[] methods = search.getMethods();//获得router所有的方法
for (T entity : pagingResult.getRows()) {//遍历每条数据(如商品),写入routerCall属性
StringBuilder routerCall = new StringBuilder();
for (String method : methods) {//循环生成每个method的html
String button = generateRouterCallButton(router, search.getRouterName(), entity, method,
ArrayUtils.contains(search.getConfirmMethods(), method), search.getGridId());
if (null != button) {
routerCall.append(button);
}
}
entity.setRouterCall(routerCall.toString());//每个实体添加router属性
}
}
/**
*生成某个列表一条数据的操作方法
* @param router 实例
* @param routerName 要生成的router名称
* @param entity 当前要操作的实体(如商品,订单。。。)
* @param method 每个router方法/按钮(如已售完,发布。。)
* @param confirm
* @param gridId router的id
* @param
* @return
*/
private String generateRouterCallButton(BaseRouter router, String routerName, T entity,
String method, boolean confirm, String gridId) {
String methodName = router.getMethodDisplayName(method);//method显示名称
StringBuilder html = new StringBuilder();
html.append("");
return html.toString();
/**/
}
5.控制层切面
package cn.tsjcate.admin.base.aspect;
import cn.tsjcate.admin.base.controller.BaseAdminController;
import cn.tsjcate.framework.common.page.PagingResult;
import cn.tsjcate.framework.common.search.Search;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* Controller中方法拦截
* 找到返回结果和参数相匹配的那个方法,在方法执行完,构造所对应的router
*/
@Component
@Aspect
public class ControllerAspect {
@Around(value = "execution(public * cn.tsjcate..controller.*Controller.*(..))")
public Object aroundControllerMethod(final ProceedingJoinPoint pjp) {
try {
Object returnVal = pjp.proceed();//返回方法(连接点)执行完成的结果(PagingResult)
// MethodSignature methodSignature = (MethodSignature)pjp.getSignature();
// methodSignature.getMethod();
for (Object obj : pjp.getArgs()) {//获得所有的参数并且遍历
if (obj instanceof Search && returnVal instanceof PagingResult &&
((Search)obj).containsRouterCall()) {
((BaseAdminController)pjp.getTarget()).generateRouterCallButtons(
(PagingResult)returnVal, (Search)obj);
}
}
return returnVal;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}
6.生成的按钮js请求代码
/**
*
* @param ctx 项目路径
* @param routerName
* @param method 方法名称
* @param methodName method显示名称
* @param objectId 实体类id(如商品)
* @param confirmOperation 是否显示此操作
* @param gridId router的id
*/
function ajaxRouterCall(ctx, routerName, method, methodName, objectId, confirmOperation, gridId) {
//TODO: confirm 名称转换成中文,可以通过method对应名称
//TODO: reload data/button block
if (confirmOperation) {
$.messager.confirm('确认', '是否确认要执行'+methodName+'操作?', function(result){
if (result){
$.ajax({
type: "post",
url: ctx + "/router/call/operation?"+'routerName='+routerName+'&method='+method+'&entityId='+objectId,
//data: postData,
beforeSend: function(XMLHttpRequest){
//ShowLoading(); or disableLink
},
success: function(data, textStatus){
processAjaxRouterCallResult(objectId,data,gridId);//在这里提示操作成功
},
complete: function(XMLHttpRequest, textStatus){
//HideLoading(); or enableLink
},
error: function(){
//请求出错处理
}
});
}
});
}
}
7.按钮请求controller
@Controller
@RequestMapping(value = "/router/call")
public class RouterCallController extends BaseAdminController {
/**
* Router统一入口
* @param routerName 名
* @param method 方法
* @param entityId 实体ID
* @param 实体泛型
* @return
*/
@RequestMapping(value = "/operation", method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public AjaxResult routerCall(String routerName, String method, Long entityId) {
AjaxResult ajaxResult = new AjaxResult();
ajaxResult.setStatusCode(AjaxResult.AJAX_STATUS_CODE_WARN);
//参数判断
if (null == entityId || StringUtil.isEmpty(routerName) || StringUtil.isEmpty(method)) {
return ajaxResult;
}
//获得Router实例
BaseRouter router = getRouter(routerName);
if (null == router) {
return ajaxResult;
}
//加载实体
T entity = router.loadEntity(entityId);
if (null == entity) {
return ajaxResult;
}
//鉴权
if (!router.isAuthorizedToCall(getCurrentUser().getId(), method)) {
// return ajaxResult;
}
//call router's method
try {
//两个对象有共同的设置
//Router调用实例的方法(method名的方法)
AjaxResult routerResult = router.callMethod(getCurrentUser(), entity, method, null);
ajaxResult.setStatusCode(routerResult.getStatusCode());
} catch (BusinessException e) {
// e.printStackTrace();
ajaxResult.setStatusCode(AjaxResult.AJAX_STATUS_CODE_ERROR);
ajaxResult.setMessage(e.getMessage());
} catch (Exception e) {
// e.printStackTrace();
ajaxResult.setStatusCode(AjaxResult.AJAX_STATUS_CODE_ERROR);
ajaxResult.setMessage(e.getMessage());
}
return ajaxResult;
}
}