Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"bulletin_test.go",
"dao_test.go",
"guest_test.go",
"item_test.go",
"promotion_test.go",
"screen_test.go",
"seat_test.go",
"stock_test.go",
"tag_test.go",
"ticket_test.go",
"venue_test.go",
"version_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/service/openplatform/ticket-item/api/grpc/v1:go_default_library",
"//app/service/openplatform/ticket-item/conf:go_default_library",
"//app/service/openplatform/ticket-item/model:go_default_library",
"//library/log:go_default_library",
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"banner.go",
"bulletin.go",
"dao.cache.go",
"dao.go",
"fav.go",
"guest.go",
"item.go",
"project.go",
"promotion.go",
"screen.go",
"seat.go",
"stock.go",
"tag.go",
"ticket.go",
"venue.go",
"version.go",
"wish.go",
],
importpath = "go-common/app/service/openplatform/ticket-item/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/common/openplatform/random:go_default_library",
"//app/service/openplatform/ticket-item/api/grpc/v1:go_default_library",
"//app/service/openplatform/ticket-item/conf:go_default_library",
"//app/service/openplatform/ticket-item/model:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/elastic:go_default_library",
"//library/database/orm:go_default_library",
"//library/database/sql:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/stat/prom:go_default_library",
"//library/sync/pipeline/fanout:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/jinzhu/gorm:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,526 @@
package dao
import (
"context"
"encoding/json"
"fmt"
item "go-common/app/service/openplatform/ticket-item/api/grpc/v1"
"go-common/app/service/openplatform/ticket-item/model"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/time"
"strconv"
"strings"
xtime "time"
"github.com/jinzhu/gorm"
)
// DelBannerInfo 删除缓存所需banner信息
type DelBannerInfo struct {
Order int32
Position int32
SubPosition int32
DistrictIDs []string
}
// AddBanner 添加新投放
func (d *Dao) AddBanner(c context.Context, info *item.BannerEditRequest) (bannerID int64, verID uint64, err error) {
// 开启事务
tx := d.db.Begin()
//创建banner信息
banner := model.Banner{
PubStart: time.Time(info.PubStart),
PubEnd: time.Time(info.PubEnd),
Name: info.Name,
Pic: info.Pic,
URL: info.Url,
From: info.From,
TargetUser: info.TargetUser,
}
if err = tx.Create(&banner).Error; err != nil {
log.Error("创建banner失败:%s", err)
tx.Rollback()
return 0, 0, err
}
bannerID = banner.ID
if bannerID == 0 {
log.Error("bannerID为0")
tx.Rollback()
return 0, 0, err
}
var mainInfo []byte
mainInfo, err = json.Marshal(info)
if err != nil {
log.Error("jsonMarshal版本详情失败:%s", err)
tx.Rollback()
return bannerID, 0, err
}
//生成新审核版本ver,verExt
str := fmt.Sprintf("%d%d%.2d", info.Position, info.SubPosition, info.Order)
forInt, _ := strconv.ParseInt(str, 10, 64)
verInfo := model.Version{
Type: model.VerTypeBanner,
Status: info.OpType,
ItemName: info.Name,
TargetItem: bannerID,
AutoPub: 1, // 自动上架
PubStart: time.Time(info.PubStart),
PubEnd: time.Time(info.PubEnd),
For: forInt,
}
err = d.AddVersion(c, tx, &verInfo, &model.VersionExt{
Type: model.VerTypeBanner,
MainInfo: string(mainInfo),
})
if err != nil {
log.Error("创建banner版本失败: %s", err)
return 0, verInfo.VerID, ecode.TicketAddVersionFailed
}
if verInfo.VerID == 0 {
log.Error("创建后获取verID为0")
return 0, verInfo.VerID, ecode.TicketAddVersionFailed
}
// 提交事务
tx.Commit()
return bannerID, verInfo.VerID, nil
}
// EditBanner 编辑投放
func (d *Dao) EditBanner(c context.Context, info *item.BannerEditRequest) (err error) {
// 开启事务
tx := d.db.Begin()
var mainInfo []byte
mainInfo, err = json.Marshal(info)
if err != nil {
log.Error("jsonMarshal版本详情失败:%s", err)
tx.Rollback()
return err
}
//更新审核版本ver,verExt
str := fmt.Sprintf("%d%d%.2d", info.Position, info.SubPosition, info.Order)
forInt, _ := strconv.ParseInt(str, 10, 64)
if err = tx.Model(&model.Version{}).Where("ver_id = ?", info.VerId).Updates(
map[string]interface{}{
"item_name": info.Name,
"status": info.OpType,
"pub_start": time.Time(info.PubStart),
"pub_end": time.Time(info.PubEnd),
"for": forInt,
}).Error; err != nil {
log.Error("更新banner版本失败:%s", err)
tx.Rollback()
return err
}
if err = tx.Model(&model.VersionExt{}).Where("ver_id = ?", info.VerId).Updates(
map[string]interface{}{
"main_info": mainInfo,
}).Error; err != nil {
log.Error("更新banner版本详情失败:%s", err)
tx.Rollback()
return err
}
// 提交事务
tx.Commit()
return nil
}
// PassOrPublishBanner 审核通过或上架banner版本
func (d *Dao) PassOrPublishBanner(c context.Context, verID uint64) (err error) {
//获取版本信息
verInfo, verExtInfo, getErr := d.GetVersion(c, verID, true)
if getErr != nil {
return getErr
}
//解析mainInfo
var decodedMainInfo item.BannerEditRequest
err = json.Unmarshal([]byte(verExtInfo.MainInfo), &decodedMainInfo)
if err != nil {
return err
}
bannerID := verInfo.TargetItem
tx := d.db.Begin()
//如果存在审核通过/进行中版本 删除版本
var bannerVersions []model.Version
if err = tx.Where("target_item = ? and status in (?) and ver_id != ? and deleted_at='0000-00-00 00:00:00'",
bannerID, []int32{model.VerStatusReadyForSale, model.VerStatusOnShelf}, verID).Find(&bannerVersions).Error; err != nil {
log.Error("获取banner所有审核通过/已上架版本失败:%s", err)
tx.Rollback()
return err
}
var delCacheInfos []DelBannerInfo
var verIDs []uint64
var delPosition int32
var delSubPosition int32
var delOrder int32
var delDistrictIDs []string
for _, v := range bannerVersions {
//获取该版本详情 删除对应bannerDistrict信息
if delPosition, delSubPosition, delOrder, delDistrictIDs, err = d.DelBannerDistrictByVerID(c, tx, v.VerID, bannerID); err != nil {
return
}
delCacheInfos = append(delCacheInfos, DelBannerInfo{
Order: delOrder,
Position: delPosition,
SubPosition: delSubPosition,
DistrictIDs: delDistrictIDs,
})
verIDs = append(verIDs, v.VerID)
}
if verIDs != nil {
if err = tx.Exec("UPDATE version SET deleted_at=? WHERE ver_id in (?)", xtime.Now().Format("2006-01-02 15:04:05"), verIDs).Error; err != nil {
log.Error("删除banner版本失败:%s", err)
tx.Rollback()
return err
}
if err = tx.Exec("UPDATE version_ext SET deleted_at=? WHERE ver_id in (?)", xtime.Now().Format("2006-01-02 15:04:05"), verIDs).Error; err != nil {
log.Error("删除banner ext版本失败:%s", err)
tx.Rollback()
return err
}
}
//如果达到投放开始时间 还要更新bannerDistrict并直接上架
var newVerStatus int32
var newBannerStatus int32
var districtIDs []string
if decodedMainInfo.PubStart <= xtime.Now().Unix() {
districtIDs = strings.Split(decodedMainInfo.Location, ",")
for _, districtID := range districtIDs {
newDistrictID, _ := strconv.ParseInt(districtID, 10, 64)
if err = d.CreateOrUpdateBannerDistrict(c, tx, model.BannerDistrict{
BannerID: bannerID,
Position: decodedMainInfo.Position,
SubPosition: decodedMainInfo.SubPosition,
Order: decodedMainInfo.Order,
DistrictID: newDistrictID,
}); err != nil {
return err
}
}
newVerStatus = model.VerStatusOnShelf
newBannerStatus = 1
} else {
newVerStatus = model.VerStatusReadyForSale
newBannerStatus = 0
}
//更新banner表
if err = tx.Model(&model.Banner{}).Where("id = ?", bannerID).Updates(
map[string]interface{}{
"pub_start": time.Time(decodedMainInfo.PubStart),
"pub_end": time.Time(decodedMainInfo.PubEnd),
"name": decodedMainInfo.Name,
"pic": decodedMainInfo.Pic,
"url": decodedMainInfo.Url,
"from": decodedMainInfo.From,
"status": newBannerStatus,
"target_user": decodedMainInfo.TargetUser,
}).Error; err != nil {
log.Error("更新banner失败:%s", err)
tx.Rollback()
return err
}
//更新version状态为审核通过(待上架)或已上架
if err = tx.Model(&model.Version{}).Where("ver_id = ?", verID).Updates(
map[string]interface{}{
"status": newVerStatus,
}).Error; err != nil {
log.Error("更新banner审核版本失败:%s", err)
tx.Rollback()
return err
}
tx.Commit()
//如果新状态是上架状态 删除相关缓存
if newVerStatus == model.VerStatusOnShelf {
_, err = d.DelBannerCache(c, decodedMainInfo.Position, decodedMainInfo.SubPosition, decodedMainInfo.Order, districtIDs, bannerID)
if err != nil {
return
}
for _, v := range delCacheInfos {
_, err = d.DelBannerCache(c, v.Position, v.SubPosition, v.Order, v.DistrictIDs, 0)
if err != nil {
return
}
}
}
return
}
// DelBannerCache 删除banner相关缓存
func (d *Dao) DelBannerCache(c context.Context, position int32, subPosition int32, order int32, districtIDs []string, bannerID int64) (res bool, err error) {
var (
keys []interface{}
)
conn := d.redis.Get(c)
defer func() {
conn.Flush()
conn.Close()
}()
for _, districtID := range districtIDs {
// DEL bannerList
keys = append(keys, keyBannerList(order, districtID, position, subPosition))
}
//删除bannerInfo缓存
if bannerID != 0 {
keys = append(keys, keyBannerInfo(bannerID))
}
log.Info("DEL %v", keys)
//DEL bannerList
if err = conn.Send("DEL", keys...); err != nil {
log.Error("DEL %v, error(%v)", keys, err)
}
if err != nil {
return false, err
}
return true, err
}
//DelBannerDistrictByVerID 删除版本详情内对应的关系表信息
func (d *Dao) DelBannerDistrictByVerID(c context.Context, tx *gorm.DB, verID uint64, bannerID int64) (position int32, subPosition int32, order int32, districtIDs []string, err error) {
var verExtInfo model.VersionExt
var delDecodedInfo item.BannerEditRequest
//获取该版本详情 删除对应bannerDistrict信息
if err = tx.Where("ver_id = ?", verID).First(&verExtInfo).Error; err != nil {
log.Error("获取需要删除版本详情失败:%s", err)
tx.Rollback()
return 0, 0, 0, nil, err
}
//解析mainInfo
err = json.Unmarshal([]byte(verExtInfo.MainInfo), &delDecodedInfo)
if err != nil {
tx.Rollback()
return 0, 0, 0, nil, err
}
districtIDs = strings.Split(delDecodedInfo.Location, ",")
for _, districtID := range districtIDs {
if err = tx.Exec("UPDATE banner_district SET is_deleted=1 WHERE banner_id = ? and district_id = ? and position = ? and sub_position = ? and `order` = ?",
bannerID, districtID, delDecodedInfo.Position, delDecodedInfo.SubPosition, delDecodedInfo.Order).Error; err != nil {
log.Error("删除bannerDistrict失败:%s", err)
tx.Rollback()
return 0, 0, 0, nil, err
}
}
return delDecodedInfo.Position, delDecodedInfo.SubPosition, delDecodedInfo.Order, districtIDs, err
}
// CreateOrUpdateBannerDistrict 更新或新建bannerDistrict关系记录
func (d *Dao) CreateOrUpdateBannerDistrict(c context.Context, tx *gorm.DB, info model.BannerDistrict) (err error) {
var bannerDist model.BannerDistrict
log.Info("bannerDist:%v", info)
err = tx.Where("district_id = ? and position = ? and sub_position = ? and `order` = ?",
info.DistrictID, info.Position, info.SubPosition, info.Order).First(&bannerDist).Error
//除去没查找到记录的报错 其他直接抛错
if err != nil && err != ecode.NothingFound {
log.Error("获取banner dist信息失败:%s", err)
tx.Rollback()
return
}
if bannerDist.ID != 0 {
//update
log.Info("update bannerDistrict")
if err = tx.Exec("UPDATE banner_district SET banner_id=?,is_deleted=0 WHERE district_id = ? and position = ? and sub_position = ? and `order` = ?",
info.BannerID, info.DistrictID, info.Position, info.SubPosition, info.Order).Error; err != nil {
log.Error("更新bannerDistrict失败:%s", err)
tx.Rollback()
return err
}
} else {
//create
log.Info("insert bannerDistrict")
if err = tx.Create(&model.BannerDistrict{
BannerID: info.BannerID,
Position: info.Position,
SubPosition: info.SubPosition,
Order: info.Order,
DistrictID: info.DistrictID,
}).Error; err != nil {
log.Error("创建bannerDistrict失败:%s", err)
tx.Rollback()
return err
}
}
return nil
}
// DeleteBanner 删除banner
func (d *Dao) DeleteBanner(c context.Context, verID uint64) (err error) {
tx := d.db.Begin()
if err = tx.Exec("UPDATE version SET deleted_at=? WHERE ver_id = ?", xtime.Now().Format("2006-01-02 15:04:05"), verID).Error; err != nil {
tx.Rollback()
log.Error("删除banner版本失败:%s", err)
return err
}
if err = tx.Exec("UPDATE version_ext SET deleted_at=? WHERE ver_id=?", xtime.Now().Format("2006-01-02 15:04:05"), verID).Error; err != nil {
tx.Rollback()
log.Error("删除banner ext版本失败:%s", err)
return err
}
tx.Commit()
return
}
// UnpublishBannerManual 手动取消激活banner
func (d *Dao) UnpublishBannerManual(c context.Context, verID uint64) (err error) {
//取消激活banner
//获取版本信息
tx := d.db.Begin()
verInfo, _, getErr := d.GetVersion(c, verID, true)
if getErr != nil {
return getErr
}
bannerID := verInfo.TargetItem
if err = tx.Exec("UPDATE banner SET status = 0 WHERE id = ?", bannerID).Error; err != nil {
tx.Rollback()
log.Error("更新banner未激活状态失败:%s", err)
return err
}
//更新版本为草稿状态
if err = tx.Exec("UPDATE version SET status = ? WHERE ver_id = ?", model.VerStatusNotReviewed, verID).Error; err != nil {
tx.Rollback()
log.Error("更新banner版本为草稿状态失败:%s", err)
return err
}
//删除此版本bannerDistrict信息
var delPosition int32
var delSubPosition int32
var delOrder int32
var delDistrictIDs []string
if delPosition, delSubPosition, delOrder, delDistrictIDs, err = d.DelBannerDistrictByVerID(c, tx, verID, bannerID); err != nil {
return
}
//如果存在除这个版本以外同个投放id的版本 删除版本
var bannerVersions []model.Version
if err = tx.Where("target_item = ? and ver_id != ? and deleted_at='0000-00-00 00:00:00'",
bannerID, verID).Find(&bannerVersions).Error; err != nil {
log.Error("获取banner所有其他版本失败:%s", err)
tx.Rollback()
return err
}
var verIDs []uint64
for _, v := range bannerVersions {
//获取该版本详情 删除对应bannerDistrict信息
_, _, _, _, err = d.DelBannerDistrictByVerID(c, tx, v.VerID, bannerID)
if err != nil {
return
}
verIDs = append(verIDs, v.VerID)
}
if verIDs != nil {
if err = tx.Exec("UPDATE version SET deleted_at=? WHERE ver_id in (?)", xtime.Now().Format("2006-01-02 15:04:05"), verIDs).Error; err != nil {
log.Error("删除banner版本失败:%s", err)
tx.Rollback()
return err
}
if err = tx.Exec("UPDATE version_ext SET deleted_at=? WHERE ver_id in (?)", xtime.Now().Format("2006-01-02 15:04:05"), verIDs).Error; err != nil {
log.Error("删除banner ext版本失败:%s", err)
tx.Rollback()
return err
}
}
tx.Commit()
//删除下架相关缓存
_, err = d.DelBannerCache(c, delPosition, delSubPosition, delOrder, delDistrictIDs, bannerID)
return
}
// UnpublishBannerForced 已过期自动下架操作
func (d *Dao) UnpublishBannerForced(c context.Context, verID uint64) (err error) {
//获取版本信息
tx := d.db.Begin()
verInfo, _, getErr := d.GetVersion(c, verID, true)
if getErr != nil {
return getErr
}
//取消激活banner
bannerID := verInfo.TargetItem
if err = tx.Exec("UPDATE banner SET status = 0 WHERE id = ?", bannerID).Error; err != nil {
tx.Rollback()
log.Error("更新banner未激活状态失败:%s", err)
return err
}
//删除bannerDistrict信息
var delPosition int32
var delSubPosition int32
var delOrder int32
var delDistrictIDs []string
if delPosition, delSubPosition, delOrder, delDistrictIDs, err = d.DelBannerDistrictByVerID(c, tx, verID, bannerID); err != nil {
return
}
//更新版本为强制下架状态
if err = tx.Exec("UPDATE version SET status = ? WHERE ver_id = ?", model.VerStatusOffShelfForced, verID).Error; err != nil {
tx.Rollback()
log.Error("更新banner版本为强制下架状态失败:%s", err)
return err
}
tx.Commit()
//删除下架相关缓存
_, err = d.DelBannerCache(c, delPosition, delSubPosition, delOrder, delDistrictIDs, bannerID)
return
}
// CgBannerStatus 更改banner版本状态
func (d *Dao) CgBannerStatus(c context.Context, info *item.VersionStatusRequest) (err error) {
switch info.OpType {
case 0:
//手动取消激活操作
err = d.UnpublishBannerManual(c, info.VerId)
case 1:
//提交审核操作
err = d.db.Exec("UPDATE version SET status = ? WHERE ver_id = ?", model.VerStatusReadyForReview, info.VerId).Error
if err != nil {
return err
}
//提交审核时 记入versionLog
err = d.AddVersionLog(c, &model.VersionLog{
VerID: info.VerId,
Type: 2, //用户操作记录
Log: "提交审核",
Uname: info.Uname,
})
case 2:
//删除操作
err = d.DeleteBanner(c, info.VerId)
case 3:
//上架操作
err = d.PassOrPublishBanner(c, info.VerId)
case 4:
//已过期自动下架操作
err = d.UnpublishBannerForced(c, info.VerId)
default:
return ecode.NothingFound
}
return
}

View File

@@ -0,0 +1,305 @@
package dao
import (
"context"
"encoding/json"
"go-common/app/common/openplatform/random"
"go-common/app/service/openplatform/ticket-item/model"
"go-common/library/ecode"
"go-common/library/log"
item "go-common/app/service/openplatform/ticket-item/api/grpc/v1"
)
// BulletinMainInfo 公告版本内容
type BulletinMainInfo struct {
Name string `json:"name"`
Introduction string `json:"introduction"`
Content string `json:"content"`
ProjectName string `json:"project_name"`
Pid int64 `json:"pid"`
}
// GetBulletins 获取项目下所有公告
func (d *Dao) GetBulletins(c context.Context, pid int64) (res []*item.BulletinInfo, err error) {
// 获取项目下所有bulletin基础信息
var projectBulletins []*model.Bulletin
if dbErr := d.db.Order("ctime desc").Where("project_id = ? and status = 1", pid).Find(&projectBulletins).Error; dbErr != nil {
log.Error("获取项目公告基础信息失败: %s", dbErr)
return nil, ecode.NothingFound
}
var bulIDs []int64
for _, value := range projectBulletins {
bulIDs = append(bulIDs, value.ID)
}
// 获取公告详情并用id当键值组成map
var bulletinDetail []*model.BulletinExtra
if dbErr := d.db.Where("id in (?)", bulIDs).Find(&bulletinDetail).Error; dbErr != nil {
log.Error("获取项目公告详情失败: %s", dbErr)
return nil, ecode.NothingFound
}
bulletinDetailMap := make(map[int64]*model.BulletinExtra)
for _, value := range bulletinDetail {
bulletinDetailMap[value.ID] = value
}
// 添加详情信息到公告信息中
for _, value := range projectBulletins {
tmpBul := &item.BulletinInfo{
ID: value.ID,
Title: value.Title,
Content: value.Content,
Ctime: value.Ctime.Time().Format("2006-01-02 15:04:05"),
Mtime: value.Mtime.Time().Format("2006-01-02 15:04:05"),
VerID: value.VerID,
}
detailInfo, ok := bulletinDetailMap[value.ID]
if ok {
tmpBul.Detail = detailInfo.Detail
} else {
tmpBul.Detail = ""
}
res = append(res, tmpBul)
}
return
}
// AddBulletin 添加公告
func (d *Dao) AddBulletin(c context.Context, info *item.BulletinInfoRequest) (bool, error) {
pid := info.ParentID
mainInfo, jsonErr := d.GenBulMainInfo(pid, info.Title, info.Content, info.Detail)
if jsonErr != nil {
log.Error("获取整合maininfo失败: %s", jsonErr)
return false, ecode.NothingFound
}
// add version and version ext
verErr := d.AddVersion(c, nil, &model.Version{
Type: model.VerTypeBulletin,
Status: 1, // 审核中
ItemName: info.Title,
ParentID: pid,
TargetItem: info.TargetItem,
AutoPub: 1, // 自动上架
PubStart: model.TimeNull,
PubEnd: model.TimeNull,
}, &model.VersionExt{
Type: model.VerTypeBulletin,
MainInfo: string(mainInfo),
})
if verErr != nil {
log.Error("创建公告版本失败: %s", verErr)
return false, ecode.NothingFound
}
return true, nil
}
// PassBulletin 审核通过公告
func (d *Dao) PassBulletin(c context.Context, verID uint64) (bool, error) {
verInfo, verExtInfo, err := d.GetVersion(c, verID, true)
if err != nil {
return false, ecode.NothingFound
}
targetItem := verInfo.TargetItem
var decodedMainInfo BulletinMainInfo
err = json.Unmarshal([]byte(verExtInfo.MainInfo), &decodedMainInfo)
if err != nil {
return false, err
}
var finalTargetItem int64
// 开启事务
tx := d.db.Begin()
if targetItem == 0 {
// 没新建过bulletin信息
bulletinID := random.Uniqid(19)
bulData := &model.Bulletin{
Status: 1,
Title: decodedMainInfo.Name,
Content: decodedMainInfo.Introduction,
ProjectID: decodedMainInfo.Pid,
VerID: verID,
BulletinID: bulletinID,
}
insertErr := tx.Save(&bulData).Error
if insertErr != nil {
tx.Rollback()
log.Error("新建bulletin失败: %s", insertErr)
return false, ecode.NotModified
}
// 获取新建的bulletin自增id
bulPrimID := bulData.ID
if bulPrimID == 0 {
tx.Rollback()
log.Error("获取新建bulletin自增id失败")
return false, ecode.NothingFound
}
// 新建bulletin_extra
insertExtErr := tx.Create(&model.BulletinExtra{
ID: bulPrimID,
Detail: decodedMainInfo.Content,
BulletinID: bulletinID,
}).Error
if insertExtErr != nil {
tx.Rollback()
log.Error("新建bulletin_extra失败%s", insertExtErr)
return false, ecode.NotModified
}
finalTargetItem = bulPrimID
} else {
// 已建过的直接更新bulletin
updateErr := tx.Where("id = ?", targetItem).Model(&model.Bulletin{}).Updates(
map[string]interface{}{
"status": 1,
"title": decodedMainInfo.Name,
"content": decodedMainInfo.Introduction,
"ver_id": verID,
}).Error
if updateErr != nil {
tx.Rollback()
log.Error("UPDATE BULLETIN FAILED")
return false, ecode.NotModified
}
// 更新bulletin_extra
updateExtErr := tx.Where("id = ?", targetItem).Model(&model.BulletinExtra{}).Updates(
map[string]interface{}{
"detail": decodedMainInfo.Content,
}).Error
if updateExtErr != nil {
tx.Rollback()
log.Error("UPDATE BULLETIN_EXTRA FAILED")
return false, ecode.NotModified
}
finalTargetItem = targetItem
}
// 更新版本为已审核状态
updateVerErr := tx.Where("ver_id = ?", verID).Model(&model.Version{}).Updates(
map[string]interface{}{
"status": 4, // 已上架状态
"ver": "1.0",
"target_item": finalTargetItem,
}).Error
if updateVerErr != nil {
tx.Rollback()
log.Error("UPDATE BULLETIN VERSION FAILED")
return false, ecode.NotModified
}
tx.Commit()
return true, nil
}
// UpdateBulletin 编辑公告版本
func (d *Dao) UpdateBulletin(c context.Context, info *item.BulletinInfoRequest) (bool, error) {
// 获取JSONEncode好的maininfo
pid := info.ParentID
mainInfo, jsonErr := d.GenBulMainInfo(pid, info.Title, info.Content, info.Detail)
if jsonErr != nil || mainInfo == "" {
log.Error("获取maininfo失败: %s", jsonErr)
return false, ecode.NothingFound
}
// 开启事务
tx := d.db.Begin()
// 编辑version_ext
updateExtErr := tx.Model(&model.VersionExt{}).Where("ver_id = ? and type = ?", info.VerID, model.VerTypeBulletin).Update("main_info", mainInfo).Error
if updateExtErr != nil {
tx.Rollback()
log.Error("更新versionext失败:%s", updateExtErr)
return false, ecode.NotModified
}
// 编辑version
updateErr := d.db.Model(&model.Version{}).Where("ver_id = ? and type = ?", info.VerID, model.VerTypeBulletin).Updates(
map[string]interface{}{
"status": 1, // 审核中
"item_name": info.Title,
}).Error
if updateErr != nil {
tx.Rollback()
log.Error("VERSION UPDATE FAILED:%s", updateErr)
return false, ecode.NotModified
}
tx.Commit()
return true, nil
}
// GenBulMainInfo 整合公告的详情maininfo字段
func (d *Dao) GenBulMainInfo(pid int64, name string, introduction string, content string) (string, error) {
var projectInfo []model.Item
if dbErr := d.db.Select("name").Where("id = ?", pid).Find(&projectInfo).Error; dbErr != nil {
log.Error("获取项目信息失败:%s", dbErr)
return "", ecode.NothingFound
}
extInfo := BulletinMainInfo{
Name: name,
Introduction: introduction,
Content: content,
ProjectName: projectInfo[0].Name,
Pid: pid,
}
mainInfo, jsonErr := json.Marshal(extInfo)
if jsonErr != nil {
log.Error("JSONEncode失败: %s", jsonErr)
return "", ecode.NothingFound
}
return string(mainInfo), nil
}
// UnpublishBulletin 下架版本
func (d *Dao) UnpublishBulletin(c context.Context, verID uint64, status int8) (bool, error) {
// 获取版本信息
verInfo, _, err := d.GetVersion(c, verID, false)
if err != nil {
return false, ecode.NothingFound
}
bulletinID := verInfo.TargetItem
// 开启事务
tx := d.db.Begin()
// 取消激活公告
updateErr := tx.Model(&model.Bulletin{}).Where("id = ? and ver_id = ?", bulletinID, verID).Updates(
map[string]interface{}{
"status": 0,
}).Error
if updateErr != nil {
tx.Rollback()
log.Error("取消激活公告失败:%s", updateErr)
return false, ecode.NotModified
}
// 更新版本为已下架状态
updateVerErr := tx.Model(&model.Version{}).Where("ver_id = ? and type = ?", verID, model.VerTypeBulletin).Updates(
map[string]interface{}{
"status": status,
}).Error
if updateVerErr != nil {
tx.Rollback()
log.Error("版本更新状态失败:%s", updateVerErr)
}
tx.Commit()
return true, nil
}

View File

@@ -0,0 +1,73 @@
package dao
import (
"context"
"testing"
item "go-common/app/service/openplatform/ticket-item/api/grpc/v1"
. "github.com/smartystreets/goconvey/convey"
)
// TestGetBulletins
func TestDao_GetBulletins(t *testing.T) {
Convey("GetBulletins", t, func() {
once.Do(startService)
res, err := d.GetBulletins(context.TODO(), 72)
So(res, ShouldNotBeNil)
So(err, ShouldBeNil)
})
}
// TestAddBulletin
func TestDao_AddBulletin(t *testing.T) {
Convey("AddBulletin", t, func() {
once.Do(startService)
res, err := d.AddBulletin(context.TODO(), &item.BulletinInfoRequest{
ParentID: 72,
Title: "go test bulletin",
Content: "goooo",
Detail: "goo",
TargetItem: 0,
})
So(res, ShouldBeTrue)
So(err, ShouldBeNil)
})
}
// TestUpdateBulletin
func TestDao_UpdateBulletin(t *testing.T) {
Convey("UpdateBulletin", t, func() {
once.Do(startService)
res, err := d.UpdateBulletin(context.TODO(), &item.BulletinInfoRequest{
ParentID: 72,
Title: "go test bulletin22",
Content: "gooossso",
Detail: "goo",
TargetItem: 0,
VerID: 2692936350844594047,
})
So(res, ShouldBeTrue)
So(err, ShouldBeNil)
})
}
// TestPassBulletin
func TestDao_PassBulletin(t *testing.T) {
Convey("PassBulletin", t, func() {
once.Do(startService)
res, err := d.PassBulletin(context.TODO(), 2692936350844594047)
So(res, ShouldBeTrue)
So(err, ShouldBeNil)
})
}
// TestUnpublishBulletin
func TestDao_UnpublishBulletin(t *testing.T) {
Convey("UnpublishBulletin", t, func() {
once.Do(startService)
res, err := d.UnpublishBulletin(context.TODO(), 2692936350844594047, -1)
So(res, ShouldBeTrue)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,441 @@
// Code generated by $GOPATH/src/go-common/app/tool/cache/gen. DO NOT EDIT.
/*
Package dao is a generated cache proxy package.
It is generated from:
type _cache interface {
// cache: -nullcache=&model.Item{ID:-1} -check_null_code=$!=nil&&$.ID==-1
Items(c context.Context, pid []int64) (info map[int64]*model.Item, err error)
// cache: -nullcache=&model.ItemDetail{ProjectID:-1} -check_null_code=$!=nil&&$.ProjectID==-1
ItemDetails(c context.Context, pid []int64) (details map[int64]*model.ItemDetail, err error)
// cache: -nullcache=[]*model.TicketInfo{{TicketPrice:model.TicketPrice{ProjectID:-1}}} -check_null_code=len($)==1&&$[0].ProjectID==-1
TkListByItem(c context.Context, pid []int64) (info map[int64][]*model.TicketInfo, err error)
// cache: -nullcache=&model.Venue{ID:-1} -check_null_code=$!=nil&&$.ID==-1
Venues(c context.Context, id []int64) (venues map[int64]*model.Venue, err error)
// cache: -nullcache=&model.Place{ID:-1} -check_null_code=$!=nil&&$.ID==-1
Place(c context.Context, id int64) (place *model.Place, err error)
// cache: -nullcache=[]*model.Screen{{ProjectID:-1}} -check_null_code=len($)==1&&$[0].ProjectID==-1
ScListByItem(c context.Context, pid []int64) (info map[int64][]*model.Screen, err error)
// cache: -nullcache=&model.Screen{ProjectID:-1} -check_null_code=$!=nil&&$.ProjectID==-1
ScList(c context.Context, sids []int64) (info map[int64]*model.Screen, err error)
// cache: -nullcache=&model.TicketInfo{TicketPrice:model.TicketPrice{ProjectID:-1}} -check_null_code=$!=nil&&$.ProjectID==-1
TkList(c context.Context, tids []int64) (info map[int64]*model.TicketInfo, err error)
}
*/
package dao
import (
"context"
"go-common/app/service/openplatform/ticket-item/model"
"go-common/library/stat/prom"
)
var _ _cache
// Items get data from cache if miss will call source method, then add to cache.
func (d *Dao) Items(c context.Context, keys []int64) (res map[int64]*model.Item, err error) {
if len(keys) == 0 {
return
}
addCache := true
if res, err = d.CacheItems(c, keys); err != nil {
addCache = false
res = nil
err = nil
}
var miss []int64
for _, key := range keys {
if (res == nil) || (res[key] == nil) {
miss = append(miss, key)
}
}
prom.CacheHit.Add("Items", int64(len(keys)-len(miss)))
for k, v := range res {
if v != nil && v.ID == -1 {
delete(res, k)
}
}
missLen := len(miss)
if missLen == 0 {
return
}
var missData map[int64]*model.Item
prom.CacheMiss.Add("Items", int64(len(miss)))
missData, err = d.RawItems(c, miss)
if res == nil {
res = make(map[int64]*model.Item, len(keys))
}
for k, v := range missData {
res[k] = v
}
if err != nil {
return
}
for _, key := range miss {
if res[key] == nil {
missData[key] = &model.Item{ID: -1}
}
}
if !addCache {
return
}
d.cache.Do(c, func(c context.Context) {
d.AddCacheItems(c, missData)
})
return
}
// ItemDetails get data from cache if miss will call source method, then add to cache.
func (d *Dao) ItemDetails(c context.Context, keys []int64) (res map[int64]*model.ItemDetail, err error) {
if len(keys) == 0 {
return
}
addCache := true
if res, err = d.CacheItemDetails(c, keys); err != nil {
addCache = false
res = nil
err = nil
}
var miss []int64
for _, key := range keys {
if (res == nil) || (res[key] == nil) {
miss = append(miss, key)
}
}
prom.CacheHit.Add("ItemDetails", int64(len(keys)-len(miss)))
for k, v := range res {
if v != nil && v.ProjectID == -1 {
delete(res, k)
}
}
missLen := len(miss)
if missLen == 0 {
return
}
var missData map[int64]*model.ItemDetail
prom.CacheMiss.Add("ItemDetails", int64(len(miss)))
missData, err = d.RawItemDetails(c, miss)
if res == nil {
res = make(map[int64]*model.ItemDetail, len(keys))
}
for k, v := range missData {
res[k] = v
}
if err != nil {
return
}
for _, key := range miss {
if res[key] == nil {
missData[key] = &model.ItemDetail{ProjectID: -1}
}
}
if !addCache {
return
}
d.cache.Do(c, func(c context.Context) {
d.AddCacheItemDetails(c, missData)
})
return
}
// TkListByItem get data from cache if miss will call source method, then add to cache.
func (d *Dao) TkListByItem(c context.Context, keys []int64) (res map[int64][]*model.TicketInfo, err error) {
if len(keys) == 0 {
return
}
addCache := true
if res, err = d.CacheTkListByItem(c, keys); err != nil {
addCache = false
res = nil
err = nil
}
var miss []int64
for _, key := range keys {
if (res == nil) || (len(res[key]) == 0) {
miss = append(miss, key)
}
}
prom.CacheHit.Add("TkListByItem", int64(len(keys)-len(miss)))
for k, v := range res {
if len(v) == 1 && v[0].ProjectID == -1 {
delete(res, k)
}
}
missLen := len(miss)
if missLen == 0 {
return
}
var missData map[int64][]*model.TicketInfo
prom.CacheMiss.Add("TkListByItem", int64(len(miss)))
missData, err = d.RawTkListByItem(c, miss)
if res == nil {
res = make(map[int64][]*model.TicketInfo, len(keys))
}
for k, v := range missData {
res[k] = v
}
if err != nil {
return
}
for _, key := range miss {
if len(res[key]) == 0 {
missData[key] = []*model.TicketInfo{{TicketPrice: model.TicketPrice{ProjectID: -1}}}
}
}
if !addCache {
return
}
d.cache.Do(c, func(c context.Context) {
d.AddCacheTkListByItem(c, missData)
})
return
}
// Venues get data from cache if miss will call source method, then add to cache.
func (d *Dao) Venues(c context.Context, keys []int64) (res map[int64]*model.Venue, err error) {
if len(keys) == 0 {
return
}
addCache := true
if res, err = d.CacheVenues(c, keys); err != nil {
addCache = false
res = nil
err = nil
}
var miss []int64
for _, key := range keys {
if (res == nil) || (res[key] == nil) {
miss = append(miss, key)
}
}
prom.CacheHit.Add("Venues", int64(len(keys)-len(miss)))
for k, v := range res {
if v != nil && v.ID == -1 {
delete(res, k)
}
}
missLen := len(miss)
if missLen == 0 {
return
}
var missData map[int64]*model.Venue
prom.CacheMiss.Add("Venues", int64(len(miss)))
missData, err = d.RawVenues(c, miss)
if res == nil {
res = make(map[int64]*model.Venue, len(keys))
}
for k, v := range missData {
res[k] = v
}
if err != nil {
return
}
for _, key := range miss {
if res[key] == nil {
missData[key] = &model.Venue{ID: -1}
}
}
if !addCache {
return
}
d.cache.Do(c, func(c context.Context) {
d.AddCacheVenues(c, missData)
})
return
}
// Place get data from cache if miss will call source method, then add to cache.
func (d *Dao) Place(c context.Context, id int64) (res *model.Place, err error) {
addCache := true
res, err = d.CachePlace(c, id)
if err != nil {
addCache = false
err = nil
}
defer func() {
if res != nil && res.ID == -1 {
res = nil
}
}()
if res != nil {
prom.CacheHit.Incr("Place")
return
}
prom.CacheMiss.Incr("Place")
res, err = d.RawPlace(c, id)
if err != nil {
return
}
miss := res
if miss == nil {
miss = &model.Place{ID: -1}
}
if !addCache {
return
}
d.cache.Do(c, func(c context.Context) {
d.AddCachePlace(c, id, miss)
})
return
}
// ScListByItem get data from cache if miss will call source method, then add to cache.
func (d *Dao) ScListByItem(c context.Context, keys []int64) (res map[int64][]*model.Screen, err error) {
if len(keys) == 0 {
return
}
addCache := true
if res, err = d.CacheScListByItem(c, keys); err != nil {
addCache = false
res = nil
err = nil
}
var miss []int64
for _, key := range keys {
if (res == nil) || (len(res[key]) == 0) {
miss = append(miss, key)
}
}
prom.CacheHit.Add("ScListByItem", int64(len(keys)-len(miss)))
for k, v := range res {
if len(v) == 1 && v[0].ProjectID == -1 {
delete(res, k)
}
}
missLen := len(miss)
if missLen == 0 {
return
}
var missData map[int64][]*model.Screen
prom.CacheMiss.Add("ScListByItem", int64(len(miss)))
missData, err = d.RawScListByItem(c, miss)
if res == nil {
res = make(map[int64][]*model.Screen, len(keys))
}
for k, v := range missData {
res[k] = v
}
if err != nil {
return
}
for _, key := range miss {
if len(res[key]) == 0 {
missData[key] = []*model.Screen{{ProjectID: -1}}
}
}
if !addCache {
return
}
d.cache.Do(c, func(c context.Context) {
d.AddCacheScListByItem(c, missData)
})
return
}
// ScList get data from cache if miss will call source method, then add to cache.
func (d *Dao) ScList(c context.Context, keys []int64) (res map[int64]*model.Screen, err error) {
if len(keys) == 0 {
return
}
addCache := true
if res, err = d.CacheScList(c, keys); err != nil {
addCache = false
res = nil
err = nil
}
var miss []int64
for _, key := range keys {
if (res == nil) || (res[key] == nil) {
miss = append(miss, key)
}
}
prom.CacheHit.Add("ScList", int64(len(keys)-len(miss)))
for k, v := range res {
if v != nil && v.ProjectID == -1 {
delete(res, k)
}
}
missLen := len(miss)
if missLen == 0 {
return
}
var missData map[int64]*model.Screen
prom.CacheMiss.Add("ScList", int64(len(miss)))
missData, err = d.RawScList(c, miss)
if res == nil {
res = make(map[int64]*model.Screen, len(keys))
}
for k, v := range missData {
res[k] = v
}
if err != nil {
return
}
for _, key := range miss {
if res[key] == nil {
missData[key] = &model.Screen{ProjectID: -1}
}
}
if !addCache {
return
}
d.cache.Do(c, func(c context.Context) {
d.AddCacheScList(c, missData)
})
return
}
// TkList get data from cache if miss will call source method, then add to cache.
func (d *Dao) TkList(c context.Context, keys []int64) (res map[int64]*model.TicketInfo, err error) {
if len(keys) == 0 {
return
}
addCache := true
if res, err = d.CacheTkList(c, keys); err != nil {
addCache = false
res = nil
err = nil
}
var miss []int64
for _, key := range keys {
if (res == nil) || (res[key] == nil) {
miss = append(miss, key)
}
}
prom.CacheHit.Add("TkList", int64(len(keys)-len(miss)))
for k, v := range res {
if v != nil && v.ProjectID == -1 {
delete(res, k)
}
}
missLen := len(miss)
if missLen == 0 {
return
}
var missData map[int64]*model.TicketInfo
prom.CacheMiss.Add("TkList", int64(len(miss)))
missData, err = d.RawTkList(c, miss)
if res == nil {
res = make(map[int64]*model.TicketInfo, len(keys))
}
for k, v := range missData {
res[k] = v
}
if err != nil {
return
}
for _, key := range miss {
if res[key] == nil {
missData[key] = &model.TicketInfo{TicketPrice: model.TicketPrice{ProjectID: -1}}
}
}
if !addCache {
return
}
d.cache.Do(c, func(c context.Context) {
d.AddCacheTkList(c, missData)
})
return
}

View File

@@ -0,0 +1,152 @@
package dao
import (
"context"
"fmt"
"strconv"
"time"
"go-common/app/service/openplatform/ticket-item/conf"
"go-common/app/service/openplatform/ticket-item/model"
"go-common/library/cache/redis"
"go-common/library/database/orm"
"go-common/library/log"
"go-common/library/database/elastic"
"go-common/library/sync/pipeline/fanout"
"github.com/jinzhu/gorm"
)
// Expire time
const (
CacheTimeout = 120
_expireHalfhour = 1800 // 半小时过期
)
// Dao dao
type Dao struct {
c *conf.Config
redis *redis.Pool
cache *fanout.Fanout
// DB
db *gorm.DB
expire int32
es *elastic.Elastic
}
func keyItem(id int64) string {
return "open_item_" + strconv.FormatInt(id, 10)
}
func keyItemDetail(id int64) string {
return "open_item_detail_" + strconv.FormatInt(id, 10)
}
func keyItemTicket(id int64) string {
return "open_itemticket_" + strconv.FormatInt(id, 10)
}
func keyTicket(id int64) string {
return "open_ticket_" + strconv.FormatInt(id, 10)
}
func keyVenue(id int64) string {
return "open_venue_" + strconv.FormatInt(id, 10)
}
func keyPlace(id int64) string {
return "open_place_" + strconv.FormatInt(id, 10)
}
func keyItemScreen(id int64) string {
return "open_itemscreen_" + strconv.FormatInt(id, 10)
}
func keyScreen(id int64) string {
return "open_screen_" + strconv.FormatInt(id, 10)
}
func keyBannerList(order int32, districtID string, position int32, subPosition int32) string {
return fmt.Sprintf("BANNERLISTV3:%d:%s:%d:%d", order, districtID, position, subPosition)
}
func keyBannerInfo(bannerID int64) string {
return fmt.Sprintf("%d:BANNERINFOV2", bannerID)
}
//go:generate $GOPATH/src/go-common/app/tool/cache/gen
type _cache interface {
// cache: -nullcache=&model.Item{ID:-1} -check_null_code=$!=nil&&$.ID==-1
Items(c context.Context, pid []int64) (info map[int64]*model.Item, err error)
// cache: -nullcache=&model.ItemDetail{ProjectID:-1} -check_null_code=$!=nil&&$.ProjectID==-1
ItemDetails(c context.Context, pid []int64) (details map[int64]*model.ItemDetail, err error)
// cache: -nullcache=[]*model.TicketInfo{{TicketPrice:model.TicketPrice{ProjectID:-1}}} -check_null_code=len($)==1&&$[0].ProjectID==-1
TkListByItem(c context.Context, pid []int64) (info map[int64][]*model.TicketInfo, err error)
// cache: -nullcache=&model.Venue{ID:-1} -check_null_code=$!=nil&&$.ID==-1
Venues(c context.Context, id []int64) (venues map[int64]*model.Venue, err error)
// cache: -nullcache=&model.Place{ID:-1} -check_null_code=$!=nil&&$.ID==-1
Place(c context.Context, id int64) (place *model.Place, err error)
// cache: -nullcache=[]*model.Screen{{ProjectID:-1}} -check_null_code=len($)==1&&$[0].ProjectID==-1
ScListByItem(c context.Context, pid []int64) (info map[int64][]*model.Screen, err error)
// cache: -nullcache=&model.Screen{ProjectID:-1} -check_null_code=$!=nil&&$.ProjectID==-1
ScList(c context.Context, sids []int64) (info map[int64]*model.Screen, err error)
// cache: -nullcache=&model.TicketInfo{TicketPrice:model.TicketPrice{ProjectID:-1}} -check_null_code=$!=nil&&$.ProjectID==-1
TkList(c context.Context, tids []int64) (info map[int64]*model.TicketInfo, err error)
}
// New init mysql db
func New(c *conf.Config) (dao *Dao) {
dao = &Dao{
c: c,
// orm
db: orm.NewMySQL(c.ORM),
redis: redis.NewPool(c.Redis.Master),
expire: int32(time.Duration(c.Redis.Expire) / time.Second),
cache: fanout.New("cache", fanout.Worker(1), fanout.Buffer(1024)),
es: elastic.NewElastic(&elastic.Config{
Host: c.URL.ElasticHost,
HTTPClient: c.HTTPClient.Read,
}),
}
return
}
// Ping ping 方法
func (d *Dao) Ping(c context.Context) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
_, err = conn.Do("PING")
if err != nil {
return
}
return d.db.DB().PingContext(c)
}
// Close 关闭redis 和 db 连接
func (d *Dao) Close() (err error) {
d.redis.Close()
d.db.Close()
return
}
// BeginTran 开启事务
func (d *Dao) BeginTran(c context.Context) (tx *gorm.DB, err error) {
tx = d.db.Begin()
if tx.Error != nil {
err = tx.Error
tx = nil
log.Error("开启事务失败:%s", err)
}
return
}
// CommitTran 提交事务
func (d *Dao) CommitTran(c context.Context, tx *gorm.DB) (err error) {
if err = tx.Commit().Error; err != nil {
tx = nil
log.Error("提交事务失败:%s", err)
}
return
}

View File

@@ -0,0 +1,22 @@
package dao
import (
"time"
"go-common/app/service/openplatform/ticket-item/conf"
"go-common/library/log"
)
func initConf() {
if err := conf.Init(); err != nil {
panic(err)
}
log.Init(conf.Conf.Log)
defer log.Close()
}
func startService() {
initConf()
d = New(conf.Conf)
time.Sleep(time.Second * 2)
}

View File

@@ -0,0 +1,97 @@
package dao
import (
"context"
"fmt"
"time"
"go-common/library/database/sql"
"go-common/library/log"
xtime "go-common/library/time"
)
// sql cachekey
const (
_addUserFavSQL = "INSERT INTO user_favorite (mid, item_id, type, status) VALUES(?, ?, ?, ?)"
_selectUserFavSQL = "SELECT status, mtime FROM user_favorite WHERE mid=? AND type=? AND item_id=? LIMIT 1"
_updateUserFavSQL = "UPDATE user_favorite SET status=? where mid=? AND type=? AND item_id=? AND mtime=? LIMIT 1"
_userFavList = "%d:USERFAVLIST"
_userFavState = "%d:USERFAVSTATE:%d:%d"
)
// FavUpdate 收藏状态更新
func (d *Dao) FavUpdate(c context.Context, itemID int64, mid int64, typ int32, status int32) (err error) {
res := d.db.Raw(_selectUserFavSQL, mid, typ, itemID)
if err = res.Error; err != nil {
log.Error("d.FavUpdate() error(%v)", err)
return
}
var (
oldStatus int32
lastMtime xtime.Time
notFound bool
)
if err = res.Row().Scan(&oldStatus, &lastMtime); err != nil {
if err == sql.ErrNoRows {
notFound = true
} else {
log.Error("d.FavUpdate() res.Row().Scan() error(%v)", err)
return
}
}
if notFound {
// 插入
if err = d.db.Exec(_addUserFavSQL, mid, itemID, typ, status).Error; err != nil {
log.Error("d.FavUpdate(%d, %d, %d, %d) Exec(%s) error(%v)", mid, itemID, typ, status, _addUserFavSQL, err)
return
}
} else {
// 更新
if oldStatus == status {
log.Info("d.FavUpdate(%d, %d, %d, %d) 前后状态相同 oldStatus %d ", mid, itemID, typ, status, oldStatus)
return
}
if err = d.db.Exec(_updateUserFavSQL, status, mid, typ, itemID, lastMtime.Time().Format("2006-01-02 15:04:05")).Error; err != nil {
log.Error("d.FavUpdate(%d, %d, %d, %d) Exec(%s) error(%v)", mid, itemID, typ, status, _updateUserFavSQL, err)
return
}
}
return
}
// UserFavStateCache 设置用户收藏缓存 支持多种类型
func (d *Dao) UserFavStateCache(c context.Context, itemID int64, mid int64, typ int32, status int32) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
_, err = conn.Do("SETEX", fmt.Sprintf(_userFavState, mid, itemID, typ), _expireHalfhour, status)
if err != nil {
log.Error("d.UserFavState(%d, %d, %d, %d) SETEX error(%v)", itemID, mid, typ, status, err)
return
}
return
}
// UpdateFavListCache 更新收藏列表缓存
func (d *Dao) UpdateFavListCache(c context.Context, itemID int64, mid int64, status int32) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
if status == 1 {
_, err = conn.Do("ZADD", fmt.Sprintf(_userFavList, mid), time.Now().Unix(), itemID)
} else {
_, err = conn.Do("ZREM", fmt.Sprintf(_userFavList, mid), itemID)
}
if err != nil {
log.Error("d.UpdateFavListCache(%d, %d, %d) error(%v)", itemID, mid, status, err)
return
}
return
}

View File

@@ -0,0 +1,144 @@
package dao
import (
"context"
"fmt"
"strconv"
"go-common/app/common/openplatform/random"
item "go-common/app/service/openplatform/ticket-item/api/grpc/v1"
"go-common/app/service/openplatform/ticket-item/model"
"go-common/library/database/elastic"
"go-common/library/ecode"
"go-common/library/log"
)
// AddGuest 添加新嘉宾
func (d *Dao) AddGuest(c context.Context, info *item.GuestInfoRequest) (ret bool, err error) {
// 此处guest_id调用订单号生成器
guestID := random.Uniqid(19)
// 插入新数据
if dbErr := d.db.Create(&model.Guest{
Name: info.Name,
GuestImg: info.GuestImg,
Description: info.Description,
GuestID: guestID,
}).Error; dbErr != nil {
log.Error("insertion failed")
return false, ecode.NothingFound
}
return true, nil
}
// UpdateGuest 编辑嘉宾信息
func (d *Dao) UpdateGuest(c context.Context, info *item.GuestInfoRequest) (res bool, err error) {
// find original guest with id
var oriGuest model.Guest
if dbErr := d.db.First(&oriGuest, info.ID).Error; dbErr != nil {
log.Error("guest(%d) not found", info.ID)
return false, ecode.NothingFound
}
// update guest with new info (using map can update the column with empty string)
updateErr := d.db.Model(&oriGuest).Updates(
map[string]interface{}{
"name": info.Name,
"guest_img": info.GuestImg,
"description": info.Description,
}).Error
if updateErr != nil {
log.Error("UPDATE FAILED")
return false, ecode.NotModified
}
return true, nil
}
// GuestStatus 更新嘉宾状态
func (d *Dao) GuestStatus(c context.Context, id int64, status int8) (res bool, err error) {
// find original guest with id
var oriGuest model.Guest
if dbErr := d.db.First(&oriGuest, id).Error; dbErr != nil {
log.Error("guest (%d) not found", id)
return false, ecode.NothingFound
}
// update guest with new info (using map can update the column with empty string)
updateErr := d.db.Model(&oriGuest).Update("status", status).Error
if updateErr != nil {
log.Error("CHANGE STATUS FAILED")
return false, ecode.NotModified
}
return true, nil
}
// GetGuests 获取项目下所有嘉宾
func (d *Dao) GetGuests(c context.Context, pid int64) (res []*model.Guest, err error) {
// 获取项目下所有guestID
var projectGuests []*model.ProjectGuest
if dbErr := d.db.Select("guest_id").Order("position").Where("project_id = ? and delete_status = 0", pid).Find(&projectGuests).Error; dbErr != nil {
log.Error("获取项目嘉宾id失败: %s", dbErr)
err = ecode.NothingFound
return
}
var guestIDs []int64
for idx := range projectGuests {
guestIDs = append(guestIDs, projectGuests[idx].GuestID)
}
// 获取未禁用嘉宾信息 并用id当键值组成map
var guestInfo []*model.Guest
if dbErr := d.db.Where("status = 1 and id in (?)", guestIDs).Find(&guestInfo).Error; dbErr != nil {
log.Error("获取项目嘉宾信息失败: %s", dbErr)
err = ecode.NothingFound
return
}
guestInfoMap := make(map[int64]*model.Guest)
for _, value := range guestInfo {
guestInfoMap[value.ID] = value
}
// 根据guestIDs重新排序嘉宾信息
for _, value := range guestIDs {
if guestInfoMap[value].Status == 1 {
res = append(res, guestInfoMap[value])
}
}
return
}
// GuestSearch guest es search.
func (d *Dao) GuestSearch(c context.Context, arg *model.GuestSearchParam) (guests *model.GuestSearchList, err error) {
r := d.es.NewRequest("ticket_guest").
Index("ticket_guest").
Order("ctime", elastic.OrderDesc).
Order("id", elastic.OrderAsc).
Ps(arg.Ps).Pn(arg.Pn)
if arg.Keyword != "" {
if id, err1 := strconv.Atoi(arg.Keyword); err1 == nil {
r = r.WhereEq("id", id)
} else {
r = r.WhereLike([]string{"name"}, []string{arg.Keyword}, true, elastic.LikeLevelLow)
}
}
log.Info(fmt.Sprintf("%s/x/admin/search/query?%s", d.c.URL.ElasticHost, r.Params()))
guests = new(model.GuestSearchList)
err = r.Scan(c, guests)
if err != nil {
log.Error("GuestSearch() Scan() error(%v)", err)
}
return
}

View File

@@ -0,0 +1,59 @@
package dao
import (
"context"
"testing"
item "go-common/app/service/openplatform/ticket-item/api/grpc/v1"
. "github.com/smartystreets/goconvey/convey"
)
// TestAddGuest
func TestDao_AddGuest(t *testing.T) {
Convey("AddGuest", t, func() {
once.Do(startService)
res, err := d.AddGuest(context.TODO(), &item.GuestInfoRequest{
Name: "gotester",
GuestImg: "testerimg.jpg",
Description: "this is a description",
})
So(res, ShouldBeTrue)
So(err, ShouldBeNil)
})
}
// TestUpdateGuest
func TestDao_UpdateGuest(t *testing.T) {
Convey("UpdateGuest", t, func() {
once.Do(startService)
res, err := d.UpdateGuest(context.TODO(), &item.GuestInfoRequest{
ID: 55,
Name: "gotester22",
GuestImg: "testerimg.jpg2222",
Description: "this is a description222",
})
So(res, ShouldBeTrue)
So(err, ShouldBeNil)
})
}
// TestGuestStatus
func TestDao_GuestStatus(t *testing.T) {
Convey("GuestStatus", t, func() {
once.Do(startService)
res, err := d.GuestStatus(context.TODO(), 57, 1)
So(res, ShouldBeTrue)
So(err, ShouldBeNil)
})
}
// TestGetGuests
func TestDao_GetGuests(t *testing.T) {
Convey("GetGuest", t, func() {
once.Do(startService)
res, err := d.GetGuests(context.TODO(), 60)
So(res, ShouldNotBeNil)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,187 @@
package dao
import (
"context"
"encoding/json"
"go-common/library/cache/redis"
"go-common/app/service/openplatform/ticket-item/model"
"go-common/library/log"
)
// RawItems 取项目信息
func (d *Dao) RawItems(c context.Context, ids []int64) (info map[int64]*model.Item, err error) {
rows, err := d.db.Model(&model.Item{}).Where("id in (?)", ids).Rows()
info = make(map[int64]*model.Item)
if err != nil {
log.Error("QueryItem(%v) error(%v)", ids, err)
return
}
defer rows.Close()
for rows.Next() {
var item model.Item
err = d.db.ScanRows(rows, &item)
json.Unmarshal([]byte(item.PerformanceImage), &item.Img)
info[item.ID] = &item
}
return
}
// CacheItems 缓存取项目信息
func (d *Dao) CacheItems(c context.Context, ids []int64) (info map[int64]*model.Item, err error) {
var data [][]byte
keys := make([]interface{}, len(ids))
conn := d.redis.Get(c)
defer conn.Close()
keyPidMap := make(map[string]int64, len(ids))
for k, id := range ids {
key := keyItem(id)
if _, ok := keyPidMap[key]; !ok {
// duplicate id
keyPidMap[key] = id
keys[k] = key
}
}
log.Info("MGET %v", model.JSONEncode(keys))
if data, err = redis.ByteSlices(conn.Do("MGET", keys...)); err != nil {
log.Error("MGET ERR: %v", err)
return
}
info = make(map[int64]*model.Item)
for _, d := range data {
if d != nil {
item := &model.Item{}
json.Unmarshal(d, item)
info[item.ID] = item
}
}
return
}
// AddCacheItems 缓存取项目信息
func (d *Dao) AddCacheItems(c context.Context, info map[int64]*model.Item) (err error) {
conn := d.redis.Get(c)
defer func() {
conn.Flush()
conn.Close()
}()
var data []interface{}
var keys []string
for k, v := range info {
b, _ := json.Marshal(v)
key := keyItem(k)
keys = append(keys, key)
data = append(data, key, b)
}
log.Info("MSET %v", keys)
if err = conn.Send("MSET", data...); err != nil {
return
}
for i := 0; i < len(data); i += 2 {
conn.Send("EXPIRE", data[i], CacheTimeout)
}
return
}
// RawItemDetails 批量获取项目详情
func (d *Dao) RawItemDetails(c context.Context, ids []int64) (detail map[int64]*model.ItemDetail, err error) {
rows, err := d.db.Model(&model.ItemDetail{}).Where("project_id in (?)", ids).Rows()
detail = make(map[int64]*model.ItemDetail)
if err != nil {
log.Error("RawItemDetail(%v) error(%v)", ids, err)
return
}
defer rows.Close()
for rows.Next() {
var item model.ItemDetail
err = d.db.ScanRows(rows, &item)
detail[item.ProjectID] = &item
}
return
}
// CacheItemDetails 缓存批量获取项目详情
func (d *Dao) CacheItemDetails(c context.Context, ids []int64) (detail map[int64]*model.ItemDetail, err error) {
var data [][]byte
var keys []interface{}
keyPidMap := make(map[string]int64, len(ids))
for _, id := range ids {
key := keyItemDetail(id)
if _, ok := keyPidMap[key]; !ok {
// duplicate pid
keyPidMap[key] = id
keys = append(keys, key)
}
}
conn := d.redis.Get(c)
defer conn.Close()
log.Info("MGET %v", model.JSONEncode(keys))
if data, err = redis.ByteSlices((conn.Do("MGET", keys...))); err != nil {
return
}
detail = make(map[int64]*model.ItemDetail)
for _, d := range data {
if d != nil {
var v *model.ItemDetail
json.Unmarshal(d, &v)
detail[v.ProjectID] = v
}
}
return
}
// AddCacheItemDetails 缓存取项目详情
func (d *Dao) AddCacheItemDetails(c context.Context, detail map[int64]*model.ItemDetail) (err error) {
conn := d.redis.Get(c)
defer func() {
conn.Flush()
conn.Close()
}()
var keys []string
var data []interface{}
for k, v := range detail {
b, _ := json.Marshal(v)
key := keyItemDetail(k)
keys = append(keys, key)
data = append(data, key, b)
}
log.Info("MSET %v", keys)
if err = conn.Send("MSET", data...); err != nil {
return
}
for i := 0; i < len(data); i += 2 {
conn.Send("EXPIRE", data[i], CacheTimeout)
}
return
}
// DelItemCache 删除项目相关缓存
func (d *Dao) DelItemCache(c context.Context, ids []int64) (res bool, err error) {
var (
keys []interface{}
)
conn := d.redis.Get(c)
defer func() {
conn.Flush()
conn.Close()
}()
for _, id := range ids {
keys = append(keys, keyItem(id))
keys = append(keys, keyItemDetail(id))
}
log.Info("DEL %v", keys)
if err = conn.Send("DEL", keys...); err != nil {
return
}
return
}

View File

@@ -0,0 +1,32 @@
package dao
import (
"context"
. "github.com/smartystreets/goconvey/convey"
"go-common/app/service/openplatform/ticket-item/model"
"sync"
"testing"
)
var (
once sync.Once
d *Dao
ctx = context.TODO()
)
// Test_RawItems
func TestRawItems(t *testing.T) {
Convey("RawItems", t, func() {
once.Do(startService)
res, err := d.RawItems(ctx, model.DataIDs)
So(err, ShouldBeNil)
So(res, ShouldNotBeNil)
So(res, ShouldNotBeEmpty)
})
Convey("RawItems", t, func() {
once.Do(startService)
res, err := d.RawItems(ctx, model.NoDataIDs)
So(err, ShouldNotBeNil)
So(res, ShouldBeEmpty)
})
}

View File

@@ -0,0 +1,377 @@
package dao
import (
"context"
"encoding/json"
"go-common/app/service/openplatform/ticket-item/model"
"go-common/library/ecode"
"go-common/library/log"
"strconv"
"github.com/jinzhu/gorm"
)
// ProjectMainInfo 项目版本内容
type ProjectMainInfo struct {
Name string `json:"name"`
Type string `json:"type"`
Size string `json:"size"`
ProvinceID string `json:"province_id"`
CityID string `json:"city_id"`
DistrictID string `json:"district_id"`
VenueID string `json:"venue_id"`
PlaceID string `json:"place_id"`
StartTime int32 `json:"start_time"`
EndTime int32 `json:"end_time"`
Docs []ImgInfo `json:"docs"`
Screens []Screen `json:"screens"`
TicketsSingle []TicketSingle `json:"tickets_single"`
TicketsPass []TicketPass `json:"tickets_pass"`
TicketsAllPass []TicketPass `json:"tickets_allpass"`
PerformanceImg PerformanceImg `json:"performance_image"`
PerformanceDesc string `json:"performance_desc"`
SellingProp string `json:"selling_prop"`
TagIDs []string `json:"tag_ids"`
GuestIDs []int64 `json:"guest_ids"`
GuestImgs []GuestImg `json:"guest_imgs"`
CompID string `json:"comp_id"`
Label string `json:"label"`
SponsorType string `json:"sponsor_type"`
}
// ImgInfo 图片信息
type ImgInfo struct {
URL string `json:"url"`
Desc string `json:"desc"`
}
// GuestImg 嘉宾图片信息
type GuestImg struct {
ID int64 `json:"id"`
ImgURL string `json:"img_url"`
}
// Screen 场次信息
type Screen struct {
ScreenName string `json:"screen_name"`
ScreenStartTime int32 `json:"screen_start_time"`
ScreenEndTime int32 `json:"screen_end_time"`
ScreenType string `json:"screen_type"`
PickSeat int32 `json:"pick_seat"`
TicketType string `json:"ticket_type"`
DeliveryType string `json:"delivery_type"`
ScreenID string `json:"screen_id"`
}
// TicketSingle 单场票信息
type TicketSingle struct {
Name string `json:"name"`
Type string `json:"type"`
Color string `json:"color"`
BuyLimit string `json:"buy_limit"`
PayValue int64 `json:"pay_value"`
PayMethod string `json:"pay_method"`
Desc string `json:"descrp"`
TicketID string `json:"ticket_sc_id"`
BuyLimitNum []string `json:"buy_limit_num"`
}
// TicketPass 通票信息
type TicketPass struct {
Name string `json:"name"`
LinkTicket int32 `json:"link_ticket"`
LinkScreens []int32 `json:"link_screens"`
Color string `json:"color"`
BuyLimit string `json:"buy_limit"`
PayValue int64 `json:"pay_value"`
PayMethod string `json:"pay_method"`
Desc string `json:"descrp"`
TicketID string `json:"ticket_sc_id"`
BuyLimitNum []string `json:"buy_limit_num"`
}
// PerformanceImg 项目图片信息
type PerformanceImg struct {
First ImgInfo `json:"first"`
Banner ImgInfo `json:"banner"`
}
const (
// BuyNumLimit 默认限购值
BuyNumLimit = `{"per":8}`
)
// AddProject 项目初始化
func (d *Dao) AddProject(c context.Context, verID uint64) (pid int64, err error) {
var verExtInfo model.VersionExt
if dbErr := d.db.Where("ver_id = ?", verID).First(&verExtInfo).Error; dbErr != nil {
log.Error("获取项目版本详情失败:%s", dbErr)
return 0, dbErr
}
decodedMainInfo := d.GetDefaultMainInfo()
err = json.Unmarshal([]byte(verExtInfo.MainInfo), &decodedMainInfo)
if err != nil {
return
}
// 开启事务
tx := d.db.Begin()
// 创建新project
var project model.Item
if project, err = d.CreateProject(c, tx, verID, decodedMainInfo); err != nil {
return
}
pid = project.ID
if pid == 0 {
tx.Rollback()
log.Error("pid not initialized")
return 0, ecode.TicketPidIsEmpty
}
// 创建新项目详情在project_extra表
if err = d.CreateProjectExtInfo(c, tx, pid, decodedMainInfo.PerformanceDesc); err != nil {
return
}
// 创建场次screen
scIDList := make(map[int32]int64)
scStartTimes := make(map[int32]int32)
scEndTimes := make(map[int32]int32)
var screen model.Screen
for k, v := range decodedMainInfo.Screens {
screenType, _ := strconv.ParseInt(v.ScreenType, 10, 64)
ticketType, _ := strconv.ParseInt(v.TicketType, 10, 64)
deliveryType, _ := strconv.ParseInt(v.DeliveryType, 10, 64)
screen = model.Screen{
ProjectID: pid,
Name: v.ScreenName,
StartTime: v.ScreenStartTime,
EndTime: v.ScreenEndTime,
Type: int32(screenType),
TicketType: int32(ticketType),
DeliveryType: int32(deliveryType),
PickSeat: v.PickSeat,
ScreenType: 1, // 单场票场次
}
if screen, err = d.CreateOrUpdateScreen(c, tx, screen); err != nil {
return
}
// 获取的screen自增id复制到maininfo内
decodedMainInfo.Screens[k].ScreenID = strconv.FormatInt(screen.ID, 10)
scIDList[int32(k)] = screen.ID
scStartTimes[int32(k)] = screen.StartTime
scEndTimes[int32(k)] = screen.EndTime
}
// 创建票价
TkSingleIDList := make(map[int32]int64)
TkSingleTypeList := make(map[int32]int32)
var tkPrice model.TicketPrice
for k, v := range decodedMainInfo.TicketsSingle {
saleType, _ := strconv.ParseInt(v.Type, 10, 64)
buyLimit, _ := strconv.ParseInt(v.BuyLimit, 10, 64)
payMethod, _ := strconv.ParseInt(v.PayMethod, 10, 64)
ticketID, baseErr := model.GetTicketIDFromBase()
if ticketID == 0 || baseErr != nil {
tx.Rollback()
log.Error("baseCenter获取ticketID失败 ticketid:%s,baseErr:%s", ticketID, baseErr)
return 0, baseErr
}
tkPrice = model.TicketPrice{
ID: ticketID,
ProjectID: pid,
Desc: v.Name,
Type: 1, // 单场票
SaleType: int32(saleType),
Color: v.Color,
BuyLimit: int32(buyLimit),
PaymentMethod: int32(payMethod),
PaymentValue: v.PayValue,
DescDetail: v.Desc,
IsSale: 1, // 可售
IsRefund: -10, // 不可退
OriginPrice: -1, // 未設置
MarketPrice: -1,
SaleStart: TimeNull, // 0000-00-00 00:00:00
SaleEnd: TimeNull,
}
if tkPrice, err = d.CreateOrUpdateTkPrice(c, tx, tkPrice, 0); err != nil {
return
}
//票价限购
limitData := d.FormatByPrefix(v.BuyLimitNum, "buy_limit_")
if err = d.CreateOrUpdateTkPriceExtra(c, tx, limitData, ticketID, pid); err != nil {
return
}
// 获取的ticketPrice自增id复制到mainInfo内
decodedMainInfo.TicketsSingle[k].TicketID = strconv.FormatInt(tkPrice.ID, 10)
TkSingleIDList[int32(k)] = ticketID
TkSingleTypeList[int32(k)] = int32(saleType)
}
var passScID, allPassScID int64
// 创建通票场次
passScID, err = d.GetOrUpdatePassSc(c, tx, pid, decodedMainInfo.TicketsPass, scStartTimes, scEndTimes, scTypePass, 0)
if err != nil {
return
}
decodedMainInfo.TicketsPass, err = d.InsertOrUpdateTkPass(c, tx, pid, passScID, decodedMainInfo.TicketsPass, TkTypePass, scIDList, TkSingleIDList, TkSingleTypeList)
if err != nil {
return
}
// 创建联票场次
allPassScID, err = d.GetOrUpdatePassSc(c, tx, pid, decodedMainInfo.TicketsAllPass, scStartTimes, scEndTimes, scTypeAllPass, 0)
if err != nil {
return
}
decodedMainInfo.TicketsAllPass, err = d.InsertOrUpdateTkPass(c, tx, pid, allPassScID, decodedMainInfo.TicketsAllPass, TkTypeAllPass, scIDList, TkSingleIDList, TkSingleTypeList)
if err != nil {
return
}
// 创建标签
for _, tagID := range decodedMainInfo.TagIDs {
err = d.CreateTag(c, tx, pid, tagID)
if err != nil {
return
}
}
// 创建嘉宾
guestImgMap := make(map[int64]string)
// 组合嘉宾对应头像map
if decodedMainInfo.GuestIDs != nil {
if decodedMainInfo.GuestImgs == nil {
var guestInfoList []model.Guest
if err = d.db.Select("id, guest_img").Where("id IN (?)", decodedMainInfo.GuestIDs).Find(&guestInfoList).Error; err != nil {
log.Error("获取嘉宾信息失败:%s", err)
tx.Rollback()
return
}
for _, v := range guestInfoList {
guestImgMap[v.ID] = v.GuestImg
}
} else {
for _, v := range decodedMainInfo.GuestImgs {
guestImgMap[v.ID] = v.ImgURL
}
}
}
var position int64
for _, guestID := range decodedMainInfo.GuestIDs {
if err = tx.Create(&model.ProjectGuest{
ProjectID: pid,
GuestID: guestID,
Position: position,
GuestImg: guestImgMap[guestID],
}).Error; err != nil {
log.Error("项目嘉宾添加失败:%s", err)
tx.Rollback()
return
}
position++
}
// 将有场次票价id的mainInfo更新到version_ext
encodedMainInfo, jsonErr := json.Marshal(decodedMainInfo)
if jsonErr != nil {
log.Error("JSONEncode MainInfo失败")
tx.Rollback()
return pid, jsonErr
}
finalMainInfo := string(encodedMainInfo)
if len(finalMainInfo) > 4000 {
log.Error("信息量过大")
tx.Rollback()
return pid, ecode.TicketMainInfoTooLarge
}
// 编辑version_ext
err = tx.Model(&model.VersionExt{}).Where("ver_id = ? and type = ?", verID, 1).Update("main_info", finalMainInfo).Error
if err != nil {
tx.Rollback()
log.Error("更新versionext失败: %s", err)
}
// 提交事务
tx.Commit()
return pid, nil
}
// CreateProject 灌入项目表
func (d *Dao) CreateProject(c context.Context, tx *gorm.DB, verID uint64, decodedMainInfo ProjectMainInfo) (project model.Item, err error) {
// 创建新project
venueID, _ := strconv.ParseInt(decodedMainInfo.VenueID, 10, 64)
placeID, _ := strconv.ParseInt(decodedMainInfo.PlaceID, 10, 64)
compID, _ := strconv.ParseInt(decodedMainInfo.CompID, 10, 64)
projectType, _ := strconv.ParseInt(decodedMainInfo.Type, 10, 64)
projectSponsorType, _ := strconv.ParseInt(decodedMainInfo.SponsorType, 10, 64)
performanceImg, _ := json.Marshal(decodedMainInfo.PerformanceImg)
projectInfo := model.Item{
Name: decodedMainInfo.Name,
StartTime: decodedMainInfo.StartTime,
EndTime: decodedMainInfo.EndTime,
VenueID: venueID,
PlaceID: placeID,
CompID: compID,
PerformanceImage: string(performanceImg),
TicketDesc: decodedMainInfo.SellingProp,
Type: int32(projectType),
VerID: verID,
SponsorType: int32(projectSponsorType),
Label: decodedMainInfo.Label,
BuyNumLimit: BuyNumLimit,
IsSale: 1, // 默认值可售
ExpressFee: -2, //默认值
}
if err = tx.Create(&projectInfo).Error; err != nil {
log.Error("新建项目失败:%s", err)
tx.Rollback()
return model.Item{}, err
}
return projectInfo, nil
}
// CreateProjectExtInfo 灌入项目详情表
func (d *Dao) CreateProjectExtInfo(c context.Context, tx *gorm.DB, projectID int64, performanceDesc string) (err error) {
projectExtInfo := model.ItemDetail{
ProjectID: projectID,
PerformanceDesc: performanceDesc,
}
if err = tx.Create(&projectExtInfo).Error; err != nil {
log.Error("新建项目详情失败:%s", err)
tx.Rollback()
return err
}
return nil
}
// GetDefaultMainInfo 获取初始化数组的mainInfo
func (d *Dao) GetDefaultMainInfo() ProjectMainInfo {
return ProjectMainInfo{
Docs: []ImgInfo{},
Screens: []Screen{},
TicketsSingle: []TicketSingle{},
TicketsPass: []TicketPass{},
TicketsAllPass: []TicketPass{},
TagIDs: []string{},
GuestIDs: []int64{},
GuestImgs: []GuestImg{},
}
}

View File

@@ -0,0 +1,37 @@
package dao
import (
"go-common/app/service/openplatform/ticket-item/model"
"time"
)
const (
// StatusWaitShelf 待上架
StatusWaitShelf = 1
// StatusUpShelf 已上架
StatusUpShelf = 2
)
// HasPromotion 检查场次id或者票价id下是否有未开售,售卖中的待上架和已上架拼团 checkType 1-场次id 2-票价id
func (d *Dao) HasPromotion(ids []int64, checkType int32) bool {
if ids == nil {
return false
}
status := []int{StatusWaitShelf, StatusUpShelf}
whereStr := "(begin_time > ? OR (begin_time <= ? AND end_time > ?)) AND status IN (?) AND "
if checkType == 1 {
// screen
whereStr += "extra IN (?)"
} else {
// sku
whereStr += "sku_id IN (?)"
}
var count int64
currTime := time.Now().Unix()
d.db.Model(&model.Promotion{}).Where(whereStr, currTime, currTime, currTime, status, ids).Count(&count)
return count > 0
}

View File

@@ -0,0 +1,17 @@
package dao
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
// TestHasPromotion
func TestDao_HasPromotion(t *testing.T) {
Convey("HasPromotion", t, func() {
once.Do(startService)
res := d.HasPromotion([]int64{78}, 1)
So(res, ShouldBeFalse)
})
}

View File

@@ -0,0 +1,353 @@
package dao
import (
"context"
"encoding/json"
"go-common/app/service/openplatform/ticket-item/model"
"go-common/library/cache/redis"
"go-common/library/ecode"
"go-common/library/log"
"time"
"github.com/jinzhu/gorm"
)
const (
// TypeWithoutSeat 场次类型 站票
TypeWithoutSeat = 2
// TicketTypeElec 场次票类型 电子票
TicketTypeElec = 2
// scTypeNormal 普通场次
scTypeNormal = 1
// scTypePass 通票场次
scTypePass = 2
// scTypeAllPass 联票场次
scTypeAllPass = 3
)
var scTypeName = map[int32]string{
scTypeNormal: "普通场次",
scTypePass: "通票",
scTypeAllPass: "联票",
}
// RawScListByItem 批量取项目场次
func (d *Dao) RawScListByItem(c context.Context, ids []int64) (info map[int64][]*model.Screen, err error) {
info = make(map[int64][]*model.Screen)
rows, err := d.db.Model(&model.Screen{}).Where("project_id in (?)", ids).Rows()
if err != nil {
log.Error("RawScListByItem(%v) error(%v)", ids, err)
return
}
defer rows.Close()
for rows.Next() {
var sc model.Screen
err = d.db.ScanRows(rows, &sc)
if err != nil {
log.Error("RawScListByItem(%v) scan error(%v)", ids, err)
return
}
info[sc.ProjectID] = append(info[sc.ProjectID], &sc)
}
return
}
// CacheScListByItem 缓存取项目票价
func (d *Dao) CacheScListByItem(c context.Context, ids []int64) (info map[int64][]*model.Screen, err error) {
var keys []interface{}
keyPidMap := make(map[string]int64, len(ids))
for _, id := range ids {
key := keyItemScreen(id)
if _, ok := keyPidMap[key]; !ok {
// duplicate mid
keyPidMap[key] = id
keys = append(keys, key)
}
}
conn := d.redis.Get(c)
defer conn.Close()
var data [][]byte
log.Info("MGET %v", model.JSONEncode(keys))
if data, err = redis.ByteSlices(conn.Do("mget", keys...)); err != nil {
log.Error("ItemScList MGET %v ERR: %v", model.JSONEncode(keys), err)
return
}
info = make(map[int64][]*model.Screen)
for _, d := range data {
if d != nil {
var scs []*model.Screen
json.Unmarshal(d, &scs)
info[scs[0].ProjectID] = scs
}
}
return
}
// AddCacheScListByItem 为项目场次添加缓存
func (d *Dao) AddCacheScListByItem(c context.Context, info map[int64][]*model.Screen) (err error) {
conn := d.redis.Get(c)
defer func() {
conn.Flush()
conn.Close()
}()
var data []interface{}
var keys []string
for k, v := range info {
b, _ := json.Marshal(v)
key := keyItemScreen(k)
keys = append(keys, key)
data = append(data, key, b)
}
log.Info("MSET %v", keys)
if err = conn.Send("MSET", data...); err != nil {
return
}
for i := 0; i < len(data); i += 2 {
conn.Send("EXPIRE", data[i], CacheTimeout)
}
return
}
// RawScList 批量取场次
func (d *Dao) RawScList(c context.Context, ids []int64) (info map[int64]*model.Screen, err error) {
info = make(map[int64]*model.Screen)
rows, err := d.db.Model(&model.Screen{}).Where("id in (?)", ids).Rows()
if err != nil {
log.Error("RawScList(%v) error(%v)", ids, err)
return
}
defer rows.Close()
for rows.Next() {
var sc model.Screen
err = d.db.ScanRows(rows, &sc)
if err != nil {
log.Error("RawScList(%v) scan error(%v)", ids, err)
return
}
info[sc.ID] = &sc
}
return
}
// CacheScList 缓存批量取场次
func (d *Dao) CacheScList(c context.Context, ids []int64) (info map[int64]*model.Screen, err error) {
var keys []interface{}
keyPidMap := make(map[string]int64, len(ids))
for _, id := range ids {
key := keyScreen(id)
if _, ok := keyPidMap[key]; !ok {
// duplicate id
keyPidMap[key] = id
keys = append(keys, key)
}
}
conn := d.redis.Get(c)
defer conn.Close()
var data [][]byte
log.Info("MGET %v", model.JSONEncode(keys))
if data, err = redis.ByteSlices(conn.Do("mget", keys...)); err != nil {
log.Error("ScList MGET %v ERR: %v", model.JSONEncode(keys), err)
return
}
info = make(map[int64]*model.Screen)
for _, d := range data {
if d != nil {
var sc model.Screen
json.Unmarshal(d, &sc)
info[sc.ID] = &sc
}
}
return
}
// AddCacheScList 为场次添加缓存
func (d *Dao) AddCacheScList(c context.Context, info map[int64]*model.Screen) (err error) {
conn := d.redis.Get(c)
defer func() {
conn.Flush()
conn.Close()
}()
var data []interface{}
var keys []string
for k, v := range info {
b, _ := json.Marshal(v)
key := keyScreen(k)
keys = append(keys, key)
data = append(data, key, b)
}
log.Info("MSET %v", keys)
if err = conn.Send("MSET", data...); err != nil {
return
}
for i := 0; i < len(data); i += 2 {
conn.Send("EXPIRE", data[i], CacheTimeout)
}
return
}
// CreateOrUpdateScreen 创建或更新场次
func (d *Dao) CreateOrUpdateScreen(c context.Context, tx *gorm.DB, screenInfo model.Screen) (model.Screen, error) {
if screenInfo.ID == 0 {
// create
if err := tx.Create(&screenInfo).Error; err != nil {
log.Error("创建场次失败:%s", err)
tx.Rollback()
return model.Screen{}, err
}
} else {
// update
if err := tx.Model(&model.Screen{}).Where("id = ?", screenInfo.ID).Updates(map[string]interface{}{
"name": screenInfo.Name,
"status": screenInfo.Status,
"type": screenInfo.Type,
"ticket_type": screenInfo.TicketType,
"screen_type": screenInfo.ScreenType,
"delivery_type": screenInfo.DeliveryType,
"pick_seat": screenInfo.PickSeat,
"start_time": screenInfo.StartTime,
"end_time": screenInfo.EndTime,
"project_id": screenInfo.ProjectID,
}).Error; err != nil {
log.Error("更新场次失败:%s", err)
tx.Rollback()
return model.Screen{}, err
}
}
return screenInfo, nil
}
// GetOrUpdatePassSc 更新或新建通联票场次 返回id
func (d *Dao) GetOrUpdatePassSc(c context.Context, tx *gorm.DB, pid int64, tksPass []TicketPass, scStartTimes map[int32]int32,
scEndTimes map[int32]int32, scType int32, opType int32) (int64, error) {
var passScID int64
scTime, err := d.CalPassScTime(scStartTimes, scEndTimes, tksPass)
if err != nil {
tx.Rollback()
return 0, err
}
var screen model.Screen
if d.db.Where("project_id = ? and screen_type = ? and deleted_at = 0", pid, scType).First(&screen).RecordNotFound() {
// 不存在场次并且有票种存在则新建一个
if len(tksPass) > 0 {
passScreen := model.Screen{
ProjectID: pid,
Name: scTypeName[scType],
StartTime: scTime[0],
EndTime: scTime[1],
Type: TypeWithoutSeat, // 站票
TicketType: TicketTypeElec, // 电子票
DeliveryType: 1, // 默认不配送
ScreenType: scType,
Status: opType,
}
if err := tx.Create(&passScreen).Error; err != nil {
log.Error("创建通票或联票场次失败:%s", err)
tx.Rollback()
return 0, err
}
passScID = passScreen.ID
}
} else {
if len(tksPass) > 0 {
// update
if err := tx.Model(&model.Screen{}).Where("id = ?", screen.ID).Updates(map[string]interface{}{
"status": opType,
"start_time": scTime[0],
"end_time": scTime[1],
}).Error; err != nil {
log.Error("更新通票联票场次失败:%s", err)
tx.Rollback()
return 0, err
}
passScID = screen.ID
} else {
// 如果没有通票/联票 删除通票/联票场次
if err := d.DelScreen(c, tx, []int64{screen.ID}, nil, pid); err != nil {
return 0, err
}
passScID = screen.ID
}
}
return passScID, nil
}
// DelScreen 删除场次
func (d *Dao) DelScreen(c context.Context, tx *gorm.DB, oldIDs []int64, newIDs []int64, pid int64) error {
delIDs, _ := model.ClassifyIDs(oldIDs, newIDs)
for _, delID := range delIDs {
if !d.CanDelScreen(delID) {
tx.Rollback()
return ecode.TicketCannotDelSc
}
// 删除场次前把改场次下的票价一起删除
priceIDs, err := d.GetPriceIDs(delID, 1)
if err != nil {
return err
}
if err := d.DelTicket(c, tx, priceIDs, nil, pid, true); err != nil {
return err
}
// 删除场次
if err := tx.Exec("UPDATE screen SET deleted_at=? WHERE id = ? AND project_id = ?", time.Now().Format("2006-01-02 15:04:05"), delID, pid).Error; err != nil {
log.Error("删除场次失败:%s", err)
tx.Rollback()
return err
}
}
return nil
}
// CalPassScTime 获取通联场次的开始结束时间, 取关联场次时间的极值。返回[0]=starttime [1]=endtime
func (d *Dao) CalPassScTime(scStartTimes map[int32]int32, scEndTimes map[int32]int32, tksPass []TicketPass) ([]int32, error) {
var startTimes, endTimes, currStartTimes, currEndTimes []int32
for _, v := range tksPass {
currStartTimes = []int32{}
currEndTimes = []int32{}
for _, v2 := range v.LinkScreens {
if _, ok := scStartTimes[v2]; !ok {
log.Error("关联的场次开始时间不存在")
return []int32{}, ecode.TicketLkScTimeNotFound
}
if _, ok := scEndTimes[v2]; !ok {
log.Error("关联的场次结束时间不存在")
return []int32{}, ecode.TicketLkScTimeNotFound
}
currStartTimes = append(currStartTimes, scStartTimes[v2])
currEndTimes = append(currEndTimes, scEndTimes[v2])
}
startTimes = append(startTimes, model.Min(currStartTimes))
endTimes = append(endTimes, model.Max(currEndTimes))
}
return []int32{model.Min(startTimes), model.Max(endTimes)}, nil
}
// CanDelScreen 检查是否可以删除场次
func (d *Dao) CanDelScreen(id int64) bool {
var priceIDs []int64
// 获取场次下 所有票价id
var prices []model.TicketPrice
if err := d.db.Select("id").Where("screen_id = ? and deleted_at = 0", id).Find(&prices).Error; err != nil {
log.Error("获取场次下所有票价id失败:%s", err)
return false
}
for _, v := range prices {
priceIDs = append(priceIDs, v.ID)
}
if d.HasPromotion(priceIDs, 2) || d.StockChanged(priceIDs) {
log.Error("场次的票价下存在拼团或者库存有变动")
return false
}
return true
}

View File

@@ -0,0 +1,22 @@
package dao
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
// TestDao_CalPassScTime
func TestDao_CalPassScTime(t *testing.T) {
Convey("CalPassScTime", t, func() {
once.Do(startService)
startTimes := map[int32]int32{0: 1530860100, 1: 1530860122}
endTimes := map[int32]int32{0: 1533365702, 1: 1533970523}
var tksPass []TicketPass
tksPass = append(tksPass, TicketPass{Name: "test1", LinkScreens: []int32{0, 1}})
tksPass = append(tksPass, TicketPass{Name: "test2", LinkScreens: []int32{0, 1}})
res, err := d.CalPassScTime(startTimes, endTimes, tksPass)
So(res, ShouldNotBeNil)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,268 @@
package dao
import (
"context"
"fmt"
"strings"
"time"
"go-common/app/service/openplatform/ticket-item/model"
"go-common/library/ecode"
"go-common/library/log"
"github.com/jinzhu/gorm"
)
// 管理area_seats、area_seatmap、seat_set、seat_order座位相关表
// area_seats为区域座位表每个座位一行
// area_seatmap为区域座位图表每个区域一行与area_seats对应
// seat_order为场次的座位订单表每个座位一行创建场次座位图时生成
// seat_set为场次的座位设置图表每个区域一行基于area_seatmap生成针对场次含有不同的票价标记
const (
// StatusCansale 可售
StatusCansale = 0
// StatusIssue 已出票
StatusIssue = 2
// StatusLocked 已锁定
StatusLocked = 3
// StatusBooked 已预订
StatusBooked = 4
)
// TxUpdateSeat 编辑区域座位信息(事务)
func (d *Dao) TxUpdateSeat(c context.Context, tx *gorm.DB, area *model.Area) (err error) {
if err = tx.Table("area").Where("id = ?", area.ID).Updates(
map[string]interface{}{
"seats_num": area.SeatsNum,
"width": area.Width,
"height": area.Height,
"deleted_status": area.DeletedStatus,
"col_start": area.ColStart,
"col_type": area.ColType,
"col_direction": area.ColDirection,
"row_list": area.RowList,
"seat_start": area.SeatStart,
}).Error; err != nil {
log.Error("更新区域座位信息ID:%d失败:%s", area.ID, err)
err = ecode.NotModified
return
}
return
}
// TxGetAreaSeats 批量获取区域对应的区域座位信息(事务)
func (d *Dao) TxGetAreaSeats(c context.Context, tx *gorm.DB, area int64) (areaSeats []*model.AreaSeats, err error) {
if err = tx.Find(&areaSeats, "area = ?", area).Error; err != nil {
log.Error("批量获取区域座位信息area:%d失败:%s", area, err)
return
}
return
}
// TxBatchAddAreaSeats 批量添加区域座位信息(事务)
func (d *Dao) TxBatchAddAreaSeats(c context.Context, tx *gorm.DB, areaSeats []*model.AreaSeats) (err error) {
if len(areaSeats) == 0 {
return
}
var values = make([]string, len(areaSeats))
for i, areaSeat := range areaSeats {
values[i] = fmt.Sprintf("(%d,%d,'%s','%s',%d,%d)", areaSeat.X, areaSeat.Y, areaSeat.Label, areaSeat.Bgcolor, areaSeat.Area, 0)
}
var sql = fmt.Sprintf("INSERT INTO `area_seats` (`x`, `y`, `label`, `bgcolor`, `area`, `dstatus`) VALUES %s;", strings.Join(values, ","))
if err = tx.Exec(sql).Error; err != nil {
log.Error("批量添加区域座位信息(%s失败:%s", sql, err)
err = ecode.NotModified
return
}
return
}
// TxBatchDeleteAreaSeats 软删除区域对应的区域座位表信息
func (d *Dao) TxBatchDeleteAreaSeats(c context.Context, tx *gorm.DB, area int64) (err error) {
if err = tx.Table("area_seats").Where("area = ?", area).Updates(
map[string]interface{}{
"dstatus": 1,
}).Error; err != nil {
log.Error("删除区域座位信息area:%d失败:%s", area, err)
err = ecode.NotModified
return
}
return
}
// TxBatchRecoverAreaSeats 恢复软删除的区域座位表信息
func (d *Dao) TxBatchRecoverAreaSeats(c context.Context, tx *gorm.DB, ids []int64) (err error) {
if err = tx.Table("area_seats").Where("id in (?)", ids).Updates(
map[string]interface{}{
"dstatus": 0,
}).Error; err != nil {
log.Error("批量恢复区域座位信息失败:%s", err)
err = ecode.NotModified
return
}
return
}
// TxRawAreaSeatmap 获取区域座位图信息(事务)
func (d *Dao) TxRawAreaSeatmap(c context.Context, tx *gorm.DB, id int64) (areaSeatmap *model.AreaSeatmap, err error) {
areaSeatmap = new(model.AreaSeatmap)
if err = tx.First(&areaSeatmap, id).Error; err != nil {
log.Error("获取区域座位信息ID:%d失败:%s", id, err)
return
}
return
}
// TxSaveAreaSeatmap 添加/修改区域座位图信息(事务)
func (d *Dao) TxSaveAreaSeatmap(c context.Context, tx *gorm.DB, areaSeatmap *model.AreaSeatmap) (err error) {
if res := tx.Save(areaSeatmap); res.Error != nil {
log.Error("添加区域座位信息失败:%s", res.Error)
err = ecode.NotModified
return
}
return
}
// TxGetSeatChart 根据场次ID和区域ID查询ID和票价设置图事务
func (d *Dao) TxGetSeatChart(c context.Context, tx *gorm.DB, screen int64, area int64) (seatSet *model.SeatSet, err error) {
seatSet = new(model.SeatSet)
if res := tx.Select("id, seat_chart").Where("screen_id = ? AND area_id = ? AND deleted_at = 0", screen, area).First(seatSet); res.Error != nil {
if res.RecordNotFound() {
return
}
err = res.Error
log.Error("TxGetSeatChart error(%v)", err)
}
return
}
// TxGetSeatCharts 根据场次ID和多个区域ID批量查询多个票价设置ID和票价设置图事务
func (d *Dao) TxGetSeatCharts(c context.Context, tx *gorm.DB, screen int64, areas []int64) (seatSets []*model.SeatSet, err error) {
if err = tx.Select("id, seat_chart").Where("screen_id = ? AND area_id in (?) AND deleted_at = 0", screen, areas).Find(&seatSets).Error; err != nil {
log.Error("TxGetSeatCharts error(%v)", err)
}
return
}
// TxGetSeatSets 根据区域ID批量查询多个票价设置ID和场次ID事务
func (d *Dao) TxGetSeatSets(c context.Context, tx *gorm.DB, area int64) (seatSets []*model.SeatSet, err error) {
if err = tx.Select("id, screen_id").Where("area_id = ? AND deleted_at = 0", area).Find(&seatSets).Error; err != nil {
log.Error("TxGetSeatSets error(%v)", err)
}
return
}
// TxAddSeatChart 添加票价设置图(事务)
func (d *Dao) TxAddSeatChart(c context.Context, tx *gorm.DB, seatSet *model.SeatSet) (err error) {
if res := tx.Create(seatSet); res.Error != nil {
log.Error("添加票价设置图失败:%s", res.Error)
err = ecode.NotModified
return
}
return
}
// TxUpdateSeatChart 更新票价设置图(事务)
func (d *Dao) TxUpdateSeatChart(c context.Context, tx *gorm.DB, id int64, seatChart string) (err error) {
if err = tx.Table("seat_set").Where("id = ? AND deleted_at = 0", id).Updates(
map[string]interface{}{
"seat_chart": seatChart,
}).Error; err != nil {
log.Error("更新票价设置图ID:%d失败:%s", id, err)
}
return
}
// TxClearSeatCharts 清空票价设置图(事务)
func (d *Dao) TxClearSeatCharts(c context.Context, tx *gorm.DB, ids []int64) (err error) {
if err = tx.Table("seat_set").Where("id IN (?) AND deleted_at = 0", ids).Updates(
map[string]interface{}{
"seat_chart": "",
}).Error; err != nil {
log.Error("清空票价设置图失败:%s", err)
}
return
}
// TxGetUnsaleableSeatOrders 根据场次和区域ID查询不可售座位订单信息事务
func (d *Dao) TxGetUnsaleableSeatOrders(c context.Context, tx *gorm.DB, screen int64, area int64) (seatOrders []*model.SeatOrder, err error) {
if err = tx.Unscoped().Where("screen_id = ? AND area_id = ? AND status in (?) AND deleted_at = 0", screen, area, []int32{StatusIssue, StatusLocked, StatusBooked}).Find(&seatOrders).Error; err != nil {
log.Error("TxGetUnsaleableSeatOrders error(%v)", err)
}
return
}
// TxGetSaleableSeatOrders 根据场次ID和票价ID查询可售座位订单ID和区域ID信息事务
func (d *Dao) TxGetSaleableSeatOrders(c context.Context, tx *gorm.DB, screen int64, price int64) (seatOrders []*model.SeatOrder, err error) {
if err = tx.Select("id, area_id").Unscoped().Where("screen_id = ? AND price_id = ? AND status = ? AND deleted_at = 0", screen, price, StatusCansale).Find(&seatOrders).Error; err != nil {
log.Error("TxGetSaleableSeatOrders error(%v)", err)
}
return
}
// TxBatchDelUnsoldSeatOrders 根据区域ID清空未售出的座位订单事务
func (d *Dao) TxBatchDelUnsoldSeatOrders(c context.Context, tx *gorm.DB, area int64) (err error) {
if err = tx.Table("seat_order").Where("area_id = ? AND status IN (?) AND deleted_at = 0", area, []int32{StatusCansale, StatusLocked}).Updates(
map[string]interface{}{
"deleted_at": time.Now(),
}).Error; err != nil {
log.Error("批量删除座位订单信息失败:%s", err)
err = ecode.NotModified
return
}
return
}
// TxAddSeatOrder 添加座位订单信息(事务,暂未使用)
func (d *Dao) TxAddSeatOrder(c context.Context, tx *gorm.DB, seatOrder *model.SeatOrder) (err error) {
if res := tx.Create(seatOrder); res.Error != nil {
log.Error("添加座位订单信息失败:%s", res.Error)
err = ecode.NotModified
return
}
return
}
// TxUpdateSeatOrder 编辑座位订单信息(事务,暂未使用)
// TODO: 具体字段未指定
func (d *Dao) TxUpdateSeatOrder(c context.Context, tx *gorm.DB, seatOrder *model.SeatOrder) (err error) {
if err = tx.Table("seat_order").Where("id = ? AND deleted_at = 0", seatOrder.ID).Updates(
map[string]interface{}{}).Error; err != nil {
log.Error("更新座位订单信息ID:%d失败:%s", seatOrder.ID, err)
err = ecode.NotModified
return
}
return
}
// TxBatchDeleteSeatOrder 批量软删除座位订单信息(事务)
func (d *Dao) TxBatchDeleteSeatOrder(c context.Context, tx *gorm.DB, ids []int64) (err error) {
if err = tx.Table("seat_order").Where("id in (?)", ids).Updates(
map[string]interface{}{
"deleted_at": time.Now(),
}).Error; err != nil {
log.Error("批量删除座位订单信息失败:%s", err)
err = ecode.NotModified
return
}
return
}
// TxBatchAddSeatOrder 批量添加座位订单(事务)
func (d *Dao) TxBatchAddSeatOrder(c context.Context, tx *gorm.DB, seatOrders []*model.SeatOrder) (err error) {
if len(seatOrders) == 0 {
return
}
var values = make([]string, len(seatOrders))
for i, so := range seatOrders {
values[i] = fmt.Sprintf("(%d,%d,%d,%d,%d,%d)", so.AreaID, so.ScreenID, so.Row, so.Col, so.PriceID, so.Price)
}
var sql = fmt.Sprintf("INSERT INTO `seat_order` (`area_id`, `screen_id`, `row`, `col`, `price_id`, `price`) VALUES %s;", strings.Join(values, ","))
if err = tx.Exec(sql).Error; err != nil {
log.Error("批量添加区域座位信息(%s失败:%s", sql, err)
err = ecode.NotModified
return
}
return
}

View File

@@ -0,0 +1,289 @@
package dao
import (
"context"
"go-common/app/service/openplatform/ticket-item/model"
"strconv"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
// TestDaoAreaSeats
func TestDaoAreaSeats(t *testing.T) {
var id int64
Convey("TxGetAreaSeats", t, func() {
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
So(tx, ShouldNotBeNil)
area := &model.Area{
AID: "test" + strconv.FormatInt(time.Now().Unix(), 10),
Name: "可删",
Place: 132,
}
err := d.TxAddArea(c, tx, area)
So(err, ShouldBeNil)
So(area.ID, ShouldNotEqual, 0)
id = area.ID
res, err := d.TxGetAreaSeats(c, tx, id)
So(err, ShouldBeNil)
So(len(res), ShouldEqual, 0)
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
})
var seatsIDs []int64
Convey("TxBatchAddAreaSeats", t, func() {
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
So(tx, ShouldNotBeNil)
seatsArr := []*model.AreaSeats{
{
X: 1,
Y: 2,
Label: "3",
Bgcolor: "#FFFFFF",
Area: id,
Dstatus: 0,
},
{
X: 2,
Y: 2,
Label: "4",
Bgcolor: "#FFFFFF",
Area: id,
Dstatus: 0,
},
}
err := d.TxBatchAddAreaSeats(c, tx, seatsArr)
So(err, ShouldBeNil)
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
tx, _ = d.BeginTran(c)
So(tx, ShouldNotBeNil)
res, err := d.TxGetAreaSeats(c, tx, id)
So(err, ShouldBeNil)
So(len(res), ShouldEqual, 2)
seatsIDs = []int64{res[0].ID, res[1].ID}
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
})
Convey("TxBatchDeleteAreaSeats", t, func() {
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
So(tx, ShouldNotBeNil)
err := d.TxBatchDeleteAreaSeats(c, tx, id)
So(err, ShouldBeNil)
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
tx, _ = d.BeginTran(c)
So(tx, ShouldNotBeNil)
res, err := d.TxGetAreaSeats(c, tx, id)
So(err, ShouldBeNil)
So(len(res), ShouldEqual, 2)
So(res[0].Dstatus, ShouldEqual, 1)
So(res[1].Dstatus, ShouldEqual, 1)
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
})
Convey("TxBatchRecoverAreaSeats", t, func() {
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
So(tx, ShouldNotBeNil)
err := d.TxBatchRecoverAreaSeats(c, tx, seatsIDs)
So(err, ShouldBeNil)
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
tx, _ = d.BeginTran(c)
So(tx, ShouldNotBeNil)
res, err := d.TxGetAreaSeats(c, tx, id)
So(err, ShouldBeNil)
So(len(res), ShouldEqual, 2)
So(res[0].Dstatus, ShouldEqual, 0)
So(res[1].Dstatus, ShouldEqual, 0)
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
})
}
// TestDaoAreaSeats
func TestDaoAreaSeatmap(t *testing.T) {
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
Convey("TxRawAreaSeatmap", t, func() {
So(tx, ShouldNotBeNil)
asm, err := d.TxRawAreaSeatmap(c, tx, 239)
So(asm.ID, ShouldEqual, 239)
So(asm.SeatMap, ShouldNotBeBlank)
So(err, ShouldBeNil)
})
Convey("TxRawAreaSeatmap", t, func() {
So(tx, ShouldNotBeNil)
asm := &model.AreaSeatmap{
ID: 239,
SeatMap: "[\"abc\"]",
}
err := d.TxSaveAreaSeatmap(c, tx, asm)
So(err, ShouldBeNil)
asm, err = d.TxRawAreaSeatmap(c, tx, 239)
So(asm.ID, ShouldEqual, 239)
So(asm.SeatMap, ShouldEqual, "[\"abc\"]")
So(err, ShouldBeNil)
})
tx.Rollback()
}
func TestSeatSet(t *testing.T) {
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
Convey("TxGetSeatChart", t, func() {
So(tx, ShouldNotBeNil)
sc, err := d.TxGetSeatChart(c, tx, 1633, 239)
So(sc.ID, ShouldBeZeroValue)
So(sc.SeatChart, ShouldBeEmpty)
So(err, ShouldBeNil)
})
Convey("TxGetSeatCharts", t, func() {
So(tx, ShouldNotBeNil)
scs, err := d.TxGetSeatCharts(c, tx, 1633, []int64{239})
So(len(scs), ShouldBeZeroValue)
So(err, ShouldBeNil)
})
Convey("TxAddSeatChart", t, func() {
So(tx, ShouldNotBeNil)
err := d.TxAddSeatChart(c, tx, &model.SeatSet{
AreaID: 239,
ScreenID: 1633,
})
So(err, ShouldBeNil)
})
var id int64
Convey("TxGetSeatCharts", t, func() {
So(tx, ShouldNotBeNil)
scs, err := d.TxGetSeatCharts(c, tx, 1633, []int64{239})
So(len(scs), ShouldEqual, 1)
So(scs[0].SeatChart, ShouldBeEmpty)
So(err, ShouldBeNil)
id = scs[0].ID
})
Convey("TxUpdateSeatChart", t, func() {
So(tx, ShouldNotBeNil)
err := d.TxUpdateSeatChart(c, tx, id, "[\"_______aaaaaaaaaaaaa\",\"_______aaaaaaaaaaaaa\",\"______aaaaaaaaaaaaaa\",\"______aaaaaaaaaaaaaa\",\"_____aaaaaaaaaaaaaaa\",\"_____aaaaaaaaaaaaaaa\",\"____aaaaaaaaaaaaaaaa\",\"____aaaaaaaaaaaaaaaa\",\"___aaaaaaaaaaaaaaaaa\",\"___aaaaaaaaaaaaaaaaa\",\"__aaaaaaaaaaaaaaaaaa\",\"__aaaaaaaaaaaaaaaaaa\",\"aaaaaaaaaaaaaaaaaaaa\",\"aaaaaaaaaaaaaaaaaaaa\",\"___aaaaaaaaaaaaaa___\",\"_aaaaaaaaaaaaaaaaaa_\"]")
So(err, ShouldBeNil)
})
Convey("TxGetSeatChart", t, func() {
So(tx, ShouldNotBeNil)
sc, err := d.TxGetSeatChart(c, tx, 1633, 239)
So(sc.ID, ShouldEqual, id)
So(sc.SeatChart, ShouldNotBeEmpty)
So(err, ShouldBeNil)
})
tx.Rollback()
}
// TestSeatOrder
func TestSeatOrder(t *testing.T) {
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
Convey("TxBatchAddSeatOrder", t, func() {
So(tx, ShouldNotBeNil)
err := d.TxBatchAddSeatOrder(c, tx, []*model.SeatOrder{
{
AreaID: 239,
ScreenID: 1633,
Row: 1,
Col: 1,
PriceID: 36093,
Price: 123456,
}, {
AreaID: 239,
ScreenID: 1633,
Row: 2,
Col: 2,
PriceID: 36093,
Price: 123456,
}, {
AreaID: 239,
ScreenID: 1633,
Row: 3,
Col: 3,
PriceID: 36093,
Price: 123456,
},
})
So(err, ShouldBeNil)
})
Convey("TxGetUnsaleableSeatOrders", t, func() {
res, err := d.TxGetUnsaleableSeatOrders(c, tx, 1633, 239)
So(err, ShouldBeNil)
So(len(res), ShouldEqual, 0)
})
var ids [3]int64
Convey("TxGetSaleableSeatOrders", t, func() {
res, err := d.TxGetSaleableSeatOrders(c, tx, 1633, 36093)
So(err, ShouldBeNil)
So(len(res), ShouldEqual, 3)
for i, so := range res {
ids[i] = so.ID
}
})
Convey("TxBatchDeleteSeatOrder", t, func() {
err := d.TxBatchDeleteSeatOrder(c, tx, ids[:])
So(err, ShouldBeNil)
})
tx.Commit()
tx, _ = d.BeginTran(c)
Convey("TxGetSaleableSeatOrders", t, func() {
res, err := d.TxGetSaleableSeatOrders(c, tx, 1633, 36093)
So(err, ShouldBeNil)
So(len(res), ShouldEqual, 0)
})
tx.Rollback()
}

View File

@@ -0,0 +1,27 @@
package dao
import (
"go-common/app/service/openplatform/ticket-item/model"
"go-common/library/log"
)
// StockChanged 检查票价下库存是否有变动
func (d *Dao) StockChanged(ids []int64) bool {
if ids == nil {
return false
}
var stocks []model.Stock
if err := d.db.Select("total_stock, stock").Where("sku_id IN (?)", ids).Find(&stocks).Error; err != nil {
log.Error("获取票价库存信息失败:%s", err)
return true
}
for _, v := range stocks {
if (v.TotalStock - v.Stock) != 0 {
log.Error("票价存在库存有变动")
return true
}
}
return false
}

View File

@@ -0,0 +1,17 @@
package dao
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
// TestStockChanged
func TestDao_StockChanged(t *testing.T) {
Convey("StockChanged", t, func() {
once.Do(startService)
res := d.StockChanged([]int64{1015, 1016})
So(res, ShouldBeTrue)
})
}

View File

@@ -0,0 +1,55 @@
package dao
import (
"context"
"go-common/app/service/openplatform/ticket-item/model"
"go-common/library/log"
"strconv"
"strings"
"github.com/jinzhu/gorm"
"go-common/app/service/openplatform/ticket-item/conf"
"go-common/library/ecode"
)
// CreateTag 创建项目标签
func (d *Dao) CreateTag(c context.Context, tx *gorm.DB, pid int64, tagID string) error {
tagMap := d.GetTagConfigInfo(c)
covTagID, _ := strconv.ParseInt(tagID, 10, 64)
if _, ok := tagMap[tagID]; !ok {
//key不存在
log.Error("标签id不存在对应标签名")
tx.Rollback()
return ecode.TicketAddTagFailed
}
// create
if err := tx.Create(&model.ProjectTags{
TagID: covTagID,
TagName: tagMap[tagID],
ProjectID: pid,
Status: 1,
}).Error; err != nil {
log.Error("创建标签失败:%s", err)
tx.Rollback()
return err
}
return nil
}
// GetTagConfigInfo 获取标签配置信息返回map
func (d *Dao) GetTagConfigInfo(c context.Context) map[string]string {
tagStr := conf.Conf.Tag.Tags
allTags := strings.Split(tagStr, ",")
tagMap := make(map[string]string)
var tagInfo []string
for _, v := range allTags {
tagInfo = strings.Split(v, "=")
tagMap[tagInfo[0]] = tagInfo[1]
}
return tagMap
}

View File

@@ -0,0 +1,19 @@
package dao
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
// TestCreateTag
func TestDao_CreateTag(t *testing.T) {
Convey("CreateTag", t, func() {
once.Do(startService)
tx := d.db.Begin()
err := d.CreateTag(context.TODO(), tx, 1, "10003")
tx.Commit()
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,569 @@
package dao
import (
"context"
"encoding/json"
"fmt"
"go-common/app/service/openplatform/ticket-item/model"
"go-common/library/cache/redis"
"go-common/library/ecode"
"go-common/library/log"
"strconv"
"strings"
"time"
"github.com/jinzhu/gorm"
)
const (
// TkTypeSingle 单场票
TkTypeSingle = 1
// TkTypePass 通票
TkTypePass = 2
// TkTypeAllPass 联票
TkTypeAllPass = 3
// TimeNull 空时间0000-00-00 00:00:00
TimeNull = -62135596800
)
// RawTkListByItem 批量取项目票价
func (d *Dao) RawTkListByItem(c context.Context, ids []int64) (info map[int64][]*model.TicketInfo, err error) {
info = make(map[int64][]*model.TicketInfo)
tkExt := make(map[int64]map[string]*model.TicketPriceExtra)
rows, err := d.db.Model(&model.TicketPrice{}).Where("project_id in (?) and deleted_at = 0", ids).Rows()
extRows, err := d.db.Model(&model.TicketPriceExtra{}).Where("project_id in (?) and is_deleted = 0", ids).Rows()
if err != nil {
log.Error("RawListByItem(%v) error(%v)", model.JSONEncode(ids), err)
return
}
defer rows.Close()
defer extRows.Close()
for extRows.Next() {
var ext model.TicketPriceExtra
err = d.db.ScanRows(extRows, &ext)
if err != nil {
log.Error("RawListByItem(%v) error(%v)", model.JSONEncode(ids), err)
return
}
if _, ok := tkExt[ext.SkuID]; !ok {
tkExt[ext.SkuID] = make(map[string]*model.TicketPriceExtra)
}
if _, ok := tkExt[ext.SkuID][ext.Attrib]; !ok {
tkExt[ext.SkuID][ext.Attrib] = new(model.TicketPriceExtra)
}
tkExt[ext.SkuID][ext.Attrib] = &ext
}
for rows.Next() {
var tk model.TicketInfo
err = d.db.ScanRows(rows, &tk)
if err != nil {
log.Error("RawListByItem(%v) error(%v)", model.JSONEncode(ids), err)
return
}
if _, ok := tkExt[tk.ID]; ok {
tk.BuyNumLimit = tkExt[tk.ID]
}
info[tk.ProjectID] = append(info[tk.ProjectID], &tk)
}
return
}
// CacheTkListByItem 缓存取项目票价
func (d *Dao) CacheTkListByItem(c context.Context, ids []int64) (info map[int64][]*model.TicketInfo, err error) {
var keys []interface{}
keyPidMap := make(map[string]int64, len(ids))
for _, id := range ids {
key := keyItemTicket(id)
if _, ok := keyPidMap[key]; !ok {
// duplicate mid
keyPidMap[key] = id
keys = append(keys, key)
}
}
conn := d.redis.Get(c)
defer conn.Close()
var data [][]byte
log.Info("MGET %v", model.JSONEncode(keys))
if data, err = redis.ByteSlices(conn.Do("mget", keys...)); err != nil {
log.Error("TkList MGET %v ERR: %v", model.JSONEncode(keys), err)
return
}
info = make(map[int64][]*model.TicketInfo)
for _, d := range data {
if d != nil {
var tks []*model.TicketInfo
json.Unmarshal(d, &tks)
info[tks[0].ProjectID] = tks
}
}
return
}
// AddCacheTkListByItem 取项目票价添加缓存
func (d *Dao) AddCacheTkListByItem(c context.Context, info map[int64][]*model.TicketInfo) (err error) {
conn := d.redis.Get(c)
defer func() {
conn.Flush()
conn.Close()
}()
var data []interface{}
var keys []string
for k, v := range info {
b, _ := json.Marshal(v)
key := keyItemTicket(k)
keys = append(keys, key)
data = append(data, key, b)
}
log.Info("MSET %v", keys)
if err = conn.Send("MSET", data...); err != nil {
return
}
for i := 0; i < len(data); i += 2 {
conn.Send("EXPIRE", data[i], CacheTimeout)
}
return
}
// RawTkList 批量取票价
func (d *Dao) RawTkList(c context.Context, ids []int64) (list map[int64]*model.TicketInfo, err error) {
list = make(map[int64]*model.TicketInfo)
tkExt := make(map[int64]map[string]*model.TicketPriceExtra)
rows, err := d.db.Model(&model.TicketPrice{}).Where("id in (?) and deleted_at = 0", ids).Rows()
extRows, err := d.db.Model(&model.TicketPriceExtra{}).Where("sku_id in (?) and is_deleted = 0", ids).Rows()
if err != nil {
log.Error("RawTkList(%v) error(%v)", model.JSONEncode(ids), err)
return
}
defer rows.Close()
defer extRows.Close()
for extRows.Next() {
var ext model.TicketPriceExtra
err = d.db.ScanRows(extRows, &ext)
if err != nil {
log.Error("RawListByItem(%v) error(%v)", model.JSONEncode(ids), err)
return
}
if _, ok := tkExt[ext.SkuID]; !ok {
tkExt[ext.SkuID] = make(map[string]*model.TicketPriceExtra)
}
if _, ok := tkExt[ext.SkuID][ext.Attrib]; !ok {
tkExt[ext.SkuID][ext.Attrib] = new(model.TicketPriceExtra)
}
tkExt[ext.SkuID][ext.Attrib] = &ext
}
for rows.Next() {
var tk model.TicketInfo
err = d.db.ScanRows(rows, &tk)
if err != nil {
log.Error("RawTkList(%v) error(%v)", model.JSONEncode(ids), err)
return
}
if _, ok := tkExt[tk.ID]; ok {
tk.BuyNumLimit = tkExt[tk.ID]
}
list[tk.ID] = &tk
}
return
}
// CacheTkList 缓存取项目票价
func (d *Dao) CacheTkList(c context.Context, ids []int64) (list map[int64]*model.TicketInfo, err error) {
var keys []interface{}
keyPidMap := make(map[string]int64, len(ids))
for _, id := range ids {
key := keyTicket(id)
if _, ok := keyPidMap[key]; !ok {
// duplicate mid
keyPidMap[key] = id
keys = append(keys, key)
}
}
conn := d.redis.Get(c)
defer conn.Close()
var data [][]byte
log.Info("MGET %v", model.JSONEncode(keys))
if data, err = redis.ByteSlices(conn.Do("mget", keys...)); err != nil {
log.Error("TkList MGET %v ERR: %v", model.JSONEncode(keys), err)
return
}
list = make(map[int64]*model.TicketInfo)
for _, d := range data {
if d != nil {
var tk *model.TicketInfo
json.Unmarshal(d, &tk)
list[tk.ID] = tk
}
}
return
}
// AddCacheTkList 取项目票价添加缓存
func (d *Dao) AddCacheTkList(c context.Context, list map[int64]*model.TicketInfo) (err error) {
conn := d.redis.Get(c)
defer func() {
conn.Flush()
conn.Close()
}()
var data []interface{}
var keys []string
for k, v := range list {
b, _ := json.Marshal(v)
key := keyTicket(k)
keys = append(keys, key)
data = append(data, key, b)
}
log.Info("MSET %v", keys)
if err = conn.Send("MSET", data...); err != nil {
return
}
for i := 0; i < len(data); i += 2 {
conn.Send("EXPIRE", data[i], CacheTimeout)
}
return
}
// CreateOrUpdateTkPrice 创建或更新票种
func (d *Dao) CreateOrUpdateTkPrice(c context.Context, tx *gorm.DB, priceInfo model.TicketPrice, opType int32) (model.TicketPrice, error) {
if opType == 0 {
// create
if err := tx.Create(&priceInfo).Error; err != nil {
log.Error("创建票种失败:%s", err)
tx.Rollback()
return model.TicketPrice{}, err
}
} else {
// update
if err := tx.Model(&model.TicketPrice{}).Where("id = ?", priceInfo.ID).Updates(
map[string]interface{}{
"project_id": priceInfo.ProjectID,
"desc": priceInfo.Desc,
"type": priceInfo.Type,
"sale_type": priceInfo.SaleType,
"color": priceInfo.Color,
"buy_limit": priceInfo.BuyLimit,
"payment_method": priceInfo.PaymentMethod,
"payment_value": priceInfo.PaymentValue,
"desc_detail": priceInfo.DescDetail,
}).Error; err != nil {
log.Error("更新票种失败:%s", err)
tx.Rollback()
return model.TicketPrice{}, err
}
}
return priceInfo, nil
}
// InsertOrUpdateTkPass 新建或更新通票联票票价
func (d *Dao) InsertOrUpdateTkPass(c context.Context, tx *gorm.DB, pid int64, scID int64, tksPass []TicketPass, tkType int32,
scIDList map[int32]int64, TkSingleIDList map[int32]int64, TkSingleTypeList map[int32]int32) ([]TicketPass, error) {
alphabetTable := model.AlphabetTable()
for k, v := range tksPass {
if _, ok := TkSingleIDList[v.LinkTicket]; !ok {
tx.Rollback()
log.Error("关联票种不存在")
return nil, ecode.TicketLkTkNotFound
}
if _, ok := TkSingleTypeList[v.LinkTicket]; !ok {
tx.Rollback()
log.Error("关联票种类型不存在")
return nil, ecode.TicketLkTkTypeNotFound
}
var linkScIDs []int64
for _, linkScID := range v.LinkScreens {
if _, ok := scIDList[linkScID]; !ok {
tx.Rollback()
log.Error("关联场次不存在")
return nil, ecode.TicketLkScNotFound
}
linkScIDs = append(linkScIDs, scIDList[linkScID])
}
tkID, _ := strconv.ParseInt(v.TicketID, 10, 64)
symbol := alphabetTable[k]
if tkID == 0 {
// create
newTkID, err := model.GetTicketIDFromBase()
if err != nil {
tx.Rollback()
log.Error("basecenter获取通票票价id失败:%s", err)
return nil, err
}
buyLimit, _ := strconv.ParseInt(v.BuyLimit, 10, 64)
payMethod, _ := strconv.ParseInt(v.PayMethod, 10, 64)
if err = tx.Create(&model.TicketPrice{
ID: newTkID,
ProjectID: pid,
ScreenID: scID,
Desc: v.Name,
BuyLimit: int32(buyLimit),
ParentID: TkSingleIDList[v.LinkTicket],
Color: v.Color,
DescDetail: v.Desc,
PaymentMethod: int32(payMethod),
PaymentValue: v.PayValue,
Type: tkType,
SaleType: TkSingleTypeList[v.LinkTicket],
Symbol: symbol,
LinkSc: model.Implode(",", linkScIDs),
IsSale: 0, // 不可售
IsRefund: -10, // 不可退
OriginPrice: -1, // 未設置
MarketPrice: -1,
SaleStart: TimeNull, // 0000-00-00 00:00:00
SaleEnd: TimeNull,
}).Error; err != nil {
log.Error("通票或联票创建失败:%s", err)
tx.Rollback()
return nil, err
}
//票价限购
limitData := d.FormatByPrefix(v.BuyLimitNum, "buy_limit_")
if err = d.CreateOrUpdateTkPriceExtra(c, tx, limitData, newTkID, pid); err != nil {
return nil, err
}
tksPass[k].TicketID = strconv.FormatInt(newTkID, 10)
} else {
// update
if err := tx.Model(&model.TicketPrice{}).Where("id = ?", tkID).Updates(map[string]interface{}{
"screen_id": scID,
"desc": v.Name,
"buy_limit": v.BuyLimit,
"parent_id": TkSingleIDList[v.LinkTicket],
"color": v.Color,
"desc_detail": v.Desc,
"payment_method": v.PayMethod,
"payment_value": v.PayValue,
"type": tkType,
"sale_type": TkSingleTypeList[v.LinkTicket],
"symbol": symbol,
"link_sc": model.Implode(",", linkScIDs),
}).Error; err != nil {
log.Error("通票或联票票价信息更新失败:%s", err)
tx.Rollback()
return nil, err
}
//票价限购
limitData := d.FormatByPrefix(v.BuyLimitNum, "buy_limit_")
if err := d.CreateOrUpdateTkPriceExtra(c, tx, limitData, tkID, pid); err != nil {
return nil, err
}
}
}
return tksPass, nil
}
// DelTicket 根据id删除票种或票价
func (d *Dao) DelTicket(c context.Context, tx *gorm.DB, oldIDs []int64, newIDs []int64, pid int64, isPrice bool) error {
delIDs, _ := model.ClassifyIDs(oldIDs, newIDs)
for _, delID := range delIDs {
if !d.CanDelTicket(delID, isPrice) {
tx.Rollback()
return ecode.TicketCannotDelTk
}
if isPrice {
// TODO 存在需要删除的票价时 检查票价是否在坐票可选座场次下 是的话需要删除对应的座位图
// 删除票价
if err := tx.Exec("UPDATE ticket_price SET deleted_at=? WHERE id = ? AND project_id = ?", time.Now().Format("2006-01-02 15:04:05"), delID, pid).Error; err != nil {
log.Error("删除票价失败:%s", err)
tx.Rollback()
return ecode.TicketDelTkFailed
}
// 删除票价额外信息表
if err := tx.Model(&model.TicketPriceExtra{}).Where("sku_id = ? project_id = ?", delID, pid).Update("is_deleted", 1).Error; err != nil {
log.Error("删除票种额外信息记录失败:%s", err)
tx.Rollback()
return ecode.TicketDelTkExFailed
}
} else {
// 票种 需要获取 所有票价id
priceIDs, err := d.GetPriceIDs(delID, 2)
if err != nil {
tx.Rollback()
return err
}
// TODO 存在需要删除的票价时 检查票价是否在坐票可选座场次下 是的话需要删除对应的座位图
// 将票种id加到需要删除的票价array里
priceIDs = append(priceIDs, delID)
// 删除票种票价
if err := tx.Exec("UPDATE ticket_price SET deleted_at=? WHERE id IN (?) AND project_id = ?", time.Now().Format("2006-01-02 15:04:05"), priceIDs, pid).Error; err != nil {
log.Error("删除票种及其票价失败:%s", err)
tx.Rollback()
return ecode.TicketDelTkFailed
}
//删除票价额外信息表
if err := tx.Model(&model.TicketPriceExtra{}).Where("sku_id IN (?) AND project_id = ?", priceIDs, pid).Update("is_deleted", 1).Error; err != nil {
log.Error("删除票种及票价额外信息记录失败:%s", err)
tx.Rollback()
return ecode.TicketDelTkExFailed
}
}
}
return nil
}
// CanDelTicket 检查是否可以删除票价或票种
func (d *Dao) CanDelTicket(id int64, isPrice bool) bool {
var priceIDs []int64
if isPrice {
// 票价
priceIDs = append(priceIDs, id)
} else {
// 票种 需要获取 所有票价id
ids, err := d.GetPriceIDs(id, 2)
if err != nil {
return false
}
priceIDs = ids
}
if d.HasPromotion(priceIDs, 2) || d.StockChanged(priceIDs) {
log.Error("票价下存在拼团或者库存有变动:%d", id)
return false
}
return true
}
// GetPriceIDs 获取场次或票种下所有票价id inputType 1-场次id 2-票种id
func (d *Dao) GetPriceIDs(id int64, inputType int32) ([]int64, error) {
var priceIDs []int64
var prices []model.TicketPrice
var whereStr string
if inputType == 1 {
// id = screenID
whereStr = "screen_id = ? and deleted_at = 0"
} else {
// id = skuID
whereStr = "parent_id = ? and deleted_at = 0"
}
if err := d.db.Select("id").Where(whereStr, id).Find(&prices).Error; err != nil {
log.Error("获取场次或票种下所有票价id失败:%s", err)
return nil, err
}
for _, v := range prices {
priceIDs = append(priceIDs, v.ID)
}
return priceIDs, nil
}
// ticket_price表同时包含票价和票种票种的parent_id为0票价的parent_id为票种ID
// 票种不直接使用于场次,票价继承自票种,指定某一场次
// 以此实现“票种可以在一个项目下多个场次通用”
// 坐票只存在单场票
// TxGetTicketPrice 获取票价的价格标志和场次(事务)
func (d *Dao) TxGetTicketPrice(c context.Context, tx *gorm.DB, id int64) (ticketPrice *model.TicketPrice, err error) {
ticketPrice = new(model.TicketPrice)
if err = tx.Select("symbol, screen_id").Where("id = ? AND parent_id <> 0 AND deleted_at = 0", id).First(ticketPrice).Error; err != nil {
log.Error("TxGetTicketPrice error(%v)", err)
}
return
}
// TxGetPriceSymbols 获取场次下的所有票价的父票种ID、价格和标志事务
func (d *Dao) TxGetPriceSymbols(c context.Context, tx *gorm.DB, screen int64) (ticketPrices []*model.TicketPrice, err error) {
if err = tx.Select("parent_id, price, symbol").Where("screen_id = ? AND type = ? AND parent_id <> 0 AND deleted_at = 0", screen, TkTypeSingle).Find(&ticketPrices).Error; err != nil {
log.Error("TxGetPriceSymbols error(%v)", err)
}
return
}
// TxGetParentTicketPrice 获取票种-单场票(事务)
func (d *Dao) TxGetParentTicketPrice(c context.Context, tx *gorm.DB, id int64) (ticketPrice *model.TicketPrice, err error) {
ticketPrice = new(model.TicketPrice)
if err = tx.Where("id = ? AND type = ? AND deleted_at = 0", id, TkTypeSingle).First(ticketPrice).Error; err != nil {
log.Error("TxGetParentTicketPrice error(%v)", err)
}
return
}
// TxBatchAddTicketPrice 批量添加票价(事务)
func (d *Dao) TxBatchAddTicketPrice(c context.Context, tx *gorm.DB, ticketPrices []*model.TicketPrice) (err error) {
if len(ticketPrices) == 0 {
return
}
var values = make([]string, len(ticketPrices))
for i, tp := range ticketPrices {
values[i] = fmt.Sprintf("(%d,%d,%d,%d,%d,'%s','%s',%d,'%s',%d,%d,%d,%d,%d,%d,%d,'%s',%d,'%s',%d,'%s',%d,%d,%d)", tp.ID, tp.ProjectID, tp.ScreenID, tp.Price, tp.BuyLimit, tp.Desc, tp.Color, tp.ParentID, tp.Symbol, tp.IsSale, tp.OriginPrice, tp.PaymentMethod, tp.PaymentValue, tp.Type, tp.IsRefund, tp.IsVisible, tp.DescDetail, tp.SaleType, tp.SaleTime, tp.LinkTicketID, tp.LinkSc, tp.SaleStart, tp.SaleEnd, tp.MarketPrice)
}
var sql = fmt.Sprintf("INSERT INTO `ticket_price` (`id`, `project_id`, `screen_id`, `price`, `buy_limit`, `desc`, `color`, `parent_id`, `symbol`, `is_sale`, `origin_price`, `payment_method`, `payment_value`, `type`, `is_refund`, `is_visible`, `desc_detail`, `sale_type`, `sale_time`, `link_ticket_id`, `link_sc`, `sale_start`, `sale_end`, `market_price`) VALUES %s;", strings.Join(values, ","))
if err = tx.Exec(sql).Error; err != nil {
log.Error("批量添加票种(%s失败:%s", sql, err)
err = ecode.NotModified
return
}
return
}
// CreateOrUpdateTkPriceExtra 创建票价额外信息记录
func (d *Dao) CreateOrUpdateTkPriceExtra(c context.Context, tx *gorm.DB, input map[string]string, skuID int64, pid int64) (err error) {
data := d.FormatInputData(input, skuID, pid)
var tmpTkExtra model.TicketPriceExtra
for attrib, val := range data {
if err = tx.Where("sku_id = ? and project_id = ? and attrib = ? and is_deleted=0", skuID, pid, attrib).First(&tmpTkExtra).Error; err != nil {
//除去没查找到记录的报错 其他直接抛错
if err != ecode.NothingFound {
log.Error("获取票价%s额外信息失败:%s", skuID, err)
tx.Rollback()
return
}
}
if tmpTkExtra.ID == 0 {
// create
if err = tx.Create(&val).Error; err != nil {
log.Error("创建票价额外信息记录失败:%s", err)
tx.Rollback()
return
}
} else {
// update
if err = tx.Model(&model.TicketPriceExtra{}).Where("sku_id = ? and project_id = ? and attrib = ?", skuID, pid, attrib).Update("value", val).Error; err != nil {
log.Error("更新票价额外信息记录失败:%s", err)
tx.Rollback()
return
}
}
}
return
}
// FormatInputData 格式化input数据
func (d *Dao) FormatInputData(input map[string]string, skuID int64, pid int64) (res []model.TicketPriceExtra) {
for key, value := range input {
res = append(res, model.TicketPriceExtra{
Attrib: key,
Value: value,
SkuID: skuID,
ProjectID: pid,
})
}
return
}
// FormatByPrefix 给键值加前缀
func (d *Dao) FormatByPrefix(input []string, prefix string) map[string]string {
result := make(map[string]string)
for k, v := range input {
result[prefix+strconv.Itoa(k)] = v
}
return result
}

View File

@@ -0,0 +1,180 @@
package dao
import (
"testing"
"time"
"context"
"go-common/app/service/openplatform/ticket-item/model"
"github.com/davecgh/go-spew/spew"
. "github.com/smartystreets/goconvey/convey"
)
// TestGetPriceIDs
func TestDao_GetPriceIDs(t *testing.T) {
Convey("GetPriceIDs", t, func() {
once.Do(startService)
res, err := d.GetPriceIDs(1, 2)
So(res, ShouldNotBeNil)
So(err, ShouldBeNil)
})
}
// TestCanDelTicket
func TestDao_CanDelTicket(t *testing.T) {
Convey("CanDelTicket", t, func() {
once.Do(startService)
res := d.CanDelTicket(33, true)
So(res, ShouldBeTrue)
})
}
/**
// TestDelTicket
func TestDao_DelTicket(t *testing.T) {
Convey("DelTicket", t, func() {
once.Do(startService)
tx := d.db.Begin()
err := d.DelTicket(context.TODO(), tx, []int64{33}, []int64{25}, 2, true)
tx.Commit()
So(err, ShouldBeNil)
})
}**/
// TestCreateOrUpdateTkPrice
func TestDao_CreateOrUpdateTkPrice(t *testing.T) {
Convey("CreateOrUpdateTkPrice", t, func() {
once.Do(startService)
tx := d.db.Begin()
priceInfo, err := d.CreateOrUpdateTkPrice(context.TODO(), tx, model.TicketPrice{
ID: 0,
ProjectID: 33,
Desc: "DESCCCC",
Type: 1, // 单场票
SaleType: 1,
Color: "#FF0000",
BuyLimit: 0,
PaymentMethod: 1,
PaymentValue: 0,
DescDetail: "DESCDETAIL",
IsSale: 1, // 可售
IsRefund: -10, // 不可退
OriginPrice: -1, // 未設置
MarketPrice: -1,
SaleStart: -62135596800, // 0000-00-00 00:00:00
SaleEnd: -62135596800,
}, 0)
spew.Dump(priceInfo)
tx.Commit()
So(priceInfo, ShouldNotBeNil)
So(err, ShouldBeNil)
})
}
/**
// TestInsertOrUpdateTkPass
func TestDao_InsertOrUpdateTkPass(t *testing.T) {
Convey("InsertOrUpdateTkPass", t, func() {
once.Do(startService)
tx := d.db.Begin()
priceInfo, err := d.InsertOrUpdateTkPass(context.TODO(), tx, model.TicketPrice{
ID: 0,
ProjectID: 33,
Desc: "DESCCCC",
Type: 1, // 单场票
SaleType: 1,
Color: "#FF0000",
BuyLimit: 0,
PaymentMethod: 1,
PaymentValue: 0,
DescDetail: "DESCDETAIL",
IsSale: 1, // 可售
IsRefund: -10, // 不可退
OriginPrice: -1, // 未設置
MarketPrice: -1,
SaleStart: -62135596800, // 0000-00-00 00:00:00
SaleEnd: -62135596800,
}, 0)
spew.Dump(priceInfo)
tx.Commit()
So(priceInfo, ShouldNotBeNil)
So(err, ShouldBeNil)
})
}**/
// TestTicketPrice
func TestTicketPrice(t *testing.T) {
var (
tp *model.TicketPrice
tps []*model.TicketPrice
err error
)
Convey("TxGetParentTicketPrice", t, func() {
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
So(tx, ShouldNotBeNil)
tp, err = d.TxGetParentTicketPrice(c, tx, 13424)
So(tp.Desc, ShouldEqual, "海尔测试票")
So(err, ShouldBeNil)
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
})
var cnt int
Convey("TxGetPriceSymbols", t, func() {
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
So(tx, ShouldNotBeNil)
tps, err = d.TxGetPriceSymbols(c, tx, 1633)
cnt = len(tps)
So(err, ShouldBeNil)
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
})
var genID int64
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
Convey("TxBatchAddTicketPrice", t, func() {
genID, err = model.GetTicketIDFromBase()
So(genID, ShouldNotEqual, 0)
So(err, ShouldBeNil)
tp.ParentID = tp.ID
tp.ID = genID
tp.ScreenID = 1633
tp.Symbol = string(int(time.Now().Unix())%26 + 'A')
tp.OriginPrice = -1
tp.MarketPrice = -1
tp.IsSale = 0
tp.IsVisible = 0
tp.IsRefund = 10
So(tx, ShouldNotBeNil)
err = d.TxBatchAddTicketPrice(c, tx, []*model.TicketPrice{tp})
So(err, ShouldBeNil)
tps, err := d.TxGetPriceSymbols(c, tx, 1633)
So(len(tps)-cnt, ShouldEqual, 1)
So(err, ShouldBeNil)
})
Convey("TxGetTicketPrice", t, func() {
So(tx, ShouldNotBeNil)
tp, err := d.TxGetTicketPrice(c, tx, genID)
So(tp.ScreenID, ShouldEqual, 1633)
So(err, ShouldBeNil)
})
tx.Rollback()
Convey("TxGetTicketPrice", t, func() {
tx, _ := d.BeginTran(c)
So(tx, ShouldNotBeNil)
_, err := d.TxGetTicketPrice(c, tx, genID)
So(err, ShouldBeError)
})
}

View File

@@ -0,0 +1,449 @@
/*
Package dao venue
场馆venue=>场地place=>区域area的三级层次均为一对多
venue表冗余place_num表示场地数
place表通过ID对应place_polygon存有area的地理位置信息
*/
package dao
import (
"context"
"encoding/json"
"fmt"
"go-common/app/service/openplatform/ticket-item/model"
"go-common/library/cache/redis"
"go-common/library/database/elastic"
"go-common/library/ecode"
"go-common/library/log"
"github.com/jinzhu/gorm"
)
// RawVenues 批量获取场馆
func (d *Dao) RawVenues(c context.Context, ids []int64) (vl map[int64]*model.Venue, err error) {
vrows, err := d.db.Model(&model.Venue{}).Where("id in (?)", ids).Rows()
vl = make(map[int64]*model.Venue)
if err != nil {
log.Error("RawVenues(%v) error(%v)", ids, err)
return
}
defer vrows.Close()
for vrows.Next() {
var v model.Venue
err = d.db.ScanRows(vrows, &v)
vl[v.ID] = &v
}
return
}
// CacheVenues 缓存批量获取场馆
func (d *Dao) CacheVenues(c context.Context, ids []int64) (vl map[int64]*model.Venue, err error) {
var keys []interface{}
keyVidMap := make(map[string]int64, len(ids))
for _, id := range ids {
key := keyVenue(id)
if _, ok := keyVidMap[key]; !ok {
// duplicate pid
keyVidMap[key] = id
keys = append(keys, key)
}
}
conn := d.redis.Get(c)
defer conn.Close()
var data [][]byte
log.Info("MGET %v", model.JSONEncode(keys))
if data, err = redis.ByteSlices((conn.Do("MGET", keys))); err != nil {
log.Error("VenueList MGET %v ERR: %v", model.JSONEncode(keys), err)
return
}
vl = make(map[int64]*model.Venue)
for _, d := range data {
if d != nil {
var v *model.Venue
vl[v.ID] = v
json.Unmarshal(d, &v)
}
}
return
}
// AddCacheVenues 缓存场馆信息
func (d *Dao) AddCacheVenues(c context.Context, vl map[int64]*model.Venue) (err error) {
conn := d.redis.Get(c)
defer func() {
conn.Flush()
conn.Close()
}()
var data []interface{}
var keys []string
for k, v := range vl {
b, _ := json.Marshal(v)
key := keyVenue(k)
keys = append(keys, key)
data = append(data, key, b)
}
log.Info("MSET %v", keys)
if err = conn.Send("MSET", data...); err != nil {
return
}
for i := 0; i < len(data); i += 2 {
conn.Send("EXPIRE", data[i], CacheTimeout)
}
return
}
// VenueSearch 场馆搜索
func (d *Dao) VenueSearch(c context.Context, req *model.VenueSearchParam) (venues *model.VenueSearchList, err error) {
r := d.es.NewRequest("ticket_venue").Index("ticket_venue")
if req.ID > 0 {
r.WhereEq("id", req.ID)
} else if req.Name != "" {
r.WhereLike([]string{"name"}, []string{req.Name}, false, elastic.LikeLevelLow)
}
if req.ProvinceID > 0 {
r.WhereEq("province", req.ProvinceID)
}
if req.CityID > 0 {
r.WhereEq("city", req.CityID)
}
r.Order("ctime", elastic.OrderDesc).Ps(req.Ps).Pn(req.Pn)
log.Info(fmt.Sprintf("%s/x/admin/search/query?%s", d.c.URL.ElasticHost, r.Params()))
venues = new(model.VenueSearchList)
if err = r.Scan(c, venues); err != nil {
log.Error("VenueSearch(%v) r.Query(%s) error(%s)", req, r.Params(), err)
}
return
}
// AddVenue 添加场馆信息
func (d *Dao) AddVenue(c context.Context, venue *model.Venue) (err error) {
if err = d.db.Create(venue).Error; err != nil {
log.Error("添加场馆信息失败:%s", err)
err = ecode.NotModified
return
}
return
}
// UpdateVenue 编辑场馆信息
func (d *Dao) UpdateVenue(c context.Context, venue *model.Venue) (err error) {
// update venue with new info (using map can update the column with empty string)
if err = d.db.Table("venue").Where("id = ?", venue.ID).Updates(
map[string]interface{}{
"name": venue.Name,
"city": venue.City,
"province": venue.Province,
"district": venue.District,
"address_detail": venue.AddressDetail,
"status": venue.Status,
"traffic": venue.Traffic,
}).Error; err != nil {
log.Error("更新场馆信息失败:%s", err)
err = ecode.NotModified
return
}
return
}
// TxIncPlaceNum 增加场馆的场地数(事务)
func (d *Dao) TxIncPlaceNum(c context.Context, tx *gorm.DB, venueID int64) (err error) {
var oriVenue model.Venue
if err = tx.First(&oriVenue, venueID).Error; err != nil {
log.Error("查找对应的场馆信息ID:%d失败:%s", venueID, err)
err = ecode.NotModified
return
}
if err = tx.Model(&oriVenue).Updates(
map[string]interface{}{
"place_num": oriVenue.PlaceNum + 1,
}).Error; err != nil {
log.Error("更新场馆信息ID:%d失败:%s", venueID, err)
err = ecode.NotModified
return
}
return
}
// TxDecPlaceNum 减少场馆的场地数(事务)
func (d *Dao) TxDecPlaceNum(c context.Context, tx *gorm.DB, venueID int64) (err error) {
var oriVenue model.Venue
if err = tx.First(&oriVenue, venueID).Error; err != nil {
log.Error("查找对应的场馆信息ID:%d失败:%s", venueID, err)
err = ecode.NotModified
return
}
if oriVenue.PlaceNum < 1 {
log.Error("更新场馆信息ID:%d失败:场地数小于1", venueID)
err = ecode.NotModified
return
}
if err = tx.Model(&oriVenue).Updates(
map[string]interface{}{
"place_num": oriVenue.PlaceNum - 1,
}).Error; err != nil {
log.Error("更新场馆信息ID:%d失败:%s", venueID, err)
err = ecode.NotModified
return
}
return
}
// RawPlace 获取场地
func (d *Dao) RawPlace(c context.Context, id int64) (place *model.Place, err error) {
place = new(model.Place)
err = d.db.Model(&model.Place{}).First(&place, id).Scan(&place).Error
if err != nil {
log.Error("RawPlace(%v) error(%v)", id, err)
}
return
}
// RawPlacePolygon 获取场地坐标
func (d *Dao) RawPlacePolygon(c context.Context, id int64) (placePolygon *model.PlacePolygon, err error) {
placePolygon = new(model.PlacePolygon)
err = d.db.Model(&model.PlacePolygon{}).First(&placePolygon, id).Scan(&placePolygon).Error
if err != nil {
log.Error("RawPlacePolygon(%v) error(%v)", id, err)
}
return
}
// TxRawPlace 获取场地(事务)
func (d *Dao) TxRawPlace(c context.Context, tx *gorm.DB, id int64) (place *model.Place, err error) {
place = new(model.Place)
err = tx.Model(&model.Place{}).First(&place, id).Scan(&place).Error
if err != nil {
log.Error("TxRawPlace(%v) error(%v)", id, err)
}
return
}
// CachePlace 缓存获取场地
func (d *Dao) CachePlace(c context.Context, id int64) (place *model.Place, err error) {
var data []byte
key := keyPlace(id)
conn := d.redis.Get(c)
defer conn.Close()
log.Info("GET %v", key)
if data, err = redis.Bytes((conn.Do("GET", key))); err != nil {
if err == redis.ErrNil {
err = nil
}
return
}
json.Unmarshal(data, &place)
return
}
// AddCachePlace 缓存场地信息
func (d *Dao) AddCachePlace(c context.Context, id int64, place *model.Place) (err error) {
conn := d.redis.Get(c)
defer func() {
conn.Flush()
conn.Close()
}()
var data []interface{}
key := keyPlace(id)
data = append(data, key, place)
log.Info("SET %v", key)
if err = conn.Send("SET", data...); err != nil {
return
}
conn.Send("EXPIRE", data[0], CacheTimeout)
return
}
// TxAddPlace 添加场地信息(事务)
func (d *Dao) TxAddPlace(c context.Context, tx *gorm.DB, place *model.Place) (err error) {
if err = tx.Create(place).Error; err != nil {
log.Error("添加场地信息失败:%s", err)
err = ecode.NotModified
return
}
return
}
// TxUpdatePlace 编辑场地信息(事务)
func (d *Dao) TxUpdatePlace(c context.Context, tx *gorm.DB, place *model.Place) (err error) {
// find original place with id
if err = tx.Table("place").Where("id = ?", place.ID).Update(
map[string]interface{}{
"name": place.Name,
"base_pic": place.BasePic,
"status": place.Status,
"venue": place.Venue,
"d_width": place.DWidth,
"d_height": place.DHeight,
}).Error; err != nil {
log.Error("更新场地信息ID:%d失败:%s", place.ID, err)
err = ecode.NotModified
return
}
return
}
// TxAddOrUpdateAreaPolygon 添加或更新区域的场地坐标信息(事务)
func (d *Dao) TxAddOrUpdateAreaPolygon(c context.Context, tx *gorm.DB, place int64, area int64, coordinate *string) (err error) {
var (
oriPlacePolygon model.PlacePolygon
coordinates map[int64]string
found bool
)
if res := tx.First(&oriPlacePolygon, place); res.Error != nil {
if !res.RecordNotFound() {
log.Error("查找对应的场地坐标信息ID:%d失败:%s", place, res.Error)
err = ecode.NotModified
return
}
coordinates = make(map[int64]string)
oriPlacePolygon.ID = place
found = false
} else {
// 反序列化出coordinate字段
if err = json.Unmarshal([]byte(oriPlacePolygon.Coordinate), &coordinates); err != nil {
log.Error("数据库中的场地坐标信息ID:%d反序列化失败:%s", place, err)
err = ecode.NotModified
return
}
found = true
}
// 添加新的coordinate并序列化
coordinates[area] = *coordinate
b, _ := json.Marshal(coordinates)
oriPlacePolygon.Coordinate = string(b)
if found {
if err = tx.Model(&oriPlacePolygon).Updates(
map[string]interface{}{
"coordinate": oriPlacePolygon.Coordinate,
}).Error; err != nil {
log.Error("更新场地坐标信息ID:%d失败:%s", place, err)
err = ecode.NotModified
return
}
} else {
if err = tx.Create(oriPlacePolygon).Error; err != nil {
log.Error("创建场地坐标信息ID:%d失败:%s", place, err)
err = ecode.NotModified
return
}
}
*coordinate = oriPlacePolygon.Coordinate
return
}
// TxDelAreaPolygon 删除区域的场地坐标信息(事务)
func (d *Dao) TxDelAreaPolygon(c context.Context, tx *gorm.DB, place int64, area int64) (err error) {
var (
oriPlacePolygon model.PlacePolygon
coordinates map[int64]string
)
if err = tx.First(&oriPlacePolygon, place).Error; err != nil {
log.Error("查找对应的场地坐标信息ID:%d失败:%s", place, err)
err = ecode.NotModified
return
}
if oriPlacePolygon.Coordinate != "" {
if err = json.Unmarshal([]byte(oriPlacePolygon.Coordinate), &coordinates); err != nil {
log.Error("数据库中的场地坐标信息ID:%d反序列化失败:%s", place, err)
err = ecode.NotModified
return
}
} else {
coordinates = make(map[int64]string)
}
// 添加新的coordinate并序列化
if _, ok := coordinates[area]; !ok {
log.Error("数据库中的场地坐标信息ID:%d并未包含该区域ID:%d删除失败", place, area)
err = ecode.NotModified
return
}
delete(coordinates, area)
b, _ := json.Marshal(coordinates)
oriPlacePolygon.Coordinate = string(b)
if err = tx.Model(&oriPlacePolygon).Updates(
map[string]interface{}{
"coordinate": oriPlacePolygon.Coordinate,
}).Error; err != nil {
log.Error("创建场地坐标信息ID:%d失败:%s", place, err)
err = ecode.NotModified
return
}
return
}
// RawArea 获取区域
func (d *Dao) RawArea(c context.Context, id int64) (area *model.Area, err error) {
area = new(model.Area)
if err = d.db.Where("deleted_status = 0").First(&area, id).Error; err != nil {
log.Error("RawArea(%v) error(%v)", id, err)
}
return
}
// TxRawArea 获取区域
func (d *Dao) TxRawArea(c context.Context, tx *gorm.DB, id int64) (area *model.Area, err error) {
area = new(model.Area)
if err = tx.Where("deleted_status = 0").First(&area, id).Error; err != nil {
log.Error("TxRawArea(%v) error(%v)", id, err)
}
return
}
// TxRawDeletedAreaByAID 通过场地ID和自定义区域编号获取已删除的区域
func (d *Dao) TxRawDeletedAreaByAID(c context.Context, tx *gorm.DB, aid string, place int64) (area *model.Area, err error) {
area = new(model.Area)
if res := tx.Where("deleted_status = 1").Where("a_id = ? AND place = ?", aid, place).First(&area); res.Error != nil {
if res.RecordNotFound() {
return nil, nil
}
err = res.Error
log.Error("TxRawAreaByAID(%v, %v) error(%v)", aid, place, err)
}
return
}
// TxAddArea 添加区域信息(事务)
func (d *Dao) TxAddArea(c context.Context, tx *gorm.DB, area *model.Area) (err error) {
if res := tx.Create(area); res.Error != nil {
log.Error("添加区域信息失败:%s", res.Error)
err = ecode.NotModified
return
}
return
}
// TxUpdateArea 编辑区域信息(事务)
func (d *Dao) TxUpdateArea(c context.Context, tx *gorm.DB, area *model.Area) (err error) {
if err = tx.Table("area").Where("id = ?", area.ID).Updates(
map[string]interface{}{
"a_id": area.AID,
"name": area.Name,
"place": area.Place,
"deleted_status": area.DeletedStatus,
}).Error; err != nil {
log.Error("更新区域信息ID:%d失败:%s", area.ID, err)
err = ecode.NotModified
return
}
return
}
// TxDelArea 软删除区域(事务)
func (d *Dao) TxDelArea(c context.Context, tx *gorm.DB, id int64) (err error) {
if err = tx.Table("area").Where("id = ?", id).Updates(
map[string]interface{}{
"deleted_status": 1,
}).Error; err != nil {
log.Error("删除区域信息失败:%s", err)
err = ecode.NotModified
return
}
return
}

View File

@@ -0,0 +1,337 @@
package dao
import (
"context"
"go-common/app/service/openplatform/ticket-item/model"
"strconv"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
// TestRawVenues
func TestRawVenues(t *testing.T) {
Convey("RawVenues", t, func() {
once.Do(startService)
res, err := d.RawVenues(context.TODO(), []int64{77, 78})
So(err, ShouldBeNil)
So(len(res), ShouldEqual, 2)
So(res[77].Name, ShouldEqual, "海尔测试-勿动")
})
}
// TestDaoVenue
func TestDaoVenue(t *testing.T) {
var id int64
Convey("AddVenue", t, func() {
once.Do(startService)
venue := model.Venue{
Name: "可删",
Province: 100000,
City: 100000,
District: 100000,
Status: 0,
AddressDetail: "地址详细",
Traffic: "交通",
}
err := d.AddVenue(context.TODO(), &venue)
So(err, ShouldBeNil)
So(venue.ID, ShouldNotEqual, 0)
id = venue.ID
})
Convey("UpdateVenue", t, func() {
once.Do(startService)
venue := model.Venue{
ID: id,
Name: "可删",
Province: 100000,
City: 100000,
District: 100010,
Status: 1,
AddressDetail: "地址详细",
Traffic: "交通",
}
err := d.UpdateVenue(context.TODO(), &venue)
So(err, ShouldBeNil)
So(venue.Status, ShouldEqual, 1)
})
var placeNum int32
Convey("TxIncPlaceNum", t, func() {
once.Do(startService)
c := context.TODO()
res, err := d.RawVenues(c, []int64{id})
placeNum = res[id].PlaceNum
So(err, ShouldBeNil)
tx, _ := d.BeginTran(c)
So(tx, ShouldNotBeNil)
err = d.TxIncPlaceNum(c, tx, id)
So(err, ShouldBeNil)
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
res, err = d.RawVenues(c, []int64{id})
So(err, ShouldBeNil)
So(res[id].PlaceNum, ShouldEqual, placeNum+1)
})
Convey("TxDecPlaceNum", t, func() {
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
So(tx, ShouldNotBeNil)
err := d.TxDecPlaceNum(c, tx, id)
So(err, ShouldBeNil)
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
res, _ := d.RawVenues(c, []int64{id})
So(res[id].PlaceNum, ShouldEqual, placeNum)
})
}
// TestRawPlace
func TestRawPlace(t *testing.T) {
Convey("RawPlace", t, func() {
once.Do(startService)
res, err := d.RawPlace(context.TODO(), 132)
So(err, ShouldBeNil)
So(res.Name, ShouldEqual, "海尔测试-勿动")
})
}
// TestTxRawPlace
func TestTxRawPlace(t *testing.T) {
Convey("TxRawPlace", t, func() {
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
So(tx, ShouldNotBeNil)
res, err := d.TxRawPlace(c, tx, 132)
So(err, ShouldBeNil)
So(res.Name, ShouldEqual, "海尔测试-勿动")
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
So(res.Name, ShouldEqual, "海尔测试-勿动")
})
}
// TestDaoPlace
func TestDaoPlace(t *testing.T) {
var id int64
Convey("TxAddPlace", t, func() {
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
So(tx, ShouldNotBeNil)
place := &model.Place{
Name: "可删",
BasePic: "url",
Status: 1,
Venue: 81,
DWidth: 800,
DHeight: 800,
}
err := d.TxAddPlace(c, tx, place)
So(err, ShouldBeNil)
So(place.ID, ShouldNotEqual, 0)
id = place.ID
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
place, err = d.RawPlace(c, id)
So(err, ShouldBeNil)
So(place.ID, ShouldNotEqual, 0)
})
Convey("TxUpdatePlace", t, func() {
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
So(tx, ShouldNotBeNil)
place := &model.Place{
ID: id,
Name: "可删",
BasePic: "url2",
Status: 1,
Venue: 81,
DWidth: 800,
DHeight: 800,
}
err := d.TxUpdatePlace(c, tx, place)
So(err, ShouldBeNil)
So(place.BasePic, ShouldEqual, "url2")
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
place, err = d.RawPlace(c, id)
So(err, ShouldBeNil)
So(place.BasePic, ShouldEqual, "url2")
})
var placePolygon *model.PlacePolygon
Convey("TxAddOrUpdateAreaPolygon", t, func() {
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
So(tx, ShouldNotBeNil)
var co = "292 103,443 113,414 263,230 247"
err := d.TxAddOrUpdateAreaPolygon(c, tx, id, 239, &co)
So(err, ShouldBeNil)
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
placePolygon, err = d.RawPlacePolygon(c, id)
So(err, ShouldBeNil)
So(placePolygon.Coordinate, ShouldEqual, "{\"239\":\"292 103,443 113,414 263,230 247\"}")
})
Convey("TxDelAreaPolygon", t, func() {
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
So(tx, ShouldNotBeNil)
err := d.TxDelAreaPolygon(c, tx, id, 239)
So(err, ShouldBeNil)
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
placePolygon, err = d.RawPlacePolygon(c, id)
So(err, ShouldBeNil)
So(placePolygon.Coordinate, ShouldEqual, "{}")
})
}
// TestRawArea
func TestRawArea(t *testing.T) {
Convey("RawArea", t, func() {
once.Do(startService)
res, err := d.RawArea(context.TODO(), 239)
So(err, ShouldBeNil)
So(res.Name, ShouldEqual, "海尔测试-勿动")
})
}
func TestTxRawArea(t *testing.T) {
Convey("RawArea", t, func() {
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
So(tx, ShouldNotBeNil)
res, err := d.TxRawArea(c, tx, 239)
So(err, ShouldBeNil)
So(res.Name, ShouldEqual, "海尔测试-勿动")
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
res, err = d.RawArea(context.TODO(), 239)
So(err, ShouldBeNil)
So(res.Name, ShouldEqual, "海尔测试-勿动")
})
}
// TestDaoArea
func TestDaoArea(t *testing.T) {
var id int64
Convey("TxAddArea", t, func() {
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
So(tx, ShouldNotBeNil)
area := &model.Area{
AID: "test" + strconv.FormatInt(time.Now().Unix(), 10),
Name: "可删",
Place: 132,
}
err := d.TxAddArea(c, tx, area)
So(err, ShouldBeNil)
So(area.ID, ShouldNotEqual, 0)
id = area.ID
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
area, err = d.RawArea(c, id)
So(err, ShouldBeNil)
So(area.ID, ShouldNotEqual, 0)
})
Convey("TxUpdateArea", t, func() {
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
So(tx, ShouldNotBeNil)
area := &model.Area{
ID: id,
AID: "test" + strconv.FormatInt(time.Now().Unix(), 10),
Name: "可删-1",
Place: 132,
}
err := d.TxUpdateArea(c, tx, area)
So(err, ShouldBeNil)
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
area, err = d.RawArea(c, id)
So(err, ShouldBeNil)
So(area.Name, ShouldEqual, "可删-1")
So(area.Place, ShouldEqual, 132)
})
Convey("TxUpdateSeat", t, func() {
once.Do(startService)
c := context.TODO()
tx, _ := d.BeginTran(c)
So(tx, ShouldNotBeNil)
area := &model.Area{
ID: id,
SeatsNum: 10,
Width: 1,
Height: 2,
DeletedStatus: 0,
ColStart: 3,
ColType: 4,
ColDirection: 1,
RowList: "[\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\",\"9\",\"10\",\"11\",\"12\",\"13\",\"14\",\"15\",\"16\",\"17\",\"18\",\"19\",\"20\"]",
SeatStart: "",
}
err := d.TxUpdateSeat(c, tx, area)
So(err, ShouldBeNil)
So(area.SeatsNum, ShouldEqual, 10)
area, err = d.RawArea(c, id)
So(err, ShouldBeNil)
So(area.SeatsNum, ShouldEqual, 0)
err = d.CommitTran(c, tx)
So(err, ShouldBeNil)
area, err = d.RawArea(c, id)
So(err, ShouldBeNil)
So(area.SeatsNum, ShouldEqual, 10)
})
}

View File

@@ -0,0 +1,149 @@
package dao
import (
"context"
"fmt"
"github.com/jinzhu/gorm"
"go-common/app/common/openplatform/random"
"go-common/app/service/openplatform/ticket-item/model"
"go-common/library/database/elastic"
"go-common/library/ecode"
"go-common/library/log"
)
// AddVersion 添加新版本
func (d *Dao) AddVersion(c context.Context, requiredTx *gorm.DB, verInfo *model.Version, verExtInfo *model.VersionExt) (err error) {
// 此处ver_id调用订单号生成器
verID := uint64(random.Uniqid(19))
// 版本号赋值与信息
verInfo.VerID = verID
verExtInfo.VerID = verID
var tx *gorm.DB
if requiredTx == nil {
// 开启事务
tx = d.db.Begin()
} else {
tx = requiredTx
}
// 插入新数据
if verError := tx.Create(&verInfo).Error; verError != nil {
log.Error("version insertion failed:%s", verError)
tx.Rollback()
return ecode.TicketAddVersionFailed
}
if extError := tx.Create(&verExtInfo).Error; extError != nil {
log.Error("version ext insertion failed:%s", extError)
tx.Rollback()
return ecode.TicketAddVerExtFailed
}
if requiredTx == nil {
tx.Commit()
}
return nil
}
// UpdateVersion 编辑版本信息
func (d *Dao) UpdateVersion(c context.Context, verInfo *model.Version) (bool, error) {
// update guest with new info (using map can update the column with empty string)
updateErr := d.db.Model(&model.Version{}).Where("ver_id = ?", verInfo.VerID).Updates(
map[string]interface{}{
"type": verInfo.Type,
"status": verInfo.Status,
"item_name": verInfo.ItemName,
"ver": verInfo.Ver,
"target_item": verInfo.TargetItem,
"auto_pub": verInfo.AutoPub,
"parent_id": verInfo.ParentID,
}).Error
if updateErr != nil {
log.Error("VERSION UPDATE FAILED:%s", updateErr)
return false, ecode.NotModified
}
return true, nil
}
// GetVersion 获取版本信息外加详情
func (d *Dao) GetVersion(c context.Context, verID uint64, needExt bool) (*model.Version, *model.VersionExt, error) {
var verInfo model.Version
var verExtInfo model.VersionExt
if dbErr := d.db.Where("ver_id = ?", verID).First(&verInfo).Error; dbErr != nil {
log.Error("verinfo:(%v) not found with err:%s", verID, dbErr)
return nil, nil, ecode.NothingFound
}
if needExt {
if dbErr := d.db.Where("ver_id = ?", verID).First(&verExtInfo).Error; dbErr != nil {
log.Error("ver_ext_info:(%v) not found with err:%s", verID, dbErr)
return nil, nil, ecode.NothingFound
}
}
return &verInfo, &verExtInfo, nil
}
// RejectVersion 驳回版本
func (d *Dao) RejectVersion(c context.Context, verID uint64, verType int32) (bool, error) {
var newStatus int32
switch verType {
case model.VerTypeBanner:
newStatus = model.VerStatusNotReviewed
default:
newStatus = model.VerStatusRejected
}
updateErr := d.db.Where("ver_id = ? and type = ?", verID, verType).Model(&model.Version{}).Update("status", newStatus).Error
if updateErr != nil {
log.Error("更新版本状态失败:%s", updateErr)
return false, ecode.NotModified
}
return true, nil
}
// AddVersionLog 新建版本审核记录
func (d *Dao) AddVersionLog(c context.Context, info *model.VersionLog) error {
if insertErr := d.db.Create(&info).Error; insertErr != nil {
log.Error("新建版本审核记录失败:%s", insertErr)
return ecode.NotModified
}
return nil
}
// VersionSearch 项目版本查询
func (d *Dao) VersionSearch(c context.Context, in *model.VersionSearchParam) (versions *model.VersionSearchList, err error) {
r := d.es.NewRequest("ticket_version").Index("ticket_version")
if in.TargetItem > 0 {
r.WhereEq("target_item", in.TargetItem)
} else if in.ItemName != "" {
r.WhereLike([]string{"item_name"}, []string{in.ItemName}, false, elastic.LikeLevelLow)
}
if in.Type > 0 {
r.WhereEq("type", in.Type)
}
if length := len(in.Status); length == 1 {
r.WhereEq("status", in.Status[0])
} else if length > 1 {
r.WhereIn("status", in.Status)
}
r.Order("ctime", elastic.OrderDesc).Ps(in.Ps).Pn(in.Pn)
log.Info(fmt.Sprintf("%s/x/admin/search/query?%s", d.c.URL.ElasticHost, r.Params()))
versions = new(model.VersionSearchList)
err = r.Scan(c, versions)
if err != nil {
log.Error("VersionSearch(%v) r.Query(%s) error(%s)", in, r.Params(), err)
return
}
return
}

View File

@@ -0,0 +1,83 @@
package dao
import (
"context"
"testing"
"go-common/app/service/openplatform/ticket-item/model"
. "github.com/smartystreets/goconvey/convey"
)
// TestAddVersion
func TestDao_AddVersion(t *testing.T) {
Convey("AddVersion", t, func() {
once.Do(startService)
err := d.AddVersion(context.TODO(), nil, &model.Version{
Type: 2,
Status: 1, // 审核中
ItemName: "gotest",
ParentID: 10164,
TargetItem: 0,
AutoPub: 1, // 自动上架
}, &model.VersionExt{
Type: 1,
MainInfo: "{'name':'公告test','introduction':'公告简介','content':'公告内容','pid':10164,'project_name':'删通票删票种'}",
})
So(err, ShouldBeNil)
})
}
// TestUpdateVersion
func TestDao_UpdateVersion(t *testing.T) {
Convey("UpdateVersion", t, func() {
once.Do(startService)
res, err := d.UpdateVersion(context.TODO(), &model.Version{
VerID: 2691387070776769288,
Type: 2,
Status: 2, // 审核中
ItemName: "gotest公告",
ParentID: 0,
TargetItem: 10164,
AutoPub: 1, // 自动上架
})
So(res, ShouldBeTrue)
So(err, ShouldBeNil)
})
}
// TestGetVersion
func TestDao_GetVersion(t *testing.T) {
Convey("GetVersion", t, func() {
once.Do(startService)
verInfo, verExtInfo, err := d.GetVersion(context.TODO(), 153008633987459678, true)
So(verInfo, ShouldNotBeNil)
So(verExtInfo, ShouldNotBeNil)
So(err, ShouldBeNil)
})
}
// TestRejectVersion
func TestDao_RejectVersion(t *testing.T) {
Convey("RejectVersion", t, func() {
once.Do(startService)
res, err := d.RejectVersion(context.TODO(), 2691387070776769288, 2)
So(res, ShouldBeTrue)
So(err, ShouldBeNil)
})
}
// TestAddVersionLog
func TestDao_AddVersionLog(t *testing.T) {
Convey("AddVersionLog", t, func() {
once.Do(startService)
err := d.AddVersionLog(context.TODO(), &model.VersionLog{
VerID: 2691387070776769288,
Type: 1,
Log: "reject",
IsPass: 0,
Uname: "tester",
})
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,64 @@
package dao
import (
"context"
"encoding/json"
"fmt"
"go-common/app/service/openplatform/ticket-item/model"
"go-common/library/cache/redis"
"go-common/library/log"
)
const (
_addWishSQL = "INSERT INTO user_wish (mid, item_id, face) VALUES(?, ?, ?)"
// 缓存 key
_userWishCountKey = "USER:WISH:COUNT:%d"
_userWishActiveKey = "USER:WISH:ACTIVE:%d:%d"
_userWishListKey = "USER:WISH:LIST:%d"
)
// AddWish 添加想去
func (d *Dao) AddWish(c context.Context, wish *model.UserWish) (err error) {
err = d.db.Exec(_addWishSQL, wish.MID, wish.ItemID, wish.Face).Error
return
}
// WishCacheUpdate 更新想去缓存
func (d *Dao) WishCacheUpdate(c context.Context, wish *model.UserWish) (err error) {
conn := d.redis.Get(c)
defer conn.Close()
if _, err = conn.Do("SETEX", fmt.Sprintf(_userWishActiveKey, wish.ItemID, wish.MID), _expireHalfhour, 1); err != nil {
log.Error("d.WishCacheUpdate(%+v) SETEX error(%v)", wish, err)
return
}
if _, err = conn.Do("INCR", fmt.Sprintf(_userWishCountKey, wish.ItemID)); err != nil {
log.Error("d.WishCacheUpdate(%+v) INCR error(%v)", wish, err)
return
}
wishCache, err := json.Marshal(map[string]interface{}{
"mid": wish.MID,
"face": wish.Face,
})
if err != nil {
log.Error("d.WishCacheUpdate(%+v) json.Marshal() error(%v)", wish, err)
return
}
listKey := fmt.Sprintf(_userWishListKey, wish.ItemID)
length, err := redis.Int64(conn.Do("RPUSH", listKey, wishCache))
if err != nil {
log.Error("d.WishCacheUpdate(%+v) RPUSH error(%v)", wish, err)
return
}
if length > 5 {
if _, err = conn.Do("LTRIM", listKey, -5, -1); err != nil {
log.Error("d.WishCacheUpdate(%+v) LTRIM error(%v)", wish, err)
return
}
}
return
}