Bladeren bron

Merge branch 'dev' into 'mp-server-test'

Dev

See merge request quanshu/mp-server!778
jiang hao 2 jaren geleden
bovenliggende
commit
384bc47798

+ 2 - 0
mp-admin/src/main/java/com/qs/mp/MpApplication.java

@@ -9,12 +9,14 @@ import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
 import org.springframework.context.annotation.Bean;
 import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.scheduling.annotation.EnableAsync;
 
 /**
  * 启动程序
  * 
  * @author ygp
  */
+@EnableAsync
 @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
 public class MpApplication
 {

+ 3 - 3
mp-admin/src/main/java/com/qs/mp/web/controller/api/admin/MarketingMgrController.java

@@ -113,7 +113,8 @@ public class MarketingMgrController extends BaseApiController {
         if (CollectionUtils.isNotEmpty(marketingList)) {
             marketingListVOS = marketingList.stream().map(marketing -> {
                 MarketingListVO marketingListVO = new MarketingListVO();
-                com.qs.mp.common.utils.bean.BeanUtils.copyProperties(marketing, marketingListVO);
+                BeanUtils.copyProperties(marketing, marketingListVO);
+                marketingListVO.setRealNum(marketingUserCodeService.countRealUserNumByMarketingId(marketing.getId()));
                 return marketingListVO;
             }).collect(Collectors.toList());
         }
@@ -230,7 +231,7 @@ public class MarketingMgrController extends BaseApiController {
             return AjaxResult.error("活动信息不存在");
         }
         marketingDataVO.setTitle(marketing.getTitle());
-        marketingDataVO.setRealNum(marketing.getRealNum());
+        marketingDataVO.setRealNum(marketingUserCodeService.countRealUserNumByMarketingId(id));
 
 
         int codeCount = marketingUserCodeService.count(new LambdaQueryWrapper<MarketingUserCode>()
@@ -261,7 +262,6 @@ public class MarketingMgrController extends BaseApiController {
         queryWrapper.eq("t1.marketing_id", id);
         queryWrapper.eq("t1.user_type", UserTypeEnum.ORDINARY.getValue());
 
-        queryWrapper.orderByAsc("t4.sort");
         queryWrapper.orderByDesc("t1.created_time");
 
         List<MarketingUserCodeListVO> list = marketingUserCodeService.listMarketingUserCodeByQueryWrapper(queryWrapper);

+ 12 - 4
mp-admin/src/main/java/com/qs/mp/web/controller/api/user/MarketingController.java

@@ -21,6 +21,7 @@ import com.qs.mp.common.utils.DateUtils;
 import com.qs.mp.common.utils.StringUtils;
 import com.qs.mp.framework.redis.RedisLockKey;
 import com.qs.mp.framework.security.handle.HostHolder;
+import com.qs.mp.framework.service.IWxSubscribeMessage;
 import com.qs.mp.system.domain.SysUser;
 import com.qs.mp.system.service.ISysUserService;
 import com.qs.mp.user.domain.MarketingHitPrize;
@@ -86,6 +87,9 @@ public class MarketingController extends BaseApiController {
     @Autowired
     private ISysUserService sysUserService;
 
+    @Autowired
+    private IWxSubscribeMessage wxSubscribeMessage;
+
     @PostMapping("/generateCode/{marketingId}")
     @ApiOperation("立即获取抽奖码")
     public AjaxResult generateCode(@PathVariable("marketingId") Long marketingId) {
@@ -116,8 +120,9 @@ public class MarketingController extends BaseApiController {
             return AjaxResult.error("错误请求");
         }
 
-        String lockKey = RedisLockKey.build(RedisLockKey.MARKETING_REAL_NUM_LOCK, marketingId);
-        if (!distributedLocker.tryLock(lockKey)) {
+        String lockKey = RedisLockKey.build(RedisLockKey.MARKETING_JOIN_LOCK, userId);
+        // 自动续期,等待3秒
+        if (!distributedLocker.tryLock(lockKey, 3, -1, TimeUnit.SECONDS)) {
             return AjaxResult.error("活动太火爆了,请稍后重试!");
         }
 
@@ -211,9 +216,10 @@ public class MarketingController extends BaseApiController {
             return AjaxResult.error("不能助力自己哦");
         }
 
-        String lockKey = RedisLockKey.build(RedisLockKey.MARKETING_REAL_NUM_LOCK, marketingHelpParam.getMarketingId());
+        String lockKey = RedisLockKey.build(RedisLockKey.MARKETING_JOIN_LOCK, userId);
 
-        if (!distributedLocker.tryLock(lockKey)) {
+        // 自动续期,等待3秒
+        if (!distributedLocker.tryLock(lockKey, 3, -1, TimeUnit.SECONDS)) {
             return AjaxResult.error("活动太火爆了,请稍后重试!");
         }
 
@@ -222,6 +228,8 @@ public class MarketingController extends BaseApiController {
         }finally {
             distributedLocker.unlock(lockKey);
         }
+        // 异步给被助力人发送成功消息
+        wxSubscribeMessage.sendMarketingHelp(helpedUserId, marketing);
 
 
         return AjaxResult.success("助力成功");

+ 98 - 23
mp-quartz/src/main/java/com/qs/mp/quartz/task/MarketingTask.java

@@ -6,7 +6,9 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.qs.mp.admin.domain.Marketing;
+import com.qs.mp.admin.domain.MarketingMsg;
 import com.qs.mp.admin.service.IMarketingAwardsService;
+import com.qs.mp.admin.service.IMarketingMsgService;
 import com.qs.mp.admin.service.IMarketingService;
 import com.qs.mp.common.core.redis.DistributedLocker;
 import com.qs.mp.common.core.redis.RedisCache;
@@ -20,10 +22,13 @@ import com.qs.mp.framework.redis.RedisLockKey;
 import com.qs.mp.framework.service.IWxSubscribeMessage;
 import com.qs.mp.user.domain.MarketingUserCode;
 import com.qs.mp.user.service.IMarketingUserCodeService;
+import java.util.ArrayList;
+import java.util.stream.Collectors;
 import org.redisson.Redisson;
 import org.redisson.api.RedissonClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.aop.framework.AopContext;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
@@ -33,6 +38,8 @@ import java.util.Date;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.TimeUnit;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.Assert;
 
 /**
  * 营销活动相关任务
@@ -56,6 +63,10 @@ public class MarketingTask {
     @Autowired
     private IMarketingUserCodeService marketingUserCodeService;
 
+    @Autowired
+    private IMarketingMsgService marketingMsgService;
+
+
     protected final Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());
 
 
@@ -82,17 +93,6 @@ public class MarketingTask {
             try {
                 // 开奖
                 marketingService.lottery(marketing);
-
-                // 获取所有的参与用户的用户id去重
-                List<MarketingUserCode> userCodeList = marketingUserCodeService.list(new LambdaQueryWrapper<MarketingUserCode>()
-                        .select(MarketingUserCode::getUserId)
-                        .eq(MarketingUserCode::getMarketingId, marketing.getId())
-                        .eq(MarketingUserCode::getUserType, UserTypeEnum.ORDINARY.getValue())
-                        .groupBy(MarketingUserCode::getUserId));
-                for (MarketingUserCode marketingUserCode : userCodeList) {
-                    // 发送开奖订阅通知
-                    wxSubscribeMessage.sendMarketingLottery(marketingUserCode.getUserId(), marketing);
-                }
             } catch (Exception e) {
                 LogUtil.error(logger, e, "活动开奖异常。marketingId:{0}", marketing.getId());
             } finally {
@@ -108,37 +108,68 @@ public class MarketingTask {
      * 发送活动开始通知
      */
     public void sendMessage() {
+        MarketingTask proxy = (MarketingTask) AopContext.currentProxy();
+        proxy.saveMarketingMsg();
+
+    }
+
+
+    @Transactional(rollbackFor = Exception.class)
+    public void saveMarketingMsg() {
         // 获取开始时间小于等于当前时间且未发开始通知的活动
         Date now = DateUtils.getNowDate();
         List<Marketing> marketingList = marketingService.list(new LambdaQueryWrapper<Marketing>()
-                .eq(Marketing::getTriggerStatus, 0)
-                .eq(Marketing::getIsOn, MarketingStatusEnum.ON.getValue())
-                .eq(Marketing::getIsSend, 0)
-                .le(Marketing::getStartTime, now));
+            .eq(Marketing::getTriggerStatus, 0)
+            .eq(Marketing::getIsOn, MarketingStatusEnum.ON.getValue())
+            .eq(Marketing::getIsSend, 0)
+            .le(Marketing::getStartTime, now));
         if (CollectionUtils.isEmpty(marketingList)) {
             return;
         }
 
         // 获取所有参与过活动的普通用户根据用户id去重
         List<MarketingUserCode> userCodeList = marketingUserCodeService.list(new LambdaQueryWrapper<MarketingUserCode>()
-                .select(MarketingUserCode::getUserId)
-                .eq(MarketingUserCode::getUserType, UserTypeEnum.ORDINARY.getValue())
-                .groupBy(MarketingUserCode::getUserId));
+            .select(MarketingUserCode::getUserId)
+            .eq(MarketingUserCode::getUserType, UserTypeEnum.ORDINARY.getValue())
+            .groupBy(MarketingUserCode::getUserId));
         if (CollectionUtils.isEmpty(userCodeList)) {
             return;
         }
 
+        List<Marketing> updateMarketingList = new ArrayList<>();
+        List<MarketingMsg> marketingMsgList = new ArrayList<>();
         for (Marketing marketing : marketingList) {
             for (MarketingUserCode marketingUserCode : userCodeList) {
-                // 发送开始订阅通知
-                wxSubscribeMessage.sendMarketingStart(marketingUserCode.getUserId(), marketing);
+
+                MarketingMsg marketingMsg = new MarketingMsg();
+                marketingMsg.setMarketingId(marketing.getId());
+                marketingMsg.setContext(marketing.getTitle());
+                marketingMsg.setType(2);
+                marketingMsg.setUserId(marketingUserCode.getUserId());
+
+                marketingMsgList.add(marketingMsg);
             }
 
-            // 更新活动通知发送状态
+            Marketing updateMarketing = new Marketing();
+            updateMarketing.setIsSend(1);
+            updateMarketing.setId(marketing.getId());
+            updateMarketingList.add(updateMarketing);
+
             marketingService.update(new LambdaUpdateWrapper<Marketing>()
-                    .set(Marketing::getIsSend, 1)
-                    .eq(Marketing::getId, marketing.getId()));
+                .set(Marketing::getIsSend, 1)
+                .eq(Marketing::getId, marketing.getId()));
+        }
+        if (CollectionUtils.isNotEmpty(marketingMsgList)) {
+            boolean rtn = marketingMsgService.saveBatch(marketingMsgList);
+            Assert.isTrue(rtn, "保存活动开始通知消息失败");
+        }
+
+        // 更新活动通知发送状态
+        if (CollectionUtils.isNotEmpty(updateMarketingList)) {
+            boolean rtn = marketingService.updateBatchById(updateMarketingList);
+            Assert.isTrue(rtn, "更新活动通知发送状态失败");
         }
+
     }
 
     /**
@@ -164,4 +195,48 @@ public class MarketingTask {
         }
     }
 
+    /**
+     * 定时发送一定数量的活动订阅消息
+     * @param limit
+     * @param type 1开奖消息,2活动开始消息
+     */
+    public void sendMsgByLimit(Integer limit, Integer type) {
+
+        String lockKey = "MARKETING_SEND_MSG_LIMIT_KEY";
+        // 同时只能有一个线程发送活动通知任务,自动续期
+        if (!distributedLocker.tryLock(lockKey,0, -1, TimeUnit.SECONDS)) {
+            return;
+        }
+
+        try {
+            List<MarketingMsg> marketingMsgList = marketingMsgService.list(new LambdaQueryWrapper<MarketingMsg>()
+                .eq(MarketingMsg::getType, type)
+                .last("limit " + limit));
+
+            if (CollectionUtils.isEmpty(marketingMsgList)) {
+                return;
+            }
+
+            for (MarketingMsg marketingMsg : marketingMsgList) {
+                Marketing marketing = new Marketing();
+                marketing.setId(marketingMsg.getMarketingId());
+                marketing.setTitle(marketingMsg.getContext());
+                if (marketingMsg.getType() == 1) {
+                    // 发送活动开奖通知
+                    wxSubscribeMessage.sendMarketingLottery(marketingMsg.getUserId(), marketing);
+                } else {
+                    // 发送活动开始通知
+                    wxSubscribeMessage.sendMarketingStart(marketingMsg.getUserId(), marketing);
+                }
+            }
+
+            // 删除已发送的消息
+            marketingMsgService.removeByIds(marketingMsgList.stream().map(MarketingMsg::getId).collect(Collectors.toList()));
+        } catch (Exception e) {
+            LogUtil.error(logger, e, "发送活动订阅通知异常");
+        } finally {
+            distributedLocker.unlock(lockKey);
+        }
+    }
+
 }

+ 71 - 0
mp-service/src/main/java/com/qs/mp/admin/domain/MarketingMsg.java

@@ -0,0 +1,71 @@
+package com.qs.mp.admin.domain;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+import java.util.Date;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableField;
+import java.io.Serializable;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+
+/**
+ * @describe 活动订阅消息表实体类
+ * @auther quanshu
+ * @create 2022-07-03 10:19:16
+ */
+@TableName("mp_marketing_msg")
+@Data
+public class MarketingMsg implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 活动id
+     */
+    @TableField("marketing_id")
+    private Long marketingId;
+
+    /**
+     * 消息发送内容
+     */
+    @TableField("context")
+    private String context;
+
+    /**
+     * 接收消息的用户
+     */
+    @TableField("user_id")
+    private Long userId;
+
+    /**
+     * 信息类型1开奖,2活动开始
+     */
+    @TableField("type")
+    private Integer type;
+
+    /**
+     * 创建时间
+     */
+    @TableField("created_time")
+    private Date createdTime;
+
+    /**
+     * 更新时间
+     */
+    @TableField("updated_time")
+    private Date updatedTime;
+
+    /**
+     * 逻辑删除标识
+     */
+    @TableField("is_deleted")
+    @TableLogic
+    private Integer isDeleted;
+
+
+}

+ 13 - 0
mp-service/src/main/java/com/qs/mp/admin/mapper/MarketingMsgMapper.java

@@ -0,0 +1,13 @@
+package com.qs.mp.admin.mapper;
+
+import com.qs.mp.admin.domain.MarketingMsg;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * @auther quanshu
+ * @create 2022-07-03 10:19:16
+ * @describe 活动订阅消息表mapper类
+ */
+public interface MarketingMsgMapper extends BaseMapper<MarketingMsg> {
+
+}

+ 16 - 0
mp-service/src/main/java/com/qs/mp/admin/service/IMarketingMsgService.java

@@ -0,0 +1,16 @@
+package com.qs.mp.admin.service;
+
+import com.qs.mp.admin.domain.MarketingMsg;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 活动订阅消息表 服务类
+ * </p>
+ *
+ * @author quanshu
+ * @since 2022-07-03
+ */
+public interface IMarketingMsgService extends IService<MarketingMsg> {
+
+}

+ 20 - 0
mp-service/src/main/java/com/qs/mp/admin/service/impl/MarketingMsgServiceImpl.java

@@ -0,0 +1,20 @@
+package com.qs.mp.admin.service.impl;
+
+import com.qs.mp.admin.domain.MarketingMsg;
+import com.qs.mp.admin.mapper.MarketingMsgMapper;
+import com.qs.mp.admin.service.IMarketingMsgService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 活动订阅消息表 服务实现类
+ * </p>
+ *
+ * @author quanshu
+ * @since 2022-07-03
+ */
+@Service
+public class MarketingMsgServiceImpl extends ServiceImpl<MarketingMsgMapper, MarketingMsg> implements IMarketingMsgService {
+
+}

+ 34 - 20
mp-service/src/main/java/com/qs/mp/admin/service/impl/MarketingServiceImpl.java

@@ -111,6 +111,9 @@ public class MarketingServiceImpl extends ServiceImpl<MarketingMapper, Marketing
     @Autowired
     private DistributedLocker distributedLocker;
 
+    @Autowired
+    private IMarketingMsgService marketingMsgService;
+
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -184,16 +187,11 @@ public class MarketingServiceImpl extends ServiceImpl<MarketingMapper, Marketing
 
         Marketing marketing = this.getById(marketingId);
 
-
-        // 真实用户
-        int realNum = marketingUserCodeService.countRealUserNumByMarketingId(marketing.getId());
-        // 获取最新活动信息
-        marketing.setRealNum(realNum);
+        // 设置更新虚拟参与人数
         int randomNum = (int) 1 + (int) (Math.random() * 3);
-        marketing.setFakeNum(marketing.getFakeNum() + randomNum);
         // 更新活动参与人数
-        boolean rtn = this.updateById(marketing);
-        Assert.isTrue(rtn, "更新活动参与人数异常。marketingId:" + marketing.getId());
+        this.update(new LambdaUpdateWrapper<Marketing>().set(Marketing::getFakeNum, marketing.getFakeNum() + randomNum)
+            .eq(Marketing::getId, marketing.getId()));
 
     }
 
@@ -399,6 +397,31 @@ public class MarketingServiceImpl extends ServiceImpl<MarketingMapper, Marketing
         redisCache.deleteObject(userLowLotteryPool);
         redisCache.deleteObject(insideLotteryPool);
 
+
+
+        // 保存开奖消息
+        // 获取所有的参与用户的用户id去重
+        List<MarketingUserCode> sendMsgUserList = marketingUserCodeService.list(new LambdaQueryWrapper<MarketingUserCode>()
+            .select(MarketingUserCode::getUserId)
+            .eq(MarketingUserCode::getMarketingId, marketing.getId())
+            .eq(MarketingUserCode::getUserType, UserTypeEnum.ORDINARY.getValue())
+            .groupBy(MarketingUserCode::getUserId));
+        if (CollectionUtils.isEmpty(sendMsgUserList)) {
+            return;
+        }
+        List<MarketingMsg> marketingMsgList = new ArrayList<>();
+        for (MarketingUserCode marketingUserCode : sendMsgUserList) {
+            // 封装开奖订阅消息
+            MarketingMsg marketingMsg = new MarketingMsg();
+            marketingMsg.setType(1);
+            marketingMsg.setMarketingId(marketing.getId());
+            marketingMsg.setContext(marketing.getTitle());
+            marketingMsg.setUserId(marketingUserCode.getUserId());
+            marketingMsgList.add(marketingMsg);
+        }
+        rtn = marketingMsgService.saveBatch(marketingMsgList);
+        Assert.isTrue(rtn, "保存活动开奖消息失败。marketingId:" + marketing.getId());
+
     }
 
     private MarketingHitPrize exchangeMarketingHitPrize(Marketing marketing, List<MarketingHitPrize> allHitPrizeList, MarketingAwards marketingAwards, List<MarketingAwardsPrize> marketingAwardsPrizeList, MarketingUserCode marketingUserCode) {
@@ -467,20 +490,11 @@ public class MarketingServiceImpl extends ServiceImpl<MarketingMapper, Marketing
         marketingUserCodeService.save(myCode);
 
 
-        // 真实用户
-        int realNum = marketingUserCodeService.countRealUserNumByMarketingId(marketing.getId());
-
-
-        // 获取最新活动信息
-        marketing.setRealNum(realNum);
+        // 设置更新虚拟参与人数
         int randomNum = (int) 1 + (int) (Math.random() * 3);
-        marketing.setFakeNum(marketing.getFakeNum() + randomNum);
         // 更新活动参与人数
-        boolean rtn = this.updateById(marketing);
-        Assert.isTrue(rtn, "更新活动参与人数异常。marketingId:" + marketing.getId());
-
-        // 给被助力人发送成功消息
-        wxSubscribeMessage.sendMarketingHelp(helpedUserId, marketing);
+        this.update(new LambdaUpdateWrapper<Marketing>().set(Marketing::getFakeNum, marketing.getFakeNum() + randomNum)
+            .eq(Marketing::getId, marketing.getId()));
 
     }
 

+ 3 - 1
mp-service/src/main/java/com/qs/mp/framework/redis/RedisLockKey.java

@@ -11,7 +11,9 @@ import com.qs.mp.common.utils.MessageHelper;
 public enum RedisLockKey {
     CREATED_ONLINE_TICKET_ORDER_KEY("created_online_ticket_order_key_{0}", "线上盲票创建锁"),
     ASYNC_TASK_KEY("async_task_key_{0}", "异步任务key"),
-    MARKETING_REAL_NUM_LOCK("marketing_real_num_lock_{0}", "活动真实人数增加锁"),
+
+    MARKETING_JOIN_LOCK("marketing_join_lock_{0}", "活动参与锁"),
+
     MARKETING_LOTTERY_KEY("marketing_lottery_key_{0}","免费抽奖活动开奖"),
     USER_TICKET_CASH_LOCK("user_ticket_cash_lock_{0}", "盲票兑奖锁");
 

+ 2 - 0
mp-service/src/main/java/com/qs/mp/framework/service/impl/WxSubscribeMessageImpl.java

@@ -13,6 +13,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 /**
@@ -50,6 +51,7 @@ public class WxSubscribeMessageImpl implements IWxSubscribeMessage {
   }
 
   @Override
+  @Async("threadPoolTaskExecutor")
   public boolean sendMarketingHelp(Long userId, Marketing marketing) {
     String openId = getOpenIdByUserId(userId);
     WxSubscribeMessage wxSubscribeMessage = new WxSubscribeMessage(userAppId, openId, state);

+ 22 - 0
mp-service/src/main/resources/mapper/admin/MarketingMsgMapper.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qs.mp.admin.mapper.MarketingMsgMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.qs.mp.admin.domain.MarketingMsg">
+        <id column="id" property="id" />
+        <result column="marketing_id" property="marketingId" />
+        <result column="context" property="context" />
+        <result column="user_id" property="userId" />
+        <result column="type" property="type" />
+        <result column="created_time" property="createdTime" />
+        <result column="updated_time" property="updatedTime" />
+        <result column="is_deleted" property="isDeleted" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, marketing_id, context, user_id, type, created_time, updated_time, is_deleted
+    </sql>
+
+</mapper>