فهرست منبع

Merge branch 'feature/v4.10.11' into feature/v4.10.14

yuelujie 3 ماه پیش
والد
کامیت
5fae4d4a0d

+ 74 - 0
src/jfw/modules/app/src/web/templates/frontRouter/activity/free/exchange-success.html

@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html lang="zh-CN" style="font-size: 50px;">
+
+<head>
+  <title>兑换成功</title>
+
+ {{include "/big-member/meta.html"}}
+
+  <!--S-当前页面的css资源-->
+  <link rel="stylesheet" href=//cdn-common.jianyu360.com/cdn/lib/reset-css/5.0.1/reset.min.css />
+  <link rel="stylesheet" href=//cdn-common.jianyu360.com/cdn/lib/vant/2.12.24/lib/index.css />
+  <link rel="stylesheet" href=//cdn-common.jianyu360.com/cdn/lib/vant/2.12.24/lib/icon/local.css />
+    <link rel="stylesheet" href='{{Cdns .Host "seo" "cdn"|SafeUrl}}/common-module/public/css/app-share-sheet.css?v={{Msg "seo" "version"}}' />
+    <link rel="stylesheet" type="text/css" href="{{Cdns .Host "seo" "cdn"|SafeUrl}}/common-module/account/css/fast-login.css?v={{Msg "seo" "version"}}"/>
+    <link rel="stylesheet" href='{{Cdns .Host "seo" "cdn"|SafeUrl}}/common-module/active/exchange/exchange-success.css?v={{Msg "seo" "version"}}' />
+  <!--E-当前页面的css资源-->
+  <style>
+  </style>
+</head>
+
+<body>
+<div class="j-container app">
+{{include "/big-member/header.html"}}
+  <div class="j-main" id="main-app" v-cloak>
+    <div class="exchange-success-page">
+      <div class="success-header">
+        <img src='{{Cdns .Host "seo" "cdn"|SafeUrl}}/common-module/active/exchange/img/new-header.png?v={{Msg "seo" "version"}}' alt="兑换成功">
+        <div class="success-title">兑换成功</div>
+        <p class="success-desc" v-html="info.success_desc1"></p>
+        <p class="success-desc mt-12">${info.success_desc2}</p>
+      </div>
+      <div class="success-main">
+        <p>您需使用如上手机号登录剑鱼标讯平台使用权益</p>
+        <div class="success-item-group">
+          <div class="success-item" v-for="(item, index) in info.list">
+            <div class="success-item-title">
+              <span class="success-item-title-before">方式${titleBefore[index]}</span>
+              <span>${item.label}</span>
+            </div>
+            <div class="success-item-img" v-if="item.img">
+              <img :src="item.img" :alt="item.label">
+            </div>
+            <p class="success-item-desc" v-if="item.desc">${item.desc}</p>
+            <p class="success-item-desc-after" v-if="item.desc_after">${item.desc_after}</p>
+            <div class="success-item-desc-app" v-if="item.app">
+              <a class="highlight-text" href="https://wx.jianyu360.cn/front/downloadapppage/normal?source=H5&f=exchange">去下载 ></a>
+            </div>
+            <div class="success-item-desc-pc" v-if="item.pc">
+              <span class="pc-link">https://www.jianyu360.cn/</span>
+              <span class="highlight-text" @click="doCopy">复制链接</span>
+            </div>
+          </div>
+        </div>
+
+      </div>
+
+    </div>
+  </div>
+</div>
+
+<script src=//cdn-common.jianyu360.com/cdn/lib/vue/2.6.11/vue.min.js> </script>
+<script src=//cdn-common.jianyu360.com/cdn/lib/vant/2.12.24/lib/vant.min.js> </script>
+<script src=//cdn-common.jianyu360.com/cdn/lib/jquery/3.6.0/jquery.min.js> </script>
+<script src=//cdn-common.jianyu360.com/cdn/lib/moment/2.29.1/min/moment.min.js></script>
+{{include "/big-member/commonjs.html"}}
+    <script src='{{Cdns .Host "seo" "cdn"|SafeUrl}}/jyapp/js/common.js?v={{Msg "seo" "version"}}'></script>
+    <!-- <script src=//res2.wx.qq.com/open/js/jweixin-1.6.0.js></script> -->
+    <script src='{{Cdns .Host "seo" "cdn"|SafeUrl}}/common-module/public/js/wx-sdk-share.js?v={{Msg "seo" "version"}}'></script>
+    <script src='{{Cdns .Host "seo" "cdn"|SafeUrl}}/common-module/public/js/app-share-sheet.js?v={{Msg "seo" "version"}}'></script>
+    <script src='{{Cdns .Host "seo" "cdn"|SafeUrl}}/common-module/active/exchange/exchange-success.js?v={{Msg "seo" "version"}}'></script>
+{{include "/common/baiducc.html"}}
+</body>
+
+</html>

+ 2 - 2
src/jfw/modules/app/src/web/templates/frontRouter/activity/free/exchange.html

@@ -49,7 +49,7 @@
             maxlength="4"
             type="number"
             >
-            
+
             <template v-slot:button>
               <div class="img-code-box" @click="getImgCode"><img :src="imgcodeUrl" alt="验证码"></div>
             </template>
@@ -108,7 +108,7 @@
           <div class="success-head">
             <div class="success-title">兑换成功</div>
           </div>
-          <p class="success-desc">${dialog.success_desc1}</p>
+          <p class="success-desc" v-html="dialog.success_desc1"></p>
           <p class="success-desc mt-12">${dialog.success_desc2}</p>
           <div class="swiper-box">
             <van-swipe :loop="false" @change="swiperChange" :show-indicators="false" ref="success_swipe">

+ 51 - 7
src/jfw/modules/subscribepay/src/config.json

@@ -133,10 +133,10 @@
     "采购单位画像包": true,
     "数据报告": true,
     "中标必听课": true,
-    "剑鱼文库会员":true,
-    "市场分析定制报告下载包":true,
-    "业主采购分析报告下载包":true,
-    "企业中标分析报告下载包":true
+    "剑鱼文库会员": true,
+    "市场分析定制报告下载包": true,
+    "业主采购分析报告下载包": true,
+    "企业中标分析报告下载包": true
   },
   "smsServiceRpc": "127.0.0.1:932",
   "nsq": "192.168.3.240:4260",
@@ -210,8 +210,8 @@
     }
   },
   "newOrderTime": 1709827200,
-  "signing_Subject":"北京剑鱼信息技术有限公司",
-  "orderTime":1704038400,
+  "signing_Subject": "北京剑鱼信息技术有限公司",
+  "orderTime": 1704038400,
   "orderEmailReminder": {
     "qmx": "duxin@topnet.net.cn",
     "jianyu": "duxin@topnet.net.cn"
@@ -233,5 +233,49 @@
   "aliProceduresMoney": 0.6,
   "corporateProceduresMoney": 0,
   "vipGiftStartTime": 1709827200,
-  "vipGiftEndTime":1709827200
+  "vipGiftEndTime": 1709827200,
+  "exchangeInfo": {
+    "member": [
+      {
+        "name": "剑鱼标讯App",
+        "qrcode": "https://www.jianyu360.cn/front/downloadJyApp/qr?page=pc_bottom&source=h5dh",
+        "aText": "去下载",
+        "aLink": "https://www.jianyu360.cn/front/downloadapppage/weixin?source=h5dh&code="
+      },
+      {
+        "name": "剑鱼招标网小程序",
+        "qrcode": "https://jy-applet.jianyu360.cn/debrisproduct/official/jyzbw.png",
+        "text": "长按识别进入小程序"
+      },
+      {
+        "name": "剑鱼标讯PC端",
+        "text": "请您复制剑鱼标讯链接在PC端浏览器打开页面,在PC端使用兑换的权益。",
+        "url": "https://www.jianyu360.cn/"
+      }
+    ],
+    "other": [
+      {
+        "name": "剑鱼标讯公众号",
+        "qrcode": "https://www.qmx.top/upload/2025/04/14/202504141703060048JIKuKjG.png",
+        "text": "长按进入公众号",
+        "remark": "如未绑定手机号,绑定兑换手机号即可"
+      },
+      {
+        "name": "剑鱼招标网小程序",
+        "qrcode": "https://jy-applet.jianyu360.cn/debrisproduct/official/jyzbw.png",
+        "text": "长按识别进入小程序"
+      },
+      {
+        "name": "剑鱼标讯App",
+        "qrcode": "https://www.jianyu360.cn/front/downloadJyApp/qr?page=pc_bottom&source=h5dh",
+        "aText": "去下载",
+        "aLink": "https://www.jianyu360.cn/front/downloadapppage/weixin?source=h5dh&code="
+      },
+      {
+        "name": "剑鱼标讯PC端",
+        "text": "请您复制剑鱼标讯链接在PC端浏览器打开页面,在PC端使用兑换的权益。",
+        "url": "https://www.jianyu360.cn/"
+      }
+    ]
+  }
 }

+ 14 - 0
src/jfw/modules/subscribepay/src/config.yaml

@@ -12,6 +12,20 @@ clickhouse:
   DbName: information
   MaxIdleConns: 5
   MaxOpenConns: 5
+clickhousePubtags:
+  Addr: 172.20.45.129:19000
+  UserName: jytop
+  Password: pwdTopJy123
+  DbName: pub_tags
+  MaxIdleConns: 5
+  MaxOpenConns: 5
+convertlabTidb:
+  Addr: 192.168.3.217:4000
+  UserName: root
+  Password: '=PDT49#80Z!RVv52_z'
+  DbName: convertlabsync
+  MaxIdleConns: 5
+  MaxOpenConns: 5
 resourceCenterKey: "resource.rpc" #资源中台
 
 

+ 20 - 0
src/jfw/modules/subscribepay/src/config/config.go

@@ -161,6 +161,26 @@ type config struct {
 	CorporateProceduresMoney float64           `json:"corporateProceduresMoney"`
 	VipGiftEndTime           int64             `json:"vipGiftEndTime"`
 	VipGiftStartTime         int64             `json:"vipGiftStartTime"`
+
+	ExchangeInfo struct {
+		Member []struct {
+			Name   string `json:"name"`
+			Qrcode string `json:"qrcode,omitempty"`
+			AText  string `json:"aText,omitempty"`
+			ALink  string `json:"aLink,omitempty"`
+			Text   string `json:"text,omitempty"`
+			Url    string `json:"url,omitempty"`
+		} `json:"member"`
+		Other []struct {
+			Name   string `json:"name"`
+			Qrcode string `json:"qrcode,omitempty"`
+			Text   string `json:"text,omitempty"`
+			Remark string `json:"remark,omitempty"`
+			AText  string `json:"aText,omitempty"`
+			ALink  string `json:"aLink,omitempty"`
+			Url    string `json:"url,omitempty"`
+		} `json:"other"`
+	} `json:"exchangeInfo"`
 }
 
 type SiteMsgStruct struct {

+ 751 - 95
src/jfw/modules/subscribepay/src/entity/equityCode.go

@@ -3,12 +3,15 @@ package entity
 import (
 	"database/sql"
 	"encoding/json"
+	"errors"
 	"fmt"
+	"github.com/google/uuid"
 	"jy/src/jfw/modules/subscribepay/src/config"
 	"jy/src/jfw/modules/subscribepay/src/pay"
 	util "jy/src/jfw/modules/subscribepay/src/util"
 	"log"
 	"net/http"
+	"strconv"
 	"strings"
 	"time"
 
@@ -20,17 +23,42 @@ import (
 	"app.yhyue.com/moapp/jypkg/common/src/qfw/util/jy"
 )
 
+const (
+	groupTypeAll       = 1 // 用户群组类型 1-全部
+	groupTypeTags      = 2 // 用户群组类型 2-标签群组
+	groupTypeBackGroup = 3 // 用户群组类型 3-后台群组
+
+)
+
+const (
+	BackGroupNew      = "0"   // 后台分组 新用户
+	BackGroupLimitM   = "lm"  // 后台分组 仅大会员(非超级订阅的大会员)
+	BackGroupLimitV   = "lv"  // 后台分组 仅超级订阅 (非大会员的超级订阅)
+	BackGroupLimitMv  = "lmv" // 后台分组 大会员且超级订阅
+	BackGroupLimitNMV = "nmv" // 后台分组 非大会员非超级订阅
+
+)
+
+var PayBackGroup = map[string]struct{}{BackGroupLimitM: {}, BackGroupLimitMv: {}, BackGroupLimitV: {}}
+
 type EquityCode struct {
-	Code      string        //权益码
-	UserId    string        //用户id
-	OpenId    string        //openId
-	Phone     string        //用户手机号
-	NickName  string        //用户昵称
-	GiftCode  int           //活动产品code
-	EquityId  int           //权益id
-	R         *http.Request //
-	OrderCode string        //订单编号
-	Sess      *httpsession.Session
+	Code           string        //权益码
+	UserId         string        //用户id
+	OpenId         string        //openId
+	Phone          string        //用户手机号
+	NickName       string        //用户昵称
+	GiftCode       int           //活动产品code
+	EquityId       int           //权益id
+	R              *http.Request //
+	OrderCode      string        //订单编号
+	Sess           *httpsession.Session
+	userInfo       map[string]interface{} // 用于保存mgo用户信息
+	xcxAccountId   int
+	groupId        string
+	frequencyLimit int
+	groupType      int
+	source         string // 注册来源企业名称
+	disChannel     string // 分销渠道  h5是在外边初始化好的,兑换中心那兑换的时候再处理
 }
 
 func GetNewEquityCode(code, userId, phone, nickName, openId string, r *http.Request, sess *httpsession.Session) *EquityCode {
@@ -127,30 +155,84 @@ func (e *EquityCode) Exchange() (msg string, b bool) {
 	}
 	return
 }
+func (e *EquityCode) H5Exchange() (status int, isMemberProduct bool) {
+	var msg string
+	var b bool
+	repeatKey := fmt.Sprintf("equity_action_phone_%s", e.Phone) // h5兑换没有登录 是手机号
+	if ok, err := redis.Exists("other", repeatKey); ok && err == nil {
+		log.Println(fmt.Sprintf("%s 权益码兑换 请求频次过高,请稍后再试", e.Phone))
+		return
+	}
+	//方式重复性请求--1秒内 允许请求一次
+	redis.Put("other", repeatKey, "REPEAT", 1)
+	// 验证用户 没有用户则创建用户
+	if e.userInfo == nil || len(e.userInfo) == 0 {
+		e.UserId, _, _, _ = e.CreateUser("权益码创建剑鱼用户", "")
+		if e.UserId == "" {
+			log.Println("创建用户失败:", e.Phone)
+			return 0, false
+		}
+	}
+	//订单编号
+	e.OrderCode = pay.GetOrderCode(e.UserId)
+	//更新权益码记录表
+	if b = e.UpdateEquityCode(false); b {
+		//下单并赋权限
+		msg, b, status, isMemberProduct = e.H5UseEquityCodeAndAuthority()
+		//下单失败或者权益开通异常,更新权益码记录表 清除记录
+		if msg != "" || !b {
+			//权限开通异常 回滚数据
+			go func(orderCode string) {
+				if updateBool := e.UpdateEquityCode(true); !updateBool {
+					log.Println(fmt.Sprintf("%s 回退权益码信息异常 ", e.UserId))
+				}
+				if orderCode != "" {
+					util.Mysql.UpdateOrDeleteBySql(`DELETE FROM dataexport_order WHERE order_code = ?`, orderCode)
+				}
+			}(e.OrderCode)
+		}
+	} else {
+		return 0, false
+	}
+	return
+}
 
 // UpdateEquityCode 更新权益码记录信息
 func (e *EquityCode) UpdateEquityCode(b bool) bool {
-	queryMap := map[string]interface{}{
-		"equityCode": e.Code,
-	}
-	updateMap := map[string]interface{}{
-		"userId":      e.UserId,
-		"receiveTime": NowFormat(Date_Full_Layout),
-		"userPerson":  qu.If(e.Phone != "", e.Phone, e.NickName).(string),
-		"userTime":    NowFormat(Date_Full_Layout),
-		"orderCode":   e.OrderCode,
-	}
-	//订单异常 或 权益异常
-	if b {
-		updateMap = map[string]interface{}{
-			"userId":      "",
-			"receiveTime": nil,
-			"userPerson":  "",
-			"orderCode":   "",
-			"userTime":    nil,
-		}
-	}
-	return util.ActivityMysql.Update("equity_record", queryMap, updateMap)
+	f := util.ActivityMysql.ExecTx("更新兑换码信息", func(tx *sql.Tx) bool {
+		q := "SELECT * FROM jyactivities.equity_record  where equityCode=? for update"
+		rs := util.ActivityMysql.SelectBySqlByTx(tx, q, e.Code)
+		if rs == nil || len(*rs) == 0 {
+			return false
+		}
+		queryMap := map[string]interface{}{
+			"equityCode": e.Code,
+		}
+		updateMap := map[string]interface{}{
+			"userId":      e.UserId,
+			"receiveTime": NowFormat(Date_Full_Layout),
+			"userPerson":  qu.If(e.Phone != "", e.Phone, e.NickName).(string),
+			"userTime":    NowFormat(Date_Full_Layout),
+			"orderCode":   e.OrderCode,
+		}
+		//订单异常 或 权益异常
+		if b { //  //
+			updateMap = map[string]interface{}{
+				"userId":      "",
+				"receiveTime": nil,
+				"userPerson":  "",
+				"orderCode":   "",
+				"userTime":    nil,
+			}
+		} else {
+			// p719 20250411 添加更新前再次验证兑换码没有被使用
+			if qu.ObjToString((*rs)[0]["userId"]) != "" {
+				return false
+			}
+		}
+		return util.ActivityMysql.UpdateByTx(tx, "equity_record", queryMap, updateMap)
+	})
+	return f
 }
 
 type ProductInfoByEquity struct {
@@ -165,7 +247,7 @@ type ProductInfoByEquity struct {
 
 // UseEquityCodeAndAuthority 权益码使用 并 赋权限
 func (e *EquityCode) UseEquityCodeAndAuthority() (m string, flag bool) {
-	gifts := util.ActivityMysql.SelectBySql(`SELECT a.entid,b.* FROM equity a LEFT JOIN gift b ON a.giftCode = b.giftCode WHERE a.giftCode =  ? AND a.state = 1 AND a.id = ?;`, e.GiftCode, e.EquityId)
+	gifts := util.ActivityMysql.SelectBySql(`SELECT a.entid,b.*,c.name as eName FROM equity a LEFT JOIN gift b ON a.giftCode = b.giftCode left join jyactivities.enterprise c on a.entid = c.id WHERE a.giftCode =  ? AND a.state = 1 AND a.id = ?;`, e.GiftCode, e.EquityId)
 	if gifts != nil && len(*gifts) > 0 {
 		gift := (*gifts)[0]
 		productInfo := &ProductInfoByEquity{
@@ -183,16 +265,13 @@ func (e *EquityCode) UseEquityCodeAndAuthority() (m string, flag bool) {
 			baseMsg                           = jy.GetBigVipUserBaseMsg(e.Sess, *config.Middleground)
 			distributionChannel, orderChannel = util.GetJyOrderChannel("", e.R.Header.Get("User-Agent")) //默认剑鱼标讯
 		)
-		//权益公司
-		if productInfo.EntId > 1 {
-			//2:青柠平台
-			switch productInfo.EntId {
-			case 2:
-				orderChannel = "xdqd05"
-				distributionChannel = "x041"
-			}
+		entName := qu.IntAll(gift["eName"])
+		orderChannel = "xdqd05"
+		distributionChannel = "x045"
+		items := util.ActivityMysql.SelectBySql(`select item_code from jianyu.dict_item where parent_code='x04' and item_name=?`, entName)
+		if items != nil && len(*items) > 0 {
+			distributionChannel = qu.ObjToString((*items)[0]["item_code"])
 		}
-
 		switch productInfo.ParentCode {
 		case 101:
 			/*
@@ -262,13 +341,7 @@ func (e *EquityCode) UseEquityCodeAndAuthority() (m string, flag bool) {
 					OrderType:  orderType,
 					Badge:      "exchange",
 				}
-				switch distributionChannel {
-				case "x041":
-					filter.ZeroOrderType = "权益码兑换"
-				default:
-					filter.ZeroOrderType = "赠送"
-				}
-
+				filter.ZeroOrderType = "权益码兑换"
 				filterStr, _ := json.Marshal(filter)
 				insertMap["filter"] = string(filterStr)
 				insertMap["filter_id"] = JyVipSubStruct.SaveSelectLog(e.UserId, e.OpenId, &filter)
@@ -334,12 +407,8 @@ func (e *EquityCode) UseEquityCodeAndAuthority() (m string, flag bool) {
 					OrderType:  orderType,
 					Badge:      "exchange",
 				}
-				switch distributionChannel {
-				case "x041":
-					filter.ZeroOrderType = "权益码兑换"
-				default:
-					filter.ZeroOrderType = "赠送"
-				}
+				filter.ZeroOrderType = "权益码兑换"
+
 				filterStr, _ := json.Marshal(filter)
 				insertMap["filter"] = string(filterStr)
 				insertMap["filter_id"] = JyVipSubStruct.SaveSelectLog(e.UserId, e.OpenId, &filter)
@@ -414,12 +483,7 @@ func (e *EquityCode) UseEquityCodeAndAuthority() (m string, flag bool) {
 				"level": level,
 				"badge": "exchange",
 			}
-			switch distributionChannel {
-			case "x041":
-				filterMap["zeroOrderType"] = "权益码兑换"
-			default:
-				filterMap["zeroOrderType"] = "赠送"
-			}
+			filterMap["zeroOrderType"] = "权益码兑换"
 			filter, _ := json.Marshal(filterMap)
 			endDate := time.Date(startTime.Year()+cycle, startTime.Month(), startTime.Day(), 23, 59, 59, 0, time.Local)
 			insertObj := map[string]interface{}{
@@ -489,12 +553,7 @@ func (e *EquityCode) UseEquityCodeAndAuthority() (m string, flag bool) {
 				m = "数据量流量包 计算价格异常"
 				return
 			}
-			switch distributionChannel {
-			case "x041":
-				packDetail.ZeroOrderType = "权益码兑换"
-			default:
-				packDetail.ZeroOrderType = "赠送"
-			}
+			packDetail.ZeroOrderType = "权益码兑换"
 			filter, err := json.Marshal(packDetail)
 			if err != nil {
 				m = "数据量流量包 序列化数据异常"
@@ -534,9 +593,403 @@ func (e *EquityCode) UseEquityCodeAndAuthority() (m string, flag bool) {
 				return
 			}
 		}
+		// 走到这说明前面都成功了
+		// 销售记录表
+		util.Mysql.Insert("order_sale_record", map[string]interface{}{
+			"state":                1,
+			"ordercode":            e.OrderCode,
+			"saler_dept":           "运营部",
+			"saler_dept_id":        27103,
+			"saler_name":           "-",
+			"saler_Id":             -1,
+			"money":                0,
+			"change_value":         0,
+			"group_uuid":           uuid.New().String(),
+			"operator":             "系统自动",
+			"change_reason":        "回款成功",
+			"distribution_channel": distributionChannel,
+			"create_time":          NowFormat(Date_Full_Layout),
+			"statistics_time":      NowFormat(Date_Full_Layout),
+		})
+		return "", flag
 	}
 	return "", flag
 }
+func (e *EquityCode) H5UseEquityCodeAndAuthority() (m string, flag bool, status int, isMemberProduct bool) {
+	gifts := util.ActivityMysql.SelectBySql(`SELECT a.entid,b.* FROM equity a LEFT JOIN gift b ON a.giftCode = b.giftCode  WHERE a.giftCode =  ? AND a.state = 1 AND a.id = ?;`, e.GiftCode, e.EquityId)
+	if gifts != nil && len(*gifts) > 0 {
+		gift := (*gifts)[0]
+		productInfo := &ProductInfoByEquity{
+			ParentCode: qu.IntAll(gift["parentCode"]),
+			GiftCode:   e.GiftCode,
+			Name:       qu.ObjToString(gift["name"]),
+			Num:        qu.IntAll(gift["time"]),
+			NumType:    qu.IntAll(gift["timeType"]),
+			Province:   qu.IntAll(gift["province"]), //0:不做限制;-1:全国;
+			EntId:      qu.IntAll(gift["entid"]),
+		}
+		var (
+			startTime = time.Now()
+			endTime   = time.Now()
+		)
+		vipStatus := 0
+		bigStatus := 0
+		if len(e.userInfo) > 0 {
+			vipStatus = qu.IntAll(e.userInfo["i_vip_status"])
+			bigStatus = qu.IntAll(e.userInfo["i_member_status"])
+		}
+		orderChannel := "xdqd05"
+		distributionChannel := e.disChannel
+		switch productInfo.ParentCode {
+		case 101:
+			/*
+				// 超级订阅 计算周期
+				//插入订单信息
+				// cycleUnit(1:年 2:月 3:天 4:季)
+				// cycleCount 数字长度
+			*/
+			var (
+				area = &map[string]interface{}{"北京": []string{}} //默认
+				//jyactivities 库 》gift 表 标准天和年和支付订单表 天和年单位相反
+				cycleUnit = func(numType int) int {
+					switch numType {
+					case 1:
+						numType = 3 //天
+					case 3:
+						numType = 1 //年
+					}
+					return numType
+				}(productInfo.NumType)
+				//默认设置
+				buySet     = JyVipSubStruct.NewBuySet(area, 1, nil, true) //改版后只能购买升级版超级订阅
+				orderType  = qu.If(vipStatus > 0, 1, 0).(int)             //vip订阅-0:试用 1:续费 2:升级(来自数据库字段说明 -- 为什么没有购买类型)
+				totalPrice int
+				userData   *map[string]interface{}
+			)
+			//插入订单
+			insertMap := map[string]interface{}{
+				"order_status":         1,
+				"user_nickname":        e.NickName,
+				"order_code":           e.OrderCode,
+				"product_type":         "VIP订阅",
+				"create_time":          FormatDate(&startTime, Date_Full_Layout),
+				"user_id":              e.UserId,
+				"user_openid":          e.OpenId,
+				"distribution_channel": distributionChannel, //销售渠道
+				"order_channel":        orderChannel,        //下单渠道
+				"audit_status":         3,                   //默认审核通过
+				"vip_starttime":        FormatDate(&startTime, Date_Full_Layout),
+				"vip_type":             orderType,
+				"pay_money":            0, //价格为0
+				//"pay_time":             FormatDate(&startTime, Date_Full_Layout), //支付时间 当前时间
+				"user_phone":         e.Phone,
+				"is_backstage_order": 1, //线下订单
+			}
+			/*
+			   //开通权限
+			*/
+			if vipStatus > 0 { //仅续费
+				userData, buySet, _ = JyVipSubStruct.GetVipDetail(e.UserId)
+				//是否需要判断 地区
+				if productInfo.Province != 0 {
+					if buySet.AreaCount != productInfo.Province {
+						m = "兑换权益与当前用户权益不对等,无法兑换。请直接联系吕经理15136295365。"
+						status = -7
+						return
+					}
+				}
+				totalPrice = JyVipSubStruct.GetSubVipPrice(buySet, productInfo.Num, cycleUnit) //价格
+				insertMap["order_money"] = totalPrice
+				insertMap["original_price"] = totalPrice
+				filter := VipSimpleMsg{
+					Area:       area,
+					Industry:   []string{},
+					Cyclecount: productInfo.Num,
+					Cycleunit:  cycleUnit,
+					NewBuyset:  buySet,
+					OrderType:  orderType,
+					Badge:      "exchange",
+				}
+				filter.ZeroOrderType = "权益码兑换"
+				filterStr, _ := json.Marshal(filter)
+				insertMap["filter"] = string(filterStr)
+				insertMap["filter_id"] = JyVipSubStruct.SaveSelectLog(e.UserId, e.OpenId, &filter)
+				//计算续费时间
+				timeStamp := qu.Int64All((*userData)["l_vip_endtime"])
+				//开始时间
+				startTime = time.Unix(timeStamp, 0)
+				//结束时间
+				endTime = util.GetDATE(cycleUnit, productInfo.Num, timeStamp)
+				insertMap["vip_type"] = 1
+				insertMap["vip_starttime"] = FormatDate(&startTime, Date_Full_Layout)
+				insertMap["vip_endtime"] = FormatDate(&endTime, Date_Full_Layout)
+				if flag = util.Mysql.Insert("dataexport_order", insertMap) > 1; !flag {
+					m = "超级订阅 续费 订单入库异常"
+					status = 0
+					return
+				}
+				//超级订阅续费--#####
+				flag = JyVipSubStruct.RenewSubVip(e.UserId, FormatDate(&endTime, Date_Full_Layout))
+				//延长超级订阅到期时间--资源中台
+				updateT := &UpdateVipTimeStruct{
+					AccountId: e.UserId,
+					VipTime:   endTime.Unix(),
+				}
+				b, err := updateT.UpdateVipEndTime()
+				if !b || err != nil {
+					log.Println(fmt.Sprintf("%s 更新资源中台 超级订阅到期时间 异常:%s", e.UserId, err.Error()))
+					flag = false
+					m = "超级订阅权限开通失败"
+					status = 0
+					go func(userId string, endTime time.Time) {
+						//调整失败  到期时间复原 对应上面 #####
+						JyVipSubStruct.RenewSubVip(userId, FormatDate(&endTime, Date_Full_Layout))
+					}(e.UserId, startTime)
+					return
+				}
+				//修改附件下载包的到期时间
+				b = FilepackEndtime(e.UserId)
+				if !b {
+					log.Println(fmt.Sprintf("%s 更新 附件下载包到期时间 异常", e.UserId))
+					flag = false
+					m = "超级订阅权限开通失败"
+					status = 0
+					go func(userId string, endTime time.Time) {
+						//调整失败  到期时间复原 对应上面 #####
+						JyVipSubStruct.RenewSubVip(userId, FormatDate(&endTime, Date_Full_Layout))
+					}(e.UserId, startTime)
+					return
+				}
+				flag = b
+			} else if vipStatus < 1 { //新订单
+				//兑换 超级订阅 默认一个省
+				buySet.AreaCount = 1
+				if productInfo.Province != 0 {
+					buySet.AreaCount = productInfo.Province
+				}
+				totalPrice = JyVipSubStruct.GetSubVipPrice(buySet, productInfo.Num, cycleUnit) //价格
+				insertMap["order_money"] = totalPrice
+				insertMap["original_price"] = totalPrice
+				filter := VipSimpleMsg{
+					Area:       area,
+					Industry:   []string{},
+					Cyclecount: productInfo.Num,
+					Cycleunit:  cycleUnit,
+					NewBuyset:  buySet,
+					OrderType:  orderType,
+					Badge:      "exchange",
+				}
+				filter.ZeroOrderType = "权益码兑换"
+				filterStr, _ := json.Marshal(filter)
+				insertMap["filter"] = string(filterStr)
+				insertMap["filter_id"] = JyVipSubStruct.SaveSelectLog(e.UserId, e.OpenId, &filter)
+				//计算时长
+				vsm := VipSimpleMsg{
+					OrderType: 1,
+					NewBuyset: &SubvipBuySet{},
+				}
+				//结束时间
+				endTime = util.GetDATE(cycleUnit, productInfo.Num, startTime.Unix())
+				insertMap["vip_endtime"] = FormatDate(&endTime, Date_Full_Layout)
+				if flag = util.Mysql.Insert("dataexport_order", insertMap) > 1; !flag {
+					m = "超级订阅 购买 订单入库异常"
+					status = 0
+					return
+				}
+				//城市 默认 北京
+				vsm.Area = area
+				vsm.NewBuyset.AreaCount = -1
+				if productInfo.Province > 0 {
+					vsm.NewBuyset.AreaCount = productInfo.Province
+				} //-1是全国
+				vsm.NewBuyset.Upgrade = 1          //超级订阅升级版
+				vsm.NewBuyset.BuyerclassCount = -1 //全行业
+				if flag = JyVipSubStruct.StartSubVip(e.UserId, vsm, startTime, endTime, false, 0, 0, 0); !flag {
+					m = "超级订阅权限开通失败"
+					status = 0
+					return
+				}
+			}
+			if flag {
+				//取消其他超级订阅 未完成的订单
+				go PayCancel(e.UserId, "VIP订阅", "")
+			}
+		case 104:
+			isMemberProduct = true
+			/*
+			   //大会员开通权限
+			*/
+			level := productInfo.NumType //1:专业版;2:智慧版;3:商机版;4:试用版 5:试用版 6:商机版2.0 7:专家版2.0
+			cycle := productInfo.Num     //默认年
+			//判断当前是否是大会员
+			if bigStatus > 0 && bigStatus != level {
+				m = "兑换权益与当前用户权益不对等,无法兑换。请直接联系吕经理15136295365。"
+				status = -7
+				return
+			}
+			if c := util.Mysql.Count("dataexport_order", map[string]interface{}{
+				"user_id":       e.UserId,
+				"product_type":  "大会员",
+				"order_status":  0,
+				"course_status": 2,
+			}); c > 0 {
+				m = "大会员有待审核订单"
+				status = 0
+				return
+			}
+			mData, ok := util.MQFW.FindById("user", e.UserId, `{"i_member_status":1,"i_member_endtime":1,"i_member_starttime":1}`)
+			if !ok || len(*mData) == 0 || mData == nil {
+				m = "查询当前用户信息失败"
+				status = 0
+				return
+			}
+			//续费计算时间
+			if (*mData)["i_member_status"] != nil && qu.Int64All((*mData)["i_member_status"]) > 0 {
+				timeStamp := qu.Int64All((*mData)["i_member_endtime"])
+				//开始时间
+				startTime = time.Unix(timeStamp, 0)
+			}
+			//计算价格
+			orderMoney := MemberStruct.GetMoney(e.UserId, level, cycle)
+			filterMap := map[string]interface{}{
+				"cycle": cycle,
+				"level": level,
+				"badge": "exchange",
+			}
+			filterMap["zeroOrderType"] = "权益码兑换"
+			filter, _ := json.Marshal(filterMap)
+			endDate := time.Date(startTime.Year()+cycle, startTime.Month(), startTime.Day(), 23, 59, 59, 0, time.Local)
+			insertObj := map[string]interface{}{
+				"order_money":          orderMoney,
+				"filter":               string(filter),
+				"order_code":           e.OrderCode,
+				"product_type":         "大会员",
+				"create_time":          NowFormat(Date_Full_Layout),
+				"prepay_time":          NowFormat(Date_Full_Layout),
+				"user_id":              e.UserId,
+				"pay_way":              "",
+				"original_price":       orderMoney,
+				"distribution_channel": distributionChannel, //销售渠道
+				"order_channel":        orderChannel,        //下单渠道
+				"audit_status":         3,                   //默认审核通过
+				"user_phone":           e.Phone,
+				"pay_money":            0,
+				//"pay_time":             NowFormat(Date_Full_Layout),
+				"order_status":       1,
+				"vip_starttime":      NowFormat(Date_Full_Layout),
+				"vip_endtime":        FormatDate(&endDate, Date_Full_Layout),
+				"is_backstage_order": 1, //线下订单
+			}
+			if flag = util.Mysql.Insert("dataexport_order", insertObj) > 1; !flag {
+				m = "大会员 订单入库异常"
+				status = 0
+				return
+			}
+			/*
+			   //开通权限
+			*/
+			if bigStatus > 0 {
+				//大会员续费
+				memberRenew(endDate, e.UserId)
+			} else {
+				//开通大会员
+				normal_member(level, endDate, e.UserId, 0, 0, 0, cycle)
+			}
+			//取消其他订单
+			go PayCancel(e.UserId, "大会员", "")
+		case 112:
+			/*
+			               //数据流量包
+			   			//生订单
+			*/
+			packId := ""
+			switch productInfo.NumType {
+			case 4: //标准
+				packId = fmt.Sprintf("%s_%d", "normal", productInfo.Num)
+			case 5: //高级
+				packId = fmt.Sprintf("%s_%d", "senior", productInfo.Num)
+			}
+			if packId == "" {
+				m = "数据流量包 参数异常"
+				status = 0
+				return
+			}
+			//根据id查询流量包价格
+			packDetail, err := JyDataExportPack.GetPackDetailById(packId)
+			log.Println("packDetail:", packDetail)
+			packDetail.GiveCycle = 0
+			packDetail.DisCountId = 0
+			packDetail.Badge = "exchange"
+			if err != nil {
+				m = "数据量流量包 计算价格异常"
+				status = 0
+				return
+			}
+			packDetail.ZeroOrderType = "权益码兑换"
+			filter, err := json.Marshal(packDetail)
+			if err != nil {
+				m = "数据量流量包 序列化数据异常"
+				status = 0
+				return
+			}
+			log.Println("filter:", string(filter))
+			insertMap := map[string]interface{}{
+				"order_money":          packDetail.Price,
+				"service_status":       0,
+				"user_nickname":        e.NickName,
+				"user_openid":          e.OpenId,
+				"user_phone":           e.Phone,
+				"order_code":           e.OrderCode,
+				"product_type":         "数据流量包",
+				"create_time":          FormatDate(&startTime, Date_Full_Layout),
+				"original_price":       packDetail.Price,
+				"filter":               string(filter),
+				"user_id":              e.UserId,            //20190719 移动端数据导出 生订单添加用户id
+				"distribution_channel": distributionChannel, //销售渠道
+				"order_channel":        orderChannel,        //下单渠道
+				"audit_status":         3,                   //默认审核通过
+				"pay_money":            0,
+				//"pay_time":             FormatDate(&startTime, Date_Full_Layout),
+				"order_status":       1,
+				"is_backstage_order": 1, //线下订单
+			}
+			if flag = util.Mysql.Insert("dataexport_order", insertMap) > 0; !flag {
+				m = "数据流量包 创建订单异常"
+				status = 0
+				return
+			}
+			/*
+			               	//调用中台 增加数据包
+			   			   //开通权限
+			*/
+			if flag, err = perRechargePack(e.UserId, startTime.AddDate(2, 0, 0).Format(Date_Short_Layout), &packDetail); !flag || err != nil {
+				m = "资源账户更改异常"
+				status = 0
+				return
+			}
+		}
+		// 走到这说明前面都成功了
+		// 销售记录表
+		util.Mysql.Insert("order_sale_record", map[string]interface{}{
+			"state":                1,
+			"ordercode":            e.OrderCode,
+			"saler_dept":           "运营部",
+			"saler_dept_id":        27103,
+			"saler_name":           "-",
+			"saler_Id":             -1,
+			"money":                0,
+			"change_value":         0,
+			"group_uuid":           uuid.New().String(),
+			"operator":             "系统自动",
+			"change_reason":        "回款成功",
+			"distribution_channel": distributionChannel,
+			"create_time":          NowFormat(Date_Full_Layout),
+			"statistics_time":      NowFormat(Date_Full_Layout),
+		})
+		return "", flag, 1, isMemberProduct
+	}
+	return "", flag, 0, isMemberProduct
+}
 
 type ExchangeRecords struct {
 	Count   int        `json:"count"`
@@ -666,8 +1119,29 @@ func (e *EquityCode) Submit(codes []string, disChannel, productName, industryCod
 			return false
 		}
 		filter := fmt.Sprintf(`{"xcx_account_id":%d,"baseUserId":%d,"area_count":%d,"buyItemCode":["%s"],"industryCode":"%s","fromMiniCode":"%s","cycleCount":%d,"cycleUnit":%d,"originalAmount":0}`, comAccountId, userId, buyNum, strings.Join(codes, `","`), industryCode, codes[0], number, cycleUnit)
-		util.ActivityMysql.InsertBatchByTx(tx, "jianyu.dataexport_order", []string{"order_code", "pay_money", "order_money", "pay_time", "order_status", "create_time", "filter", "original_price", "product_type", "user_phone", "user_id", "vip_starttime", "vip_endtime", "discount_price", "sale_time", "order_channel", "distribution_channel", "audit_status"}, []interface{}{
-			orderCode, 0, 0, nowFormat, 1, nowFormat, filter, 0, productName, e.Phone, positionId, FormatDate(&orderStartTime, Date_Full_Layout), FormatDate(&orderEndTime, Date_Full_Layout), 0, nowFormat, "xdqd05", disChannel, 3})
+		if b, _ := util.ActivityMysql.InsertBatchByTx(tx, "jianyu.dataexport_order", []string{"order_code", "pay_money", "order_money", "pay_time", "order_status", "create_time", "filter", "original_price", "product_type", "user_phone", "user_id", "vip_starttime", "vip_endtime", "discount_price", "sale_time", "order_channel", "distribution_channel", "audit_status"}, []interface{}{
+			orderCode, 0, 0, nowFormat, 1, nowFormat, filter, 0, productName, e.Phone, positionId, FormatDate(&orderStartTime, Date_Full_Layout), FormatDate(&orderEndTime, Date_Full_Layout), 0, nowFormat, "xdqd05", disChannel, 3}); b <= 0 {
+			return false
+		}
+		// 销售记录表
+		if util.Mysql.InsertByTx(tx, "jianyu.order_sale_record", map[string]interface{}{
+			"state":                1,
+			"ordercode":            orderCode,
+			"saler_dept":           "运营部",
+			"saler_dept_id":        27103,
+			"saler_name":           "-",
+			"saler_Id":             -1,
+			"money":                0,
+			"change_value":         0,
+			"group_uuid":           uuid.New().String(),
+			"operator":             "系统自动",
+			"change_reason":        "回款成功",
+			"distribution_channel": disChannel,
+			"create_time":          nowFormat,
+			"statistics_time":      nowFormat,
+		}) <= 0 {
+			return false
+		}
 		return true
 	}) {
 		return 1
@@ -724,37 +1198,40 @@ func (e *EquityCode) CreateUser(msg, miniprogramCode string) (string, int64, int
 				return false
 			}
 		}
-		//小程序公用的账户
-		if xcxAccountRes := util.BaseMysql.SelectBySqlByTx(tx, `select id from base_service.base_account where person_id=? and type=2 and miniprogram_code is null`, xcxPersonId); xcxAccountRes == nil {
-			log.Println(msg, e.Phone, "查询小程序公用的账户失败")
-			return false
-		} else if len(*xcxAccountRes) == 0 {
-			if xcxComAccountId = util.BaseMysql.InsertBySqlByTx(tx, `insert into base_service.base_account (person_id,type) values (?,?)`, xcxPersonId, 2); xcxComAccountId <= 0 {
-				log.Println(msg, e.Phone, "小程序公用的账户保存失败")
+		// 非小程序不需要走这
+		if miniprogramCode != "" {
+			//小程序公用的账户
+			if xcxAccountRes := util.BaseMysql.SelectBySqlByTx(tx, `select id from base_service.base_account where person_id=? and type=2 and miniprogram_code is null`, xcxPersonId); xcxAccountRes == nil {
+				log.Println(msg, e.Phone, "查询小程序公用的账户失败")
 				return false
+			} else if len(*xcxAccountRes) == 0 {
+				if xcxComAccountId = util.BaseMysql.InsertBySqlByTx(tx, `insert into base_service.base_account (person_id,type) values (?,?)`, xcxPersonId, 2); xcxComAccountId <= 0 {
+					log.Println(msg, e.Phone, "小程序公用的账户保存失败")
+					return false
+				}
+			} else {
+				xcxComAccountId = qu.Int64All((*xcxAccountRes)[0]["id"])
 			}
-		} else {
-			xcxComAccountId = qu.Int64All((*xcxAccountRes)[0]["id"])
-		}
-		//小程序的职位
-		positionRes := util.BaseMysql.SelectBySqlByTx(tx, `select id from base_service.base_position where user_id=? and type=2 and miniprogram_code=?`, xcxUserId, miniprogramCode)
-		if positionRes == nil {
-			log.Println(msg, e.Phone, "查询小程序职位失败")
-			return false
-		}
-		if len(*positionRes) == 0 {
-			accountId := util.BaseMysql.InsertBySqlByTx(tx, `insert into base_service.base_account (person_id,type,miniprogram_code) values (?,?,?)`, xcxPersonId, 2, miniprogramCode)
-			if accountId <= 0 {
-				log.Println(msg, e.Phone, miniprogramCode, "小程序的账户保存失败")
+			//小程序的职位
+			positionRes := util.BaseMysql.SelectBySqlByTx(tx, `select id from base_service.base_position where user_id=? and type=2 and miniprogram_code=?`, xcxUserId, miniprogramCode)
+			if positionRes == nil {
+				log.Println(msg, e.Phone, "查询小程序职位失败")
 				return false
 			}
-			xcxPositionId = util.BaseMysql.InsertBySqlByTx(tx, `insert into base_service.base_position (type,account_id,user_id,miniprogram_code,create_time) values (?,?,?,?,?)`, 2, accountId, xcxUserId, miniprogramCode, nowFormat)
-			if xcxPositionId <= 0 {
-				log.Println(msg, e.Phone, miniprogramCode, "小程序的职位保存失败")
-				return false
+			if len(*positionRes) == 0 {
+				accountId := util.BaseMysql.InsertBySqlByTx(tx, `insert into base_service.base_account (person_id,type,miniprogram_code) values (?,?,?)`, xcxPersonId, 2, miniprogramCode)
+				if accountId <= 0 {
+					log.Println(msg, e.Phone, miniprogramCode, "小程序的账户保存失败")
+					return false
+				}
+				xcxPositionId = util.BaseMysql.InsertBySqlByTx(tx, `insert into base_service.base_position (type,account_id,user_id,miniprogram_code,create_time) values (?,?,?,?,?)`, 2, accountId, xcxUserId, miniprogramCode, nowFormat)
+				if xcxPositionId <= 0 {
+					log.Println(msg, e.Phone, miniprogramCode, "小程序的职位保存失败")
+					return false
+				}
+			} else {
+				xcxPositionId = qu.Int64All((*positionRes)[0]["id"])
 			}
-		} else {
-			xcxPositionId = qu.Int64All((*positionRes)[0]["id"])
 		}
 		//
 		if _id == "" {
@@ -773,7 +1250,7 @@ func (e *EquityCode) CreateUser(msg, miniprogramCode string) (string, int64, int
 					"phone":       e.Phone,
 					"way":         "equityCode",
 					"system":      qu.GetSystem(e.R.UserAgent()),
-					"source":      "xcx",
+					"source":      e.source,
 					"ip":          qu.GetIp(e.R),
 					"user_agent":  e.R.UserAgent(),
 					"create_time": nowUnix,
@@ -791,3 +1268,182 @@ func (e *EquityCode) CreateUser(msg, miniprogramCode string) (string, int64, int
 	}
 	return "", 0, 0, 0
 }
+func (e *EquityCode) InitSource(name string) {
+	e.source = name
+}
+func (e *EquityCode) InitGiftCode(giftCode int) {
+	e.GiftCode = giftCode
+}
+func (e *EquityCode) InitDisChannel(disChannel string) {
+	e.disChannel = disChannel
+}
+func (e *EquityCode) InitUserInfo() error {
+	users, ok := util.MQFW.Find("user", map[string]interface{}{
+		"$or": []map[string]interface{}{
+			map[string]interface{}{
+				"s_phone": e.Phone,
+			}, map[string]interface{}{
+				"s_m_phone": e.Phone,
+			},
+		},
+	}, `{"s_phone":-1}`, `{"_id":1,"base_user_id":1,"i_vip_status":1,"i_member_status":1}`, false, 0, 1)
+	if !ok {
+		log.Println("GetUserInfo", e.Phone, "查询mog库user表失败")
+		return errors.New("查询mog库user表失败")
+	}
+	if users != nil && len(*users) > 0 {
+		e.userInfo = (*users)[0]
+		e.UserId = BsonIdToSId(e.userInfo["_id"])
+		//  补充用户小程序accountId
+		baseUserId := qu.Int64All(e.userInfo["base_user_id"])
+		baseUserRes := util.BaseMysql.SelectBySql(`select person_id from base_service.base_user where id=?`, baseUserId)
+		if baseUserRes == nil || len(*baseUserRes) == 0 {
+			log.Println("InitUserInfo", e.Phone, "baseUserId不存在", baseUserId)
+			return errors.New("获取用户失败")
+		}
+		xcxPersonId := qu.Int64All((*baseUserRes)[0]["person_id"])
+		if xcxAccountRes := util.BaseMysql.SelectBySql(`select id from base_service.base_account where person_id=? and type=2 and miniprogram_code is null`, xcxPersonId); xcxAccountRes == nil || len(*xcxAccountRes) == 0 {
+			log.Println("InitUserInfo", e.Phone, "查询小程序公用的账户失败 xcxPersonId:", xcxPersonId) // 说明用户没开通小程序账号
+			return nil
+		} else {
+			e.xcxAccountId = qu.IntAll((*xcxAccountRes)[0]["id"])
+		}
+	}
+	return nil
+}
+
+func (e *EquityCode) InitCheckInfo(groupId string, groupType int, frequencyLimit int, equityId int) {
+	e.groupId = groupId
+	e.groupType = groupType
+	e.frequencyLimit = frequencyLimit
+	e.EquityId = equityId
+}
+
+// CheckFrequencyLimit 校验兑换码次数
+func (e *EquityCode) CheckFrequencyLimit() bool {
+	if e.userInfo == nil || len(e.userInfo) == 0 || e.frequencyLimit == 0 {
+		// 没有用户信息 说明是新用户  不用校验  或者没有设置验证
+		return true
+	}
+	_id := qu.ObjToString(e.userInfo["_id"])
+	q := "select count(*) from jyactivities.equity_record where  userId=? and equityId=?"
+	if int(util.BaseMysql.CountBySql(q, _id, e.EquityId)) >= e.frequencyLimit {
+		return false
+	}
+	return true
+}
+
+// CheckUserGroup 校验用户群组
+func (e *EquityCode) CheckUserGroup() bool {
+	if e.groupType == groupTypeAll { //全部
+		//不用校验
+		return true
+	}
+	groupIdList := strings.Split(e.groupId, ",")
+	if len(groupIdList) == 0 { // 选择群组但是群组id为空 配置有误
+		log.Printf("EquityId[%v]: 群组id有误 \n", e.EquityId)
+		return false
+	}
+	if e.userInfo == nil || len(e.userInfo) == 0 {
+		// 没有用户信息 说明是还没有创建用户 所以不用匹配标签群组
+		if e.groupType == groupTypeTags { // 直接返回
+			return false
+		}
+		// 判断后台群组是否包含付费群组(目前付费指超级订阅、大会员 后续如果有其他付费群组这里需要同步调整)
+		// 包含付费用户群组则新用户不符合条件 所以直接返回
+		for i := 0; i < len(groupIdList); i++ {
+			if _, ok := PayBackGroup[groupIdList[i]]; ok {
+				return false
+			}
+		}
+		return true
+	}
+	// 已有用户 正常判断
+	switch e.groupType {
+	case groupTypeTags:
+		return e.checkTagsGroup()
+	case groupTypeBackGroup:
+		return e.checkBackGroup()
+	default:
+		log.Printf("异常分组类型:equityId[%v] 异常类型[%v]\n", e.EquityId, e.groupType)
+		return false
+	}
+}
+func (e *EquityCode) checkTagsGroup() bool {
+	// 用户id 不是mongoId    直接返回
+	// 选择标签分组时 只处理个人身份领取优惠券
+	selectGroupId := []int64{}
+	groupIdList := strings.Split(e.groupId, ",")
+	for i := 0; i < len(groupIdList); i++ {
+		tmpId, err := strconv.ParseInt(groupIdList[i], 10, 64)
+		if err != nil {
+			continue
+		}
+		selectGroupId = append(selectGroupId, tmpId)
+	}
+	if len(selectGroupId) == 0 {
+		log.Println("checkTagsGroup 所选用户分组有误", e.EquityId, e.groupType, e.groupId)
+		return false
+	}
+	uic := util.NewUserIdConstructor(selectGroupId, 0, util.ClickhousePubTags, util.ConvertlabMysql)
+	baseUserId := qu.Int64All(e.userInfo["base_user_id"])
+	count := uic.CountUser(baseUserId)
+	return qu.If(count > 0, true, false).(bool)
+}
+func (e *EquityCode) checkBackGroup() bool {
+	groupIdList := strings.Split(e.groupId, ",")
+	xcxNGroupId := []interface{}{}
+	xcxNGroupIdParam := []string{}
+	userAttributesMap := e.findAttributesMap()
+	//xcxHGroupId := []string{}
+	for i := 0; i < len(groupIdList); i++ {
+		if strings.HasPrefix(groupIdList[i], "xcx") {
+			xcxGroupIdList := strings.Split(groupIdList[i], "-")
+			if len(xcxGroupIdList) == 2 && xcxGroupIdList[1] == "n" { // 非xx行业的  多个时匹配上一个即为匹配成功
+				xcxNGroupId = append(xcxNGroupId, xcxGroupIdList[2])
+				xcxNGroupIdParam = append(xcxNGroupIdParam)
+			}
+		} else {
+			if userAttributesMap[groupIdList[i]] { // 匹配上一个就成功  不用再继续了
+				return true
+			}
+		}
+	}
+	// 一个都没有匹配上剑鱼会员身份 再来看一下是否有配置小程序的
+	if len(xcxNGroupId) > 0 {
+		if e.xcxAccountId == 0 {
+			// 没有小程序账户 直接匹配上非xx行业
+			return true
+		}
+		//  匹配小程序身份
+		return e.checkXcxNPower(xcxNGroupId, xcxNGroupIdParam)
+	}
+	return false
+
+}
+
+// 获取用户身份所属后台群组
+func (e *EquityCode) findAttributesMap() (attributesMap map[string]bool) {
+	attributesMap = map[string]bool{}
+	vipStatus := e.userInfo["i_vip_status"]
+	memberStatus := e.userInfo["i_member_status"]
+	if qu.IntAll(vipStatus) <= 0 && qu.IntAll(memberStatus) > 0 { // 仅大会员
+		attributesMap[BackGroupLimitM] = true
+	} else if qu.IntAll(vipStatus) > 0 && qu.IntAll(memberStatus) <= 0 { // 仅超级订阅
+		attributesMap[BackGroupLimitV] = true
+	} else if qu.IntAll(vipStatus) > 0 && qu.IntAll(memberStatus) > 0 { // 超级订阅且大会员
+		attributesMap[BackGroupLimitMv] = true
+	} else { // 非超级订阅非大会员
+		attributesMap[BackGroupLimitNMV] = true
+	}
+	return
+}
+
+// 判断非xx行业或非xx。。。行业小程序会员
+func (e *EquityCode) checkXcxNPower(xcxNGroupId []interface{}, xcxNGroupIdParam []string) bool {
+	value := []interface{}{e.xcxAccountId}
+	value = append(value, xcxNGroupId...)
+	q := fmt.Sprintf("select count(distinct(m.industry_code)) from debris_product.user_power up left join debris_product.miniprogram m on (up.miniprogram_code=m.code) where up.start_time<=now() and up.end_time>=now() and up.account_id=? and  up.industry_code in (%s) ", strings.Join(xcxNGroupIdParam, ","))
+	//查询结果数量小于查询的行业数量,说明存在不是xx行业的 表示匹配成功
+	return util.BaseMysql.CountBySql(q, xcxNGroupId...) < int64(len(xcxNGroupId))
+}

+ 84 - 20
src/jfw/modules/subscribepay/src/service/equityCode.go

@@ -1,6 +1,8 @@
 package service
 
 import (
+	. "app.yhyue.com/moapp/jybase/api"
+	"app.yhyue.com/moapp/jybase/go-xweb/httpsession"
 	"encoding/json"
 	"fmt"
 	"jy/src/jfw/modules/subscribepay/src/config"
@@ -10,7 +12,6 @@ import (
 	"strings"
 	"time"
 
-	. "app.yhyue.com/moapp/jybase/api"
 	qutil "app.yhyue.com/moapp/jybase/common"
 	"app.yhyue.com/moapp/jybase/dchest/captcha"
 	"app.yhyue.com/moapp/jybase/go-xweb/xweb"
@@ -104,11 +105,11 @@ func (e *EquityCode) Submit() {
 		} else if reqType == 2 {
 			sessVal := e.Session().GetMultiple()
 			phone := qutil.ObjToString(sessVal["identCodeKey"])
-			if jy.CheckPhoneIdent(e.Session(), e.GetString("identCode")) == "" { //验证码不正确
+			if CheckPhoneIdentNotClear(e.Session(), e.GetString("identCode")) == "" { //验证码不正确
 				return -1 //短信验证码错误
 			}
 			equityCode := e.GetString("equityCode")
-			datas := util.ActivityMysql.SelectBySql(`SELECT a.id,a.userId,a.equityId,a.startTime,a.endTime,a.state,a.number,a.timeType,b.foreignCode,b.productName,c.province,c.name,e.name as eName FROM jyactivities.equity_record a
+			datas := util.ActivityMysql.SelectBySql(`SELECT a.id,a.userId,a.equityId,a.startTime,a.endTime,a.state,a.number,a.timeType,b.foreignCode,b.productName,c.province,c.name,e.name as eName,a.giftCode,d.groupId,d.frequencyLimit,d.groupType FROM jyactivities.equity_record a
 				INNER JOIN jyactivities.product b ON (a.equityCode=? AND a.parentCode=b.productCode)
 				inner join jyactivities.gift c on (a.giftCode=c.giftCode)
 				INNER JOIN jyactivities.equity d ON (a.equityId=d.id)
@@ -143,14 +144,66 @@ func (e *EquityCode) Submit() {
 					}
 				}
 			}
+			jy.ClearPhoneIdentSession(e.Session())
+			log.Println("清除identCode")
+			disChannel := "x045"
+			items := util.ActivityMysql.SelectBySql(`select item_code from jianyu.dict_item where parent_code='x04' and item_name=?`, qutil.ObjToString((*datas)[0]["eName"]))
+			if items != nil && len(*items) > 0 {
+				disChannel = qutil.ObjToString((*items)[0]["item_code"])
+			}
+			groupId := qutil.ObjToString((*datas)[0]["groupId"])
+			frequencyLimit := qutil.IntAll((*datas)[0]["frequencyLimit"])
+			equityId := qutil.IntAll((*datas)[0]["equityId"])
+			groupType := qutil.IntAll((*datas)[0]["groupType"])
+			nec := entity.GetNewEquityCode(equityCode, "", phone, "", "", e.Request, e.Session())
+			err := nec.InitUserInfo()
+			if err != nil {
+				return 0
+			} // 获取用户信息 用于后续校验兑换次数 用户身份
+			nec.InitCheckInfo(groupId, groupType, frequencyLimit, equityId) // 初始化校验条件信息
+			//  验证兑换码次数  使用用户mgoid 验证
+			if !nec.CheckFrequencyLimit() {
+				return -9
+			}
+			//  校验身份
+			if !nec.CheckUserGroup() {
+				return -8
+			}
+			if qutil.ObjToString((*datas)[0]["eName"]) != "" {
+				nec.InitSource(qutil.ObjToString((*datas)[0]["eName"]))
+			}
+			nec.InitDisChannel(disChannel)
+			giftCode := qutil.IntAll((*datas)[0]["giftCode"])
+			nec.InitGiftCode(giftCode) // 为了后边大会员超级订阅数据流量包走兑换中心的代码 才初始化这的
+			ads, ok := util.MQFW.FindOneByField("ad", map[string]interface{}{"s_code": "mini-app-mine-customer"}, `{"a_son":1}`)
+			if ok && ads != nil && len(*ads) > 0 {
+				a_sons, _ := (*ads)["a_son"].([]interface{})
+				if len(a_sons) > 0 {
+					a_son, _ := a_sons[0].(map[string]interface{})
+					result["kfcode"] = qutil.ObjToString(a_son["s_pic"])
+				}
+			}
 			foreignCode := qutil.ObjToString((*datas)[0]["foreignCode"])
+			giftName := qutil.ObjToString((*datas)[0]["name"])
 			if foreignCode == "" {
-				log.Println(phone, equityCode, "没有找到对应的小程序code")
-				return 0
+				result["isxcx"] = 0
+				log.Println(phone, equityCode, "没有找到对应的小程序code,去验证是不是剑鱼产品")
+				//  超级订阅、大会员、数据流量包走其他的
+				status, isMemberProduct := nec.H5Exchange()
+				if status == 1 {
+					result["name"] = giftName
+					if isMemberProduct {
+						//   返回配置
+						result["list"] = config.Config.ExchangeInfo.Member
+					} else {
+						result["list"] = config.Config.ExchangeInfo.Other
+					}
+				}
+				return status
 			}
 			foreignCodes := strings.Split(foreignCode, ",")
-			giftName := qutil.ObjToString((*datas)[0]["name"])
 			wh, args := qutil.WhArgs(foreignCodes)
+			result["isxcx"] = 1
 			mps := util.ActivityMysql.SelectBySql(`select a.name as aName,a.qrcode,b.name as bName,b.code as bCode from debris_product.miniprogram a
 					inner join debris_product.industry b on (a.code in (`+wh+`) and a.industry_code=b.code)`, args...)
 			if mps == nil || len(*mps) == 0 {
@@ -160,12 +213,7 @@ func (e *EquityCode) Submit() {
 			industryCode := qutil.ObjToString((*mps)[0]["bCode"])
 			industry := qutil.ObjToString((*mps)[0]["bName"])
 			//
-			disChannel := "x045"
-			items := util.ActivityMysql.SelectBySql(`select item_code from jianyu.dict_item where parent_code='x04' and item_name=?`, qutil.ObjToString((*datas)[0]["eName"]))
-			if items != nil && len(*items) > 0 {
-				disChannel = qutil.ObjToString((*items)[0]["item_code"])
-			}
-			res := entity.GetNewEquityCode(equityCode, "", phone, "", "", e.Request, e.Session()).Submit(foreignCodes, disChannel, qutil.ObjToString((*datas)[0]["productName"]), industryCode, qutil.IntAll((*datas)[0]["province"]), qutil.IntAll((*datas)[0]["number"]), qutil.IntAll((*datas)[0]["timeType"]))
+			res := nec.Submit(foreignCodes, disChannel, qutil.ObjToString((*datas)[0]["productName"]), industryCode, qutil.IntAll((*datas)[0]["province"]), qutil.IntAll((*datas)[0]["number"]), qutil.IntAll((*datas)[0]["timeType"]))
 			if res == 1 {
 				result["name"] = giftName
 				list := []map[string]interface{}{}
@@ -183,14 +231,6 @@ func (e *EquityCode) Submit() {
 		return 0
 	}()
 	result["status"] = status
-	ads, ok := util.MQFW.FindOneByField("ad", map[string]interface{}{"s_code": "mini-app-mine-customer"}, `{"a_son":1}`)
-	if ok && ads != nil && len(*ads) > 0 {
-		a_sons, _ := (*ads)["a_son"].([]interface{})
-		if len(a_sons) > 0 {
-			a_son, _ := a_sons[0].(map[string]interface{})
-			result["kfcode"] = qutil.ObjToString(a_son["s_pic"])
-		}
-	}
 	e.ServeJson(Result{
 		Data: result,
 	})
@@ -210,3 +250,27 @@ func (e *EquityCode) Captcha() error {
 	w.Header().Set("Content-Type", "image/png")
 	return captcha.WriteImage(w, id, 90, 30)
 }
+
+const (
+	defaultPhoneFlag = "identCode"
+	ExperienceSign   = "EXPERIENCESIGN"
+)
+
+// 短信验证码校验 不清除
+func CheckPhoneIdentNotClear(session *httpsession.Session, code string, sessionKey ...string) string {
+	sessionKeyFlag := defaultPhoneFlag
+	if len(sessionKey) > 0 && sessionKey[0] != "" {
+		sessionKeyFlag = sessionKey[0]
+	}
+	lastSentTime := qutil.Int64All(session.Get(fmt.Sprintf("%sTime", sessionKeyFlag)))
+	// 是否超过三分钟
+	if lastSentTime < 0 || time.Now().Unix()-lastSentTime > 300 {
+		return ""
+	}
+	identCodeValue, _ := session.Get(fmt.Sprintf("%sValue", sessionKeyFlag)).(string)
+	if identCodeValue != "" && identCodeValue == code {
+		identCodeKey, _ := session.Get(fmt.Sprintf("%sKey", sessionKeyFlag)).(string)
+		return identCodeKey
+	}
+	return ""
+}

+ 43 - 0
src/jfw/modules/subscribepay/src/util/db.go

@@ -24,12 +24,15 @@ var Mysql *mysql.Mysql
 var BaseMysql *mysql.Mysql
 var PushMysql *mysql.Mysql
 var InfoMysql *mysql.Mysql
+var ConvertlabMysql *mysql.Mysql
 var ActivityMysql *mysql.Mysql
 var Mgo_bidding mg.MongodbSim
 var Mgo_log mg.MongodbSim
 var ClickhouseConn driver.Conn
+var ClickhousePubTags driver.Conn
 
 func init() {
+	var ctx = context.Background()
 	//初始化elastic
 	elastic.NewEs(Config.ElasticVersion, Config.Elasticsearch, qutil.IntAllDef(Config.ElasticPoolSize, 30), Config.ElasticUserName, Config.ElasticPassword)
 	//初始化redis
@@ -109,6 +112,15 @@ func init() {
 		MaxIdleConns: Config.ActivityMysql.MaxIdleConns,
 	}
 	ActivityMysql.Init()
+	ConvertlabMysql = &mysql.Mysql{
+		Address:      g.Cfg().MustGet(ctx, "convertlabTidb.Addr").String(),
+		UserName:     g.Cfg().MustGet(ctx, "convertlabTidb.UserName").String(),
+		PassWord:     g.Cfg().MustGet(ctx, "convertlabTidb.Password").String(),
+		DBName:       g.Cfg().MustGet(ctx, "convertlabTidb.DbName").String(),
+		MaxOpenConns: g.Cfg().MustGet(ctx, "convertlabTidb.MaxOpenConns").Int(),
+		MaxIdleConns: g.Cfg().MustGet(ctx, "convertlabTidb.MaxIdleConns").Int(),
+	}
+	ConvertlabMysql.Init()
 	//bidding
 	Mgo_bidding = mg.MongodbSim{
 		MongodbAddr: Config.Mongobidding.Address,
@@ -142,6 +154,7 @@ func init() {
 	}
 	ActivityMysql.Init()
 	ConnectClickhouse()
+	ConnectClickhousePubtags()
 }
 func ConnectClickhouse() error {
 	var (
@@ -173,3 +186,33 @@ func ConnectClickhouse() error {
 	}
 	return nil
 }
+func ConnectClickhousePubtags() error {
+	var (
+		ctx = context.Background()
+		err error
+	)
+	ClickhousePubTags, err = clickhouse.Open(&clickhouse.Options{
+		Addr:         []string{g.Cfg().MustGet(ctx, "clickhousePubtags.Addr").String()},
+		DialTimeout:  10 * time.Second,
+		MaxIdleConns: g.Cfg().MustGet(ctx, "clickhousePubtags.MaxIdleConns").Int(),
+		MaxOpenConns: g.Cfg().MustGet(ctx, "clickhousePubtags.MaxOpenConns").Int(),
+		Auth: clickhouse.Auth{
+			Database: g.Cfg().MustGet(ctx, "clickhousePubtags.DbName").String(),
+			Username: g.Cfg().MustGet(ctx, "clickhousePubtags.UserName").String(),
+			Password: g.Cfg().MustGet(ctx, "clickhousePubtags.Password").String(),
+		},
+		Debugf: func(format string, v ...interface{}) {
+			fmt.Printf(format, v)
+		},
+	})
+	if err != nil {
+		return err
+	}
+	if err := ClickhousePubTags.Ping(ctx); err != nil {
+		if exception, ok := err.(*clickhouse.Exception); ok {
+			fmt.Printf("ClickhousePubTags Exception [%d] %s \n%s\n", exception.Code, exception.Message, exception.StackTrace)
+		}
+		return err
+	}
+	return nil
+}

+ 281 - 0
src/jfw/modules/subscribepay/src/util/userGroupService.go

@@ -0,0 +1,281 @@
+package util
+
+import (
+	"app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jybase/mysql"
+	"context"
+	"fmt"
+	"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
+	"log"
+	"strings"
+)
+
+const (
+	logicalOperatorNormal = 0                           // 正常运算标签
+	logicalOperatorNot    = 1                           // 非运算标签
+	tagOperatorAnd        = 1                           // 且
+	tagOperatorOr         = 2                           // 或
+	Tabledwd_d_tag        = "pub_tags.dwd_d_tag"        // 标签用户表  todo 后边调整
+	Tabledwd_mgo_position = "pub_tags.dwd_mgo_position" // base_user_id 对应的mgoid
+	FullUserTagSql        = `SELECT groupBitmapAndState(bitobj) as userIds from pub_tags.dwd_d_tag ddt WHERE  ddt.id=2017`
+	andSql                = `SELECT groupBitmapAndState(bitobj) as userIds from pub_tags.dwd_d_tag ddt WHERE  ddt.id in (%s) `
+	orSql                 = `SELECT groupBitmapOrState(bitobj) as userIds from pub_tags.dwd_d_tag ddt WHERE  ddt.id in (%s) `
+	hasAllSql             = ` bitmapHasAll( ddut.bitobj,bitmapBuild([%s])) `
+	hasAnySql             = ` bitmapHasAny( ddut.bitobj,bitmapBuild([%s])) `
+	notHasAllSql          = ` not bitmapHasAll( ddut.bitobj,bitmapBuild([%s])) `
+	notHasAnySql          = ` not bitmapHasAny( ddut.bitobj,bitmapBuild([%s])) `
+	countUserSql          = `SELECT COUNT(1) as count FROM pub_tags.dwd_d_user_tag ddut  WHERE ddut.baseUserId=%v  AND ( %s )`
+)
+
+// UserIdConstructor 用户群组标签转换
+type UserIdConstructor struct {
+	groupFilter      []int64         // 群组过滤条件
+	userGtFilter     int64           // 用户过滤条件  暂停发消息时 用的
+	userGroupTagList []*UserGroupTag // 用户群组标签列表 (整理后的)
+	baseQuerySQL     string          // 查询群组下base_user_id 的sql
+	countUserSQL     string          // 查询用户标签是否符合群组标签的sql
+	clickhouseConn   driver.Conn
+	tidbConn         *mysql.Mysql
+}
+
+type UserGroupTag struct {
+	GroupId     int64   // 群组id
+	TagOperator int64   // 群组内关系
+	NormalTag   []int64 // 正常标签
+	NotTag      []int64 // 非标签
+}
+
+func NewUserIdConstructor(groupFilter []int64, userGtFilter int64, clickHouseConn driver.Conn, tidbConn *mysql.Mysql) (u *UserIdConstructor) {
+	u = &UserIdConstructor{
+		groupFilter:      groupFilter,
+		userGtFilter:     userGtFilter,
+		userGroupTagList: []*UserGroupTag{},
+		clickhouseConn:   clickHouseConn,
+		tidbConn:         tidbConn,
+	}
+	return
+}
+
+// GetGroupTags 获取用户群组标签信息
+func (u *UserIdConstructor) getGroupTags() *[]map[string]interface{} {
+	groupIdFilter := []string{}
+	groupIdValue := []interface{}{}
+	where := ""
+	for i := 0; i < len(u.groupFilter); i++ {
+		groupIdFilter = append(groupIdFilter, "?")
+		groupIdValue = append(groupIdValue, u.groupFilter[i])
+	}
+	where = fmt.Sprintf("where ugt.group_id in (%s)", strings.Join(groupIdFilter, ","))
+	query := fmt.Sprintf(`SELECT ugt.group_id,ug.tag_operator,ugt.tag_id,ugt.logical_operator FROM convertlabsync.user_group_tag ugt left join user_group ug  on (ugt.group_id=ug.id) %s`, where)
+	rs := u.tidbConn.SelectBySql(query, groupIdValue...)
+	return rs
+}
+
+// InitTagList 处理成方便用的数组
+func (u *UserIdConstructor) InitTagList() bool {
+	rs := u.getGroupTags()
+	if rs == nil || len(*rs) == 0 {
+		return false
+	}
+	groupMap := map[int64]*UserGroupTag{}
+	for i := 0; i < len(*rs); i++ {
+		groupId := common.Int64All((*rs)[i]["group_id"])
+		tagOperator := common.Int64All((*rs)[i]["tag_operator"])
+		tagId := common.Int64All((*rs)[i]["tag_id"])
+		logicalOperator := common.IntAll((*rs)[i]["logical_operator"])
+		if _, ok := groupMap[groupId]; !ok {
+			groupMap[groupId] = &UserGroupTag{
+				GroupId:     groupId,
+				TagOperator: tagOperator,
+				NormalTag:   []int64{},
+				NotTag:      []int64{},
+			}
+		}
+		// 追加
+		switch logicalOperator {
+		case logicalOperatorNormal:
+			groupMap[groupId].NormalTag = append(groupMap[groupId].NormalTag, tagId)
+		case logicalOperatorNot:
+			groupMap[groupId].NotTag = append(groupMap[groupId].NotTag, tagId)
+		}
+	}
+	for _, v := range groupMap {
+		u.userGroupTagList = append(u.userGroupTagList, v)
+	}
+	return true
+}
+
+// 转换成sql   这需要判断
+// '正常标签'这里指不是非运算
+// toBaseQuerySQL 转换成查询baseUserId 的sql
+func (u *UserIdConstructor) toBaseQuerySQL() string {
+	sqlList := []string{} // 包含多个群组的sql
+	for i := 0; i < len(u.userGroupTagList); i++ {
+		// 拼接群组内sql
+		groupTag := u.userGroupTagList[i]
+		normalTagSQL, notTagSQL := "", ""
+		tagSql := ""
+		switch groupTag.TagOperator {
+		case tagOperatorAnd:
+			if len(groupTag.NormalTag) > 0 { // 正常标签
+				normalTagList := []string{}
+				for j := 0; j < len(groupTag.NormalTag); j++ {
+					normalTagList = append(normalTagList, fmt.Sprint(groupTag.NormalTag[j]))
+				}
+				normalTagSQL = fmt.Sprintf(andSql, strings.Join(normalTagList, ","))
+			}
+			if len(groupTag.NotTag) > 0 { // 非标签
+				notTagList := []string{}
+				for j := 0; j < len(groupTag.NotTag); j++ {
+					notTagList = append(notTagList, fmt.Sprint(groupTag.NotTag[j]))
+				}
+				notTagSQL = fmt.Sprintf(orSql, strings.Join(notTagList, ","))
+			}
+			// 同时有:  正常标签 - 非标签
+			if normalTagSQL != "" && notTagSQL != "" {
+				tagSql = fmt.Sprintf("SELECT  bitmapAndnot((%s),(%s)) as userIds", normalTagSQL, notTagSQL)
+			} else if normalTagSQL != "" {
+				// 只有正常标签 : 正常标签
+				tagSql = normalTagSQL
+			} else if notTagSQL != "" {
+				// 只有非标签 :   全量标签-非标签
+				tagSql = fmt.Sprintf("SELECT  bitmapAndnot((%s),(%s)) as userIds", FullUserTagSql, notTagSQL)
+			}
+
+		case tagOperatorOr:
+			if len(groupTag.NormalTag) > 0 { // 正常标签
+				normalTagList := []string{}
+				for j := 0; j < len(groupTag.NormalTag); j++ {
+					normalTagList = append(normalTagList, fmt.Sprint(groupTag.NormalTag[j]))
+				}
+				normalTagSQL = fmt.Sprintf(orSql, strings.Join(normalTagList, ","))
+			}
+			if len(groupTag.NotTag) > 0 { // 非标签
+				notTagList := []string{}
+				for j := 0; j < len(groupTag.NotTag); j++ {
+					notTagList = append(notTagList, fmt.Sprint(groupTag.NotTag[j]))
+				}
+				notTagSQL = fmt.Sprintf(andSql, strings.Join(notTagList, ","))
+			}
+			// 同时有:  正常标签 ∪ (U-(B∩C∩D....))  U:全量标签 B、C、D... 非标签
+			if normalTagSQL != "" && notTagSQL != "" {
+				tmpNotTagSql := fmt.Sprintf("SELECT  bitmapAndnot((%s),(%s)) as userIds", FullUserTagSql, notTagSQL)
+				tagSql = fmt.Sprintf("SELECT  bitmapOr((%s),(%s)) as userIds", FullUserTagSql, tmpNotTagSql)
+			} else if normalTagSQL != "" {
+				// 只有正常标签
+				tagSql = normalTagSQL
+			} else if notTagSQL != "" {
+				// 只有非标签:    U-(B∩C∩D....)  U:全量标签 B、C、D... 非标签
+				tagSql = fmt.Sprintf("SELECT  bitmapAndnot((%s),(%s)) as userIds", FullUserTagSql, notTagSQL)
+			}
+		}
+		sqlList = append(sqlList, tagSql)
+	}
+	// 如果用户有过滤
+	if u.userGtFilter > 0 {
+		u.baseQuerySQL = fmt.Sprintf("SELECT  arrayFilter(x -> x >%v,bitmapToArray( groupBitmapOrState(userIds))) as userIds from (%s)", u.userGtFilter, strings.Join(sqlList, " UNION    DISTINCT  "))
+	} else {
+		u.baseQuerySQL = fmt.Sprintf("SELECT  bitmapToArray( groupBitmapOrState(  userIds)) as userIds  from (%s)", strings.Join(sqlList, " UNION    DISTINCT  "))
+	}
+	log.Println("baseQuerySQL:", u.baseQuerySQL)
+	return u.baseQuerySQL
+}
+
+// 从数据库查询
+func (u *UserIdConstructor) QueryBaseUserIdList() (userList []int64) {
+	if !u.InitTagList() {
+		return []int64{}
+	}
+	rows := u.clickhouseConn.QueryRow(context.Background(), u.toBaseQuerySQL())
+	if err := rows.Scan(&userList); err != nil {
+		log.Println("QueryBaseUserIdList err:", err)
+		return
+	}
+	return userList
+}
+
+// 判断活动群组id 和 用户身上的的标签是否匹配
+// 分组之间用 or 连接
+// 分组内
+// 且: 正常标签: bitmapHasAll  and  (not bitmapHasAny )
+// 或:bitmapHasAny  or (not bitmapHasAny())
+func (u *UserIdConstructor) toCountUserSQL(baseUserId int64) string {
+	sqlList := []string{} // 包含多个群组的sql
+	for i := 0; i < len(u.userGroupTagList); i++ {
+		// 拼接群组内sql
+		groupTag := u.userGroupTagList[i]
+		normalTagSQL, notTagSQL := "", ""
+		tagSql := ""
+		switch groupTag.TagOperator {
+		case tagOperatorAnd:
+			if len(groupTag.NormalTag) > 0 { // 正常标签
+				normalTagList := []string{}
+				for j := 0; j < len(groupTag.NormalTag); j++ {
+					normalTagList = append(normalTagList, fmt.Sprintf("toUInt64(%v)", groupTag.NormalTag[j]))
+				}
+				normalTagSQL = fmt.Sprintf(hasAllSql, strings.Join(normalTagList, ","))
+			}
+			if len(groupTag.NotTag) > 0 { // 非标签
+				notTagList := []string{}
+				for j := 0; j < len(groupTag.NotTag); j++ {
+					notTagList = append(notTagList, fmt.Sprintf("toUInt64(%v)", groupTag.NotTag[j]))
+				}
+				notTagSQL = fmt.Sprintf(notHasAnySql, strings.Join(notTagList, ","))
+			}
+			// 同时有:  正常标签 and 非标签
+			if normalTagSQL != "" && notTagSQL != "" {
+				tagSql = fmt.Sprintf("(%s and %s)", normalTagSQL, notTagSQL)
+			} else if normalTagSQL != "" {
+				// 只有正常标签 : 正常标签
+				tagSql = fmt.Sprintf("(%s)", normalTagSQL)
+			} else if notTagSQL != "" {
+				// 只有非标签 :
+				tagSql = fmt.Sprintf("(%s)", notTagSQL)
+			}
+
+		case tagOperatorOr:
+			if len(groupTag.NormalTag) > 0 { // 正常标签
+				normalTagList := []string{}
+				for j := 0; j < len(groupTag.NormalTag); j++ {
+					normalTagList = append(normalTagList, fmt.Sprintf("toUInt64(%v)", groupTag.NormalTag[j]))
+				}
+				normalTagSQL = fmt.Sprintf(hasAnySql, strings.Join(normalTagList, ","))
+			}
+			if len(groupTag.NotTag) > 0 { // 非标签
+				notTagList := []string{}
+				for j := 0; j < len(groupTag.NotTag); j++ {
+					notTagList = append(notTagList, fmt.Sprintf("toUInt64(%v)", groupTag.NotTag[j]))
+				}
+				notTagSQL = fmt.Sprintf(notHasAllSql, strings.Join(notTagList, ","))
+			}
+			// 同时有:  正常标签  or 非标签
+			if normalTagSQL != "" && notTagSQL != "" {
+				tagSql = fmt.Sprintf(" (%s or %s) ", normalTagSQL, notTagSQL)
+			} else if normalTagSQL != "" {
+				// 只有正常标签 : 正常标签
+				tagSql = fmt.Sprintf(" (%s) ", normalTagSQL)
+			} else if notTagSQL != "" {
+				// 只有非标签 :
+				tagSql = fmt.Sprintf(" (%s) ", notTagSQL)
+			}
+		}
+		sqlList = append(sqlList, tagSql)
+	}
+	u.countUserSQL = fmt.Sprintf(countUserSql, baseUserId, strings.Join(sqlList, " or "))
+	log.Println("toCountUserSQL:", u.countUserSQL)
+	return u.countUserSQL
+}
+
+// 从数据库查询
+func (u *UserIdConstructor) CountUser(baseUserId int64) (count uint64) {
+	if !u.InitTagList() {
+		log.Println("InitTagList 异常")
+		return 0
+	}
+	rows := u.clickhouseConn.QueryRow(context.Background(), u.toCountUserSQL(baseUserId))
+	if err := rows.Scan(&count); err != nil {
+		log.Println("QueryBaseUserIdList err:", err)
+		return
+	}
+	return count
+}

+ 100 - 0
src/web/staticres/common-module/active/exchange/exchange-success.css

@@ -0,0 +1,100 @@
+
+#main-app{
+  background-color: #F5F6F7;
+  padding: 0;
+}
+
+.success-title {
+  font-size: 0.4rem;
+  line-height: 0.52rem;
+  color: #171826;
+  text-align: center;
+  margin-top: 0.62rem;
+  margin-bottom: 0.32rem;
+}
+
+.success-desc {
+  color: #171826;
+  font-size: 0.3rem;
+  line-height: 0.44rem;
+  text-align: center;
+}
+
+.success-header {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  background: #fff;
+  margin-bottom: 0.16rem;
+  padding: 0.64rem 0.56rem 0.32rem 0.32rem;
+}
+.success-header img {
+  width: 2.52rem;
+}
+.success-main {
+  background: #fff;
+  padding: 0.56rem   0.32rem  0.64rem 0.32rem;
+}
+
+.success-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin-top: 0.56rem;
+}
+.success-item .success-item-title {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  min-width: 4.52rem;
+  height: 0.64rem;
+  font-size: 0.3rem;
+  line-height: 0.64rem;
+  border: 1px solid rgba(135, 223, 234, 1);
+  border-radius: 0.32rem;
+  padding-right: 0.4rem;
+  color: #2cb7ca;
+}
+.success-item .success-item-title-before {
+  display: inline-block;
+  height: 0.64rem;
+  line-height: 0.64rem;
+  padding: 0 0.24rem;
+  color: #fff;
+  background: #2cb7ca;
+  border-radius: 0.32rem;
+  margin-right: 0.32rem;
+}
+.success-item .success-item-desc-after {
+  margin-top: 0.08rem;
+  color: #5f5e64;
+  font-size: 0.28rem;
+  line-height: 0.4rem;
+}
+.success-item .success-item-desc-pc .pc-link {
+  color: #5f5e64;
+  margin-right: 0.32rem;
+}
+.success-item .success-item-desc-pc {
+  margin-top: 0.24rem;
+  font-size: 0.28rem;
+  line-height: 0.4rem;
+}
+.success-item .success-item-app {
+  margin-top: 0.24rem;
+  font-size: 0.28rem;
+  line-height: 0.4rem;
+}
+.success-item .success-item-desc {
+  margin-top: 0.24rem;
+  color: rgba(23, 24, 38, 1);
+  font-size: 0.28rem;
+  line-height: 0.4rem;
+
+}
+.success-item .success-item-img {
+  width: 2.62rem;
+  height: 2.62rem;
+  margin-top: 0.32rem;
+}

+ 62 - 0
src/web/staticres/common-module/active/exchange/exchange-success.js

@@ -0,0 +1,62 @@
+var sVm = new Vue({
+  el: '#main-app',
+  delimiters: ['${', '}'],
+  mixins: [shareMixin],
+  data: {
+    titleBefore: ['一', '二', '三', '四', '五', '六'],
+    info: {
+      success: true,
+      success_desc1:'',
+      success_desc2:'',
+      list: [
+        {
+          label: '剑鱼标讯公众号',
+          img: 'https://www.jianyu360.cn/front/downloadJyApp/qr?page=pc_bottom&source=pc_scan',
+          desc: '长按识别进入公众号',
+          desc_after: '如未绑定手机号,绑定兑换手机号即可'
+        },
+        {
+          label: '剑鱼标讯网小程序',
+          img: 'https://www.jianyu360.cn/front/downloadJyApp/qr?page=pc_bottom&source=pc_scan',
+          desc: '长按识别进入小程序',
+        },
+        {
+          label: '剑鱼标讯App',
+          img: 'https://www.jianyu360.cn/front/downloadJyApp/qr?page=pc_bottom&source=pc_scan',
+          app: true
+        },
+        {
+          label: '剑鱼标讯PC端',
+          img: 'https://www.jianyu360.cn/front/downloadJyApp/qr?page=pc_bottom&source=pc_scan',
+          desc: '请您复制剑鱼标讯链接在PC端浏览器打开页面,在PC端使用兑换的权益。',
+          pc: true
+        }
+      ]
+    },
+  },
+  mounted: function () {
+    this.doLoad()
+  },
+  methods: {
+    doLoad () {
+      const info = JSON.parse(sessionStorage.getItem('ex-success-info') || '{}')
+      if (info.success) {
+        this.info = info
+      } else {
+        window.close()
+      }
+    },
+    doCopy () {
+      this.copyText('https://www.jianyu360.cn/')
+      this.$toast('复制成功')
+    },
+    copyText: function (text) {
+      const input = document.createElement('textarea') // js创建一个input输入框
+      input.value = text // 将需要复制的文本赋值到创建的input输入框中
+      document.body.appendChild(input) // 将输入框暂时创建到实例里面
+      input.select() // 选中输入框中的内容
+      document.execCommand('copy') // 执行复制操作
+      document.body.removeChild(input) // 最后删除实例中临时创建的input输入框,完成复制操作
+    },
+  }
+})

+ 36 - 13
src/web/staticres/common-module/active/exchange/exchange.js

@@ -40,7 +40,7 @@ var exchangeV = new Vue({
     },
     swiperrightShow(){
       return this.swiperIndex + 1 < this.sc_qrlist.length && this.sc_qrlist.length > 1
-      
+
     }
   },
   created: function () {
@@ -55,8 +55,8 @@ var exchangeV = new Vue({
         clearTimeout(this.imgcodeTimer)
       }
       this.imgcodeTimer = setTimeout(() => {
-        this.imgcodeUrl = this.firstUrl + '/jypay/equityCode/captcha?v=' + new Date().getTime()  
-      },300)  
+        this.imgcodeUrl = this.firstUrl + '/jypay/equityCode/captcha?v=' + new Date().getTime()
+      },300)
     },
     finishDownTime () {
       this.isRunDownTime = false
@@ -124,13 +124,32 @@ var exchangeV = new Vue({
             if(res.data.status === 0){
               _this.$toast('提交失败,请重试')
             }else if(res.data.status === 1){
-              _this.swiperIndex = 0
-              _this.dialog.success_desc1 ='您已经成功兑换' + res.data.name + '权益。'
-              _this.dialog.success_desc2 ='您需要前往' + res.data.industry + '相关小程序(行业相关)上去使用。'
-              _this.sc_qrlist = res.data.list
-              _this.dialog.show_success = true  
-              _this.form.identCode = ''
-              _this.form.imgCode = ''
+              if (res.data.isxcx === 1) {
+                _this.swiperIndex = 0
+                _this.dialog.success_desc1 ='您已经成功兑换 <span class="highlight-text">' + res.data.name + '</span> 权益。'
+                _this.dialog.success_desc2 ='您需要前往' + res.data.industry + '相关小程序(行业相关)上去使用。'
+                _this.sc_qrlist = res.data.list
+                _this.dialog.show_success = true
+                _this.form.identCode = ''
+                _this.form.imgCode = ''
+              } else {
+                sessionStorage.setItem('ex-success-info', JSON.stringify({
+                  success: true,
+                  success_desc1: '您已经成功兑换 <span class="highlight-text">' + res.data.name + '</span> 权益',
+                  success_desc2:'兑换所用手机号:' + _this.form.phone,
+                  list: res.data.list.map(function (v) {
+                    return {
+                      label: v.name,
+                      img: v.qrcode || '',
+                      desc: v.text || '',
+                      desc_after: v.remark || '',
+                      app: (v.name || '').toLocaleLowerCase().indexOf('app') !== -1,
+                      pc: Boolean(v.url || false)
+                    }
+                  })
+                }))
+                location.href = './exchange-success'
+              }
             }else if(res.data.status === -1){
               _this.errorTip = '短信验证码错误'
             }else if(res.data.status === -2){
@@ -145,9 +164,13 @@ var exchangeV = new Vue({
               _this.errorTip = '兑换码已过期'
             }else if(res.data.status === -7){
               _this.dialog.kf_qr = res.data.kfcode
-              _this.dialog.show_kf = true 
+              _this.dialog.show_kf = true
               _this.form.identCode = ''
               _this.form.imgCode = ''
+            }else if(res.data.status === -8){
+              _this.errorTip = '手机号用户当前不能兑换'
+            }else if(res.data.status === -9){
+              _this.errorTip = '手机号用户已无剩余可兑换次数'
             }
           } else {
             _this.$toast(res.error_msg || '请稍后重试')
@@ -167,7 +190,7 @@ var exchangeV = new Vue({
     swipeTo(type){
       if(type === 'left'){
         if (this.swiperIndex === 0) {
-          return 
+          return
         }
         this.$refs.success_swipe.swipeTo(this.swiperIndex - 1)
       }else{
@@ -181,4 +204,4 @@ var exchangeV = new Vue({
       return /^1[3-9]\d{9}$/.test(val)
     },
   }
-})
+})

BIN
src/web/staticres/common-module/active/exchange/img/new-header.png


+ 2 - 2
src/web/templates/frontRouter/wx/activity/free/exchange.html

@@ -48,7 +48,7 @@
             maxlength="4"
             type="number"
             >
-            
+
             <template v-slot:button>
               <div class="img-code-box"  @click="getImgCode"><img :src="imgcodeUrl" alt="验证码"></div>
             </template>
@@ -107,7 +107,7 @@
           <div class="success-head">
             <div class="success-title">兑换成功</div>
           </div>
-          <p class="success-desc">${dialog.success_desc1}</p>
+          <p class="success-desc" v-html="dialog.success_desc1"></p>
           <p class="success-desc mt-12">${dialog.success_desc2}</p>
           <div class="swiper-box">
             <van-swipe :loop="false" @change="swiperChange" :show-indicators="false" ref="success_swipe">