Эх сурвалжийг харах

wip:定制化报告开发

wangkaiyue 2 жил өмнө
parent
commit
8f42f5ac4c

+ 10 - 2
db.json

@@ -23,7 +23,7 @@
 	},
     "redis": {
     	"main":{
-			"address": "other=123.56.53.97:1713,session=123.56.53.97:1713"
+			"address": "newother=123.56.53.97:1713,session=123.56.53.97:1713"
 		}
     },
     "mysql": {
@@ -34,6 +34,14 @@
 	        "passWord": "Topnet123",
 			"maxOpenConns": 5,
 			"maxIdleConns": 5
-	    }
+	    },
+		"active": {
+			"dbName": "useranaly",
+			"address": "192.168.3.11:3366",
+			"userName": "root",
+			"passWord": "Topnet123",
+			"maxOpenConns": 5,
+			"maxIdleConns": 5
+		}
     }
 }

+ 72 - 0
entity/activeUsers/activeUsers.go

@@ -0,0 +1,72 @@
+package activeUsers
+
+import (
+	"leadGeneration/entity/power"
+	"leadGeneration/public"
+	"time"
+)
+
+/*
+⚠️注意
+user_countbyhour 表只有一个月的数据
+*/
+
+// GetWeekActiveFreeUsers 获取周活无权限用户
+func GetWeekActiveFreeUsers() map[string]int {
+	now := time.Now()
+	_, start_m, start_d := now.Date()
+	_, end_m, end_d := now.AddDate(0, 0, -7).Date()
+
+	var sql string
+	var param []interface{}
+	if start_m != end_m {
+		//跨月
+		sql = "SELECT DISTINCT(user_mongoid) FROM user_countbyhour WHERE day >= ? OR day < ?"
+		param = []interface{}{start_d, end_d}
+	} else {
+		//非跨月
+		sql = "SELECT DISTINCT(user_mongoid) FROM user_countbyhour WHERE day >= ? AND day < ?"
+		param = []interface{}{start_d, end_d}
+	}
+	res := public.ActiveMysql.SelectBySql(sql, param...)
+	rData := map[string]int{}
+	if res != nil && len(*res) > 0 {
+		for _, v := range *res {
+			if userId, _ := v["user_mongoid"].(string); userId != "" {
+				if !power.HasPower(userId) {
+					rData[userId] = 0
+				}
+			}
+		}
+	}
+	return rData
+}
+
+// GetMonthActiveFreeUsers 获取月活用户
+// 有订阅或有搜索词的周活用户+最近7天注册的新用户无条件生
+func GetMonthActiveFreeUsers() map[string]int {
+	rData := map[string]int{}
+	//表数据为近一个月,所以直接查询全部
+	res := public.ActiveMysql.SelectBySql("SELECT user_mongoid,sum(search) AS total FROM user_countbyhour group by user_mongoid")
+	for _, v := range *res {
+		userId, _ := v["user_mongoid"].(string)
+		if userId == "" {
+			continue
+		}
+
+		//非会员
+		if hasPower := power.HasPower(userId); hasPower {
+			continue
+		}
+
+		//无搜索记录、无关键词
+		if searchTotal, _ := v["total"].(int); searchTotal == 0 {
+			//无订阅词
+			if !power.HasSubscribe(userId) {
+				continue
+			}
+		}
+		rData[userId] = 0
+	}
+	return rData
+}

+ 67 - 7
entity/mananger/aheadManager.go

@@ -1,33 +1,93 @@
 package mananger
 
 import (
+	"app.yhyue.com/moapp/jybase/redis"
+	"fmt"
 	"github.com/robfig/cron/v3"
+	"leadGeneration/entity/search"
+	"leadGeneration/public"
 	"leadGeneration/vars"
+	"log"
+	"sync"
+	"time"
 )
 
-type aheadManager struct {
-	Conf vars.AheadConfig
+//AheadManager 超前项目管理
+type AheadManager struct {
+	Conf      vars.AheadConfig
+	UserGroup map[string]int
+	sync.RWMutex
 }
 
+const (
+	//缓存
+	AheadCacheDb                  = "newother"
+	AheadRequestFrequencyCacheKey = "leadGeneration_AheadRequest_%d_%s"
+	AheadRequestTimesLong         = 60 * 60 * 24 //缓存一天
+)
+
 // InitAheadManager 初始化
-func InitAheadManager(conf vars.AheadConfig) *aheadManager {
-	manager := &aheadManager{
-		conf,
+func InitAheadManager(conf vars.AheadConfig) *AheadManager {
+	manager := &AheadManager{
+		Conf:      conf,
+		UserGroup: make(map[string]int),
 	}
 	go manager.ScheduledTasks()
 	return manager
 }
 
-func (this *aheadManager) ScheduledTasks() {
+// GetData 获取查询数据
+func (this *AheadManager) GetData(userId, keyWords string, isNew bool) map[string]interface{} {
+	if !(isNew || this.checkGroupUser(userId)) {
+		return nil
+	}
+	//校验每日请求次数
+	cacheKey := fmt.Sprintf(AheadRequestFrequencyCacheKey, time.Now().Format(public.TimeFormat), userId)
+	if this.Conf.DailyTimes > redis.GetInt(AheadCacheDb, fmt.Sprintf(AheadRequestFrequencyCacheKey, time.Now().Format(public.TimeFormat))) {
+		return nil
+	}
+	//查询数据
+	rDate, err := search.AdvancedProject(userId, keyWords)
+	if err != nil {
+		log.Printf("[ERROR]AheadManager %s %s GetData Error %v\n", userId, keyWords, err)
+		return nil
+	}
+	//累计请求计数
+	if rDate != nil && len(rDate) > 0 {
+		if num := redis.Incr(AheadCacheDb, cacheKey); num == 1 {
+			_ = redis.SetExpire(AheadCacheDb, cacheKey, AheadRequestTimesLong)
+		}
+	}
+	return rDate
+}
+
+//CheckGroupUser 校验用户是否有资格展示
+func (this *AheadManager) checkGroupUser(userId string) (exists bool) {
+	this.RLock()
+	defer this.RUnlock()
+	_, exists = this.UserGroup[userId]
+	return
+}
+
+//ScheduledTasks 定时任务
+func (this *AheadManager) ScheduledTasks() {
 	if this.Conf.UpdateCron != "" {
 		// 给对象增加定时任务
 		if _, err := cron.New().AddFunc(this.Conf.UpdateCron, this.UpdateUserGroupJob); err != nil {
 			panic(err)
 		}
 	}
+	//首次运行圈选用户
+	go this.UpdateUserGroupJob()
 }
 
 //UpdateUserGroupJob 更新用户群组
-func (this *aheadManager) UpdateUserGroupJob() {
+func (this *AheadManager) UpdateUserGroupJob() {
+	newGroup := map[string]int{}
+	//查询上批次活跃用户
+	//查询月活用户
 
+	this.Lock()
+	defer this.Unlock()
+	this.UserGroup = newGroup
 }

+ 164 - 0
entity/mananger/customManager.go

@@ -1 +1,165 @@
 package mananger
+
+import (
+	"app.yhyue.com/moapp/jybase/redis"
+	"encoding/json"
+	"fmt"
+	"github.com/robfig/cron/v3"
+	"leadGeneration/entity/activeUsers"
+	"leadGeneration/entity/power"
+	"leadGeneration/entity/search"
+	"leadGeneration/public"
+	"leadGeneration/vars"
+	"log"
+	"sync"
+	"time"
+)
+
+const (
+	customCacheDb           = "newother"
+	customDataCacheKey      = "leadGeneration_customData_%s"
+	customDataCacheTimeLong = 60 * 60 * 24 * 31
+)
+
+//CustomManager 定制化报告管理
+type CustomManager struct {
+	Conf      vars.CustomConfig
+	UserGroup map[string]int // 月活用户
+	BatchFlag string         // 批次标识
+	sync.RWMutex
+}
+
+type SearchEntity struct {
+	UserId string
+	Value  string
+}
+
+var (
+	newRegisterUserQueue = make(chan *SearchEntity) //新用户队列
+	activityUserQueue    = make(chan *SearchEntity) //活跃用户队列
+)
+
+// InitCustomManager 初始化
+func InitCustomManager(conf vars.CustomConfig) *CustomManager {
+	manager := &CustomManager{
+		Conf:      conf,
+		UserGroup: make(map[string]int),
+	}
+	go manager.ScheduledTasks()
+	go manager.DoSearch()
+	return manager
+}
+
+// GetData 获取查询数据
+func (this *CustomManager) GetData(userId, keyWords string, isNew bool) map[string]interface{} {
+	if !(isNew || this.checkActivityUser(userId)) {
+		return nil
+	}
+	//查询是否是付费用户
+	if power.HasPower(userId) {
+		return nil
+	}
+	//查询数据
+	if bytes, err := redis.GetBytes(customCacheDb, fmt.Sprintf(customDataCacheKey, userId)); err == nil && bytes != nil {
+		rData := map[string]interface{}{}
+		if err := json.Unmarshal(*bytes, &rData); err != nil {
+			log.Printf("[ERROR]CustomManager %s GetData Error %v \n", userId, err)
+			return nil
+		}
+		return rData
+	} else if isNew {
+		//加入优先查询队列
+		go func() {
+			newRegisterUserQueue <- &SearchEntity{
+				UserId: userId,
+				Value:  keyWords,
+			}
+		}()
+	}
+	return nil
+}
+
+//ScheduledTasks 定时任务
+func (this *CustomManager) ScheduledTasks() {
+	if this.Conf.UpdateCron != "" {
+		// 给对象增加定时任务
+		if _, err := cron.New().AddFunc(this.Conf.UpdateCron, this.UpdateUserGroupJob); err != nil {
+			panic(err)
+		}
+	}
+	//首次运行圈选用户
+	this.UpdateUserGroupJob()
+}
+
+//UpdateUserGroupJob 更新用户群组
+func (this *CustomManager) UpdateUserGroupJob() {
+	//查询月活用户
+	userMap := activeUsers.GetMonthActiveFreeUsers()
+	this.Lock()
+	defer this.Unlock()
+	this.UserGroup = userMap
+
+	//更新批次标识
+	batchFlag := time.Now().Format(public.Date_Full_Layout)
+	this.BatchFlag = batchFlag
+
+	userArr := make([]string, len(this.UserGroup), 0)
+	for userId, _ := range this.UserGroup {
+		userArr = append(userArr, userId)
+	}
+	go this.activityUserQueue(batchFlag, []string{})
+}
+
+//activityUserQueue 活跃用户查询队列
+func (this *CustomManager) activityUserQueue(batchFlag string, userIds []string) {
+	for i, userId := range userIds {
+		if i%100 == 0 {
+			log.Printf("[INFO]CustomManager Batch %s Now(%d/%d)\n", batchFlag, len(userIds), i)
+		}
+		//当批次更新时,上批次停止
+		if this.BatchFlag != batchFlag {
+			log.Printf("[INFO]CustomManager Batch %s Is END At (%d/%d) \n", batchFlag, i, len(userIds))
+			break
+		}
+		activityUserQueue <- &SearchEntity{
+			UserId: userId,
+		}
+	}
+	log.Printf("[INFO]CustomManager Batch %s Is Finished !!!\n", batchFlag)
+}
+
+//DoSearch 定制化分析报告查询队列
+func (this *CustomManager) DoSearch() {
+	for {
+		var obj *SearchEntity
+		select { //优先级 newRegisterUserQueue > activityUserQueue
+		case obj = <-newRegisterUserQueue:
+		default:
+			select {
+			case obj = <-newRegisterUserQueue:
+			case obj = <-activityUserQueue:
+			}
+		}
+		//查询结果处理
+		data, err := search.PotentialCustomizeAnalysis(obj.UserId, obj.Value)
+		if err != nil {
+			log.Printf("[ERROR]CustomManager %s DoSearch %s Error %v\n", obj.UserId, obj.Value, err)
+		}
+		if data != nil || len(data) == 0 {
+			log.Printf("[ERROR]CustomManager %s DoSearch %s Error %v\n", obj.UserId, obj.Value, err)
+			continue
+		}
+		//缓存结果
+		if bytes, err := json.Marshal(data); err == nil && bytes != nil {
+			_ = redis.PutBytes(customCacheDb, fmt.Sprintf(customDataCacheKey, obj.UserId), &bytes, customDataCacheTimeLong)
+		}
+	}
+}
+
+//checkActivityUser 校验用户是否是活跃用户
+func (this *CustomManager) checkActivityUser(userId string) (exists bool) {
+	this.RLock()
+	defer this.RUnlock()
+	_, exists = this.UserGroup[userId]
+	return
+}

+ 72 - 0
entity/power/power.go

@@ -0,0 +1,72 @@
+package power
+
+import (
+	"app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jybase/redis"
+	"encoding/json"
+	"fmt"
+	"leadGeneration/public"
+)
+
+const (
+	baseInfoCacheDb = "newother"
+	powerCacheKey   = "bigmember_power_3_%s"
+)
+
+// BigVipBaseMsg 大会员状态redis缓存
+type bigVipBaseMsg struct {
+	Status         int          `json:"status"`          //大会员状态
+	Used           bool         `json:"used"`            //是否首次使用大会员
+	PowerMap       map[int]bool `json:"p_map"`           //权限列表
+	HasTrial       bool         `json:"has_trial"`       //是否试用过
+	Customers      int          `json:"customers"`       //可关注客户数量
+	VipStatus      int          `json:"vip_status"`      //超级订阅状态
+	EntnicheStatus int          `json:"entniche_status"` //商机管理状态
+	BaseUserId     int          `json:"base_user_id"`    //用户base_user_id
+}
+
+//HasPower 是否有定制化市场报告权限
+// bigMember.power=10
+func HasPower(userId string) bool {
+	if userId == "" {
+		return false
+	}
+	//从缓存中读取会员信息
+	vipStatus := func() *bigVipBaseMsg {
+		userPower := bigVipBaseMsg{}
+		userPower.PowerMap = make(map[int]bool)
+		cacheKey := fmt.Sprintf(powerCacheKey, userId)
+		if bytes, err := redis.GetBytes(baseInfoCacheDb, cacheKey); err == nil && bytes != nil {
+			if err := json.Unmarshal(*bytes, &userPower); err == nil {
+				return &userPower
+			}
+		}
+		return nil
+	}()
+	if vipStatus != nil {
+		return vipStatus.PowerMap[10]
+	}
+	//查询mysql大会员权益表
+	return public.MainMysql.CountBySql("SELECT count(id) FROM bigmember_service_user WHERE s_userid=? AND s_serviceid =10 AND i_status =0", userId) > 0
+}
+
+// HasSubscribe 是否有订阅词
+func HasSubscribe(userId string) bool {
+	uData, _ := public.MQFW.FindById("user", userId, `"i_vip_status":1,"o_jy":1,"o_vipjy":1`)
+	if uData != nil && len(*uData) > 0 {
+		if common.IntAll((*uData)["i_vip_status"]) > 0 {
+			if subObj, ok := (*uData)["o_vipjy"]; ok {
+				if m, ok2 := subObj.(map[string]interface{}); ok2 && len(m) > 0 {
+					return true
+				}
+			}
+		} else {
+			if subObj, ok := (*uData)["o_jy"]; ok {
+				if m, ok2 := subObj.(map[string]interface{}); ok2 && len(m) > 0 {
+					return true
+				}
+			}
+		}
+	}
+	return false
+}

+ 20 - 7
public/db.go

@@ -10,10 +10,11 @@ import (
 )
 
 var (
-	DbConf    *dbConf
-	MainMysql *mysql.Mysql
-	MQFW      m.MongodbSim
-	Mgo_Log   m.MongodbSim
+	DbConf      *dbConf
+	MainMysql   *mysql.Mysql
+	ActiveMysql *mysql.Mysql
+	MQFW        m.MongodbSim
+	Mgo_Log     m.MongodbSim
 )
 
 type dbConf struct {
@@ -31,9 +32,8 @@ type dbConf struct {
 		Main *redisConf
 	}
 	Mysql struct {
-		Main       *mysqlConf //大会员&订单
-		Push       *mysqlConf //推送
-		MemberPush *mysqlConf //推送
+		Main   *mysqlConf //大会员&订单
+		Active *mysqlConf //活跃用户
 	}
 }
 type mgoConf struct {
@@ -112,5 +112,18 @@ func init() {
 			MainMysql.Init()
 		}
 
+		if DbConf.Mysql.Active != nil {
+			log.Println("初始化 active mysql")
+			ActiveMysql = &mysql.Mysql{
+				Address:      DbConf.Mysql.Main.Address,
+				UserName:     DbConf.Mysql.Main.UserName,
+				PassWord:     DbConf.Mysql.Main.PassWord,
+				DBName:       DbConf.Mysql.Main.DbName,
+				MaxOpenConns: DbConf.Mysql.Main.MaxOpenConns,
+				MaxIdleConns: DbConf.Mysql.Main.MaxIdleConns,
+			}
+			ActiveMysql.Init()
+		}
+
 	}
 }

+ 6 - 0
public/vars.go

@@ -0,0 +1,6 @@
+package public
+
+const (
+	TimeFormat       = "20060102"
+	Date_Full_Layout = "200601021504"
+)

+ 16 - 7
services/userMerge.go → services/action.go

@@ -5,6 +5,7 @@ import (
 	qutil "app.yhyue.com/moapp/jybase/common"
 	"app.yhyue.com/moapp/jybase/go-xweb/xweb"
 	"app.yhyue.com/moapp/jybase/mongodb"
+	"leadGeneration/vars"
 	"log"
 	"time"
 )
@@ -17,21 +18,28 @@ type LeadGeneration struct {
 }
 
 //GetDate 查询超前项目及定制化报告数据
+// dType  1:订阅页  0:搜索页
 func (this *LeadGeneration) GetDate() {
 	userId := qutil.ObjToString(this.GetSession("userId"))
 	rData, errMsg := func() (interface{}, error) {
 		//根据判断用户是否是7天内注册的新用户
 		isNewUser := mongodb.StringTOBsonId("userId").Timestamp().After(time.Now().AddDate(0, 0, -7))
 		keyWords := this.GetString("keyWords")
+		t, _ := this.GetInt("dType")
 		rData := map[string]interface{}{}
-		//获取超前项目
-		if aheadData := getAheadData(userId, keyWords, isNewUser); aheadData != nil && len(aheadData) > 0 {
-			rData["ahead"] = aheadData
-		}
-		//获取定制化报告数据
-		if customData := getCustomData(userId, keyWords, isNewUser); customData != nil && len(customData) > 0 {
-			rData["custom"] = customData
+
+		if !(t == 0 && keyWords == "") {
+			//获取超前项目
+			if aheadData := vars.JyAheadManager.GetData(userId, keyWords, isNewUser); aheadData != nil && len(aheadData) > 0 {
+				rData["ahead"] = aheadData
+			}
+
+			//获取定制化报告数据
+			if customData := getCustomData(userId, keyWords, isNewUser); customData != nil && len(customData) > 0 {
+				rData["custom"] = customData
+			}
 		}
+
 		return rData, nil
 	}()
 	if errMsg != nil {
@@ -53,6 +61,7 @@ func (this *LeadGeneration) ClickRecord() {
 }
 
 func getAheadData(userId, keyWords string, isNew bool) map[string]interface{} {
+
 	return nil
 }
 

+ 9 - 0
vars/config.go

@@ -2,6 +2,7 @@ package vars
 
 import (
 	qutil "app.yhyue.com/moapp/jybase/common"
+	"leadGeneration/entity/mananger"
 )
 
 var Config *config
@@ -24,7 +25,15 @@ type CustomConfig struct {
 	UpdateCron string `json:"updateCron"` //更新周活用户
 }
 
+var (
+	JyAheadManager  = &mananger.AheadManager{}
+	JyCustomManager = &mananger.CustomManager{}
+)
+
 func init() {
 	//程序配置文件
 	qutil.ReadConfig(&Config)
+	JyAheadManager = mananger.InitAheadManager(Config.Ahead)
+	JyCustomManager = mananger.InitCustomManager(Config.Custom)
+
 }