本文主要介绍mall-admin后台系统
mall
├── mall-common -- 工具类及通用代码
├── mall-mbg -- MyBatisGenerator生成的数据库操作代码
├── mall-security -- SpringSecurity封装公用模块
├── mall-admin -- 后台商城管理系统接口
├── mall-search -- 基于Elasticsearch的商品搜索系统
├── mall-portal -- 前台商城系统接口
└── mall-demo -- 框架搭建时的测试代码
本文主要介绍mall-admin,后台管理系统模块,该模块涉及最多的为业务逻辑处理,即常规的CRUD开发,但其中有很多值得学习的处理方案,本文将列举并详细介绍这些方案
后台项目包含的controller,通过前缀区分,Sms为营销管理,Oms为订单管理类,Pms为商品管理类,Ums为后台用户管理类,还有一些其他类
其他如pojo,返回结果封装等均为常规套路,此处不做介绍,请阅读mall-common中api包中的内容
所以controller需要的分页返回实现均使用PageHelper插件实现.
插件依赖
com.github.pagehelper
pagehelper
最新版本
application.yaml配置
#分页pageHelper
pagehelper:
# sql方言 默认也会自动检测
helper-dialect: mysql
# 配置分页合理化,查询页<1,会返回第一页,查询页>最大页,会返回最后一页 默认false
reasonable: true
support-methods-arguments: true
使用
// 主要是service中使用
public Object getUsers(int pageNum, int pageSize) {
// 这句话说明返回页和每一页数量
PageHelper.startPage(pageNum, pageSize);
// 不带分页的查询
List<UserEntity> list = userMapper.selectAllWithPage(null);
// 将list转换成页面数据,该数据就包含了本页信息以及一些详细信息
PageInfo<Apps> appsPageInfo = new PageInfo<>(list);
return list;
}
原理(涉及Mybatis的一些细节,之后会发相关文章!~~~)
是通过实现Mybatis的Interceptor接口
pageHelper会使用ThreadLocal获取到同一线程中的变量信息
利用这一点通过拦截器获取到同一线程中的预编译好的SQL语句之后将SQL语句包装成具有分页功能的SQL语句,并将其再次赋值给下一步操作,所以实际执行的SQL语句就是有了分页功能的SQL语句
案例:在添加商品时会涉及很多的模块,如该商品有阶梯价格,满减价格,需要关联主题,SKU库存插入,关联优选,而这些操作都是相同的操作,及添加一条记录(商品id:xxx,关联信息数组),很多数据首先要关联商品id,再插入数据库,都是重复操作,所以封装成一个基于反射的方法,获取每种类型中的setProductId来为这条记录设置商品id
/**
* 建立和插入关系表操作
* @param dao 可以操作的dao
* @param dataList 要插入的数据
* @param productId 建立关系的id
*/
private void relateAndInsertList(Object dao, List dataList, Long productId) {
try {
// 如果该数组为空,说明添加时并没有插入内容,直接返回
if (CollectionUtils.isEmpty(dataList)) {
return;
}
// 变量list数据
for (Object item : dataList) {
// 通过反射获得该Dao的setId方法
Method setId = item.getClass().getMethod("setId", Long.class);
setId.invoke(item, (Long) null);
// 通过反射获得该Dao的setProductId方法,将要添加的商品id绑定给这条记录
Method setProductId = item.getClass().getMethod("setProductId", Long.class);
// 绑定id
setProductId.invoke(item, productId);
}
// 反射获得dao的插入数据方法,将这些记录插入数据库
Method insertList = dao.getClass().getMethod("insertList", List.class);
insertList.invoke(dao, dataList);
} catch (Exception e) {
LOGGER.warn("创建产品出错:{}", e.getMessage());
throw new RuntimeException(e.getMessage());
}
}
添加商品时使用,很多表的插入都是直接使用该方法
@Override
public int create(PmsProductParam productParam) {
int count;
//创建商品
PmsProduct product = productParam;
product.setId(null);
productMapper.insertSelective(product);
//根据促销类型设置价格:会员价格、阶梯价格、满减价格
Long productId = product.getId();
//会员价格
relateAndInsertList(memberPriceDao, productParam.getMemberPriceList(), productId);
//阶梯价格
relateAndInsertList(productLadderDao, productParam.getProductLadderList(), productId);
//满减价格
relateAndInsertList(productFullReductionDao, productParam.getProductFullReductionList(), productId);
//处理sku的编码
handleSkuStockCode(productParam.getSkuStockList(),productId);
//添加sku库存信息
relateAndInsertList(skuStockDao, productParam.getSkuStockList(), productId);
//添加商品参数,添加自定义商品规格
relateAndInsertList(productAttributeValueDao, productParam.getProductAttributeValueList(), productId);
//关联专题
relateAndInsertList(subjectProductRelationDao, productParam.getSubjectProductRelationList(), productId);
//关联优选
relateAndInsertList(prefrenceAreaProductRelationDao, productParam.getPrefrenceAreaProductRelationList(), productId);
count = 1;
return count;
}
该项目使用@ControllerAdvice + @ExceptionHandler(value = xxx.class)来实现全局异常控制,当controller抛出异常后,会被该类捕获到,并做对应的处理
// 注解表示该类为全局异常处理类
@ControllerAdvice
public class GlobalExceptionHandler {
// 返回json
@ResponseBody
// 捕获对应的异常
@ExceptionHandler(value = ApiException.class)
public CommonResult handle(ApiException e) {
if (e.getErrorCode() != null) {
return CommonResult.failed(e.getErrorCode());
}
return CommonResult.failed(e.getMessage());
}
}
@Aspect 定义一个切面类
@Pointcut(“execution(public * com.macro.mall.controller..(…))||execution(public * com.macro.mall..controller..*(…))”) 定义一个切入点,这里表示controller层的方法都被增强
@Around(“pointcutxxx”) 定义一个环绕方法
用于封装需要记录的日志信息,包括操作的描述、时间、消耗时间、url、请求参数和返回结果等信息。
@Around("webLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
//获取当前请求对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//记录请求信息(通过Logstash传入Elasticsearch)
WebLog webLog = new WebLog();
Object result = joinPoint.proceed();
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method.isAnnotationPresent(ApiOperation.class)) {
ApiOperation log = method.getAnnotation(ApiOperation.class);
webLog.setDescription(log.value());
}
long endTime = System.currentTimeMillis();
String urlStr = request.getRequestURL().toString();
webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
webLog.setUsername(request.getRemoteUser());
webLog.setIp(request.getRemoteAddr());
webLog.setMethod(request.getMethod());
webLog.setParameter(getParameter(method, joinPoint.getArgs()));
webLog.setResult(result);
webLog.setSpendTime((int) (endTime - startTime));
webLog.setStartTime(startTime);
webLog.setUri(request.getRequestURI());
webLog.setUrl(request.getRequestURL().toString());
Map<String,Object> logMap = new HashMap<>();
logMap.put("url",webLog.getUrl());
logMap.put("method",webLog.getMethod());
logMap.put("parameter",webLog.getParameter());
logMap.put("spendTime",webLog.getSpendTime());
logMap.put("description",webLog.getDescription());
// LOGGER.info("{}", JSONUtil.parse(webLog));
LOGGER.info(Markers.appendEntries(logMap), JSONUtil.parse(webLog).toString());
return result;
}
该模块介绍中很多技术其实使用了mall-common或者mall-security模块中的内容,后续会出mall-security
免了重复书写,并且在Redis宕机时,不管调用哪个缓存方法,都会接收到通知信息(此处内容被定义到了mall-security模块中,因为该缓存主要时用户权限控制时使用)
该模块介绍中很多技术其实使用了mall-common或者mall-security模块中的内容,后续会出mall-security
本专题并不详细记录curd细节(除非很必要),主要介绍一些经典的技术点!