GitHub mall项目学习(二) mall-admin

GitHub mall项目学习(二) mall-admin

本文主要介绍mall-admin后台系统

mall
├── mall-common -- 工具类及通用代码
├── mall-mbg -- MyBatisGenerator生成的数据库操作代码
├── mall-security -- SpringSecurity封装公用模块
├── mall-admin -- 后台商城管理系统接口
├── mall-search -- 基于Elasticsearch的商品搜索系统
├── mall-portal -- 前台商城系统接口
└── mall-demo -- 框架搭建时的测试代码

简介

本文主要介绍mall-admin,后台管理系统模块,该模块涉及最多的为业务逻辑处理,即常规的CRUD开发,但其中有很多值得学习的处理方案,本文将列举并详细介绍这些方案

GitHub mall项目学习(二) mall-admin_第1张图片

后台项目包含的controller,通过前缀区分,Sms为营销管理,Oms为订单管理类,Pms为商品管理类,Ums为后台用户管理类,还有一些其他类

其他如pojo,返回结果封装等均为常规套路,此处不做介绍,请阅读mall-common中api包中的内容

技术点

PageHelper页面分页助手

所以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语句

  1. 先解析各位置参数
  2. 初始化 pageHelper 实例, 即 dialect;
  3. 调用方法判断是否需要进行分页,如果不需要,直接返回结果;
  4. 判断是否要进行count, 如果需要则实现一次count, ;
  5. 查询分页结果;
  6. 封装带分页的结果返回;

通过反射实现相同类型插入数据库操作方法的封装

案例:在添加商品时会涉及很多的模块,如该商品有阶梯价格,满减价格,需要关联主题,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());
    }
}

AOP实现接口信息统计

@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;
}

Redis缓存处理策略

  • 定义RedisService类来封装了RedisTemplate的方法
  • 定义UmsAdminCacheService类来封装用户缓存方法,为什么要有这一层主要是为了封装好Redis中Key的自动设置
  • 定义RedisAOP切面,将每个缓存方法都添加try-catch块,这样做避免了重复书写,并且在Redis宕机时,不管调用哪个缓存方法,都会接收到通知信息(此处内容被定义到了mall-security模块中,因为该缓存主要时用户权限控制时使用)
  • 自定义CacheException注解,在AOP增强方法时,先扫面该方法的注解,如果有该注解,就会直接抛出错误,而不是只进行记录日志

其他

该模块介绍中很多技术其实使用了mall-common或者mall-security模块中的内容,后续会出mall-security

免了重复书写,并且在Redis宕机时,不管调用哪个缓存方法,都会接收到通知信息(此处内容被定义到了mall-security模块中,因为该缓存主要时用户权限控制时使用)

  • 自定义CacheException注解,在AOP增强方法时,先扫面该方法的注解,如果有该注解,就会直接抛出错误,而不是只进行记录日志

其他

该模块介绍中很多技术其实使用了mall-common或者mall-security模块中的内容,后续会出mall-security

本专题并不详细记录curd细节(除非很必要),主要介绍一些经典的技术点!

你可能感兴趣的:(java,后端,spring,boot)