Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
package noticefmtmod
import (
"91porn-server/common/pageopt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Matcher = pageopt.Matcher
// EnableMatch
type EnableMatch struct {
Enable *bool
}
func (t *EnableMatch) New() Matcher {
return pageopt.NewAssignMatch("enable", t.Enable)
}
// TitleMatch
type TitleMatch struct {
Title *string
}
func (t *TitleMatch) New() Matcher {
return pageopt.NewAssignMatch("title", t.Title)
}
// NoticeCodeMatch
type NoticeCodeMatch struct {
NoticeCode *string
}
func (t *NoticeCodeMatch) New() Matcher {
return pageopt.NewAssignMatch("noticeCode", t.NoticeCode)
}
// SenderMatch
type SenderMatch struct {
Sender *Sender
}
func (t *SenderMatch) New() Matcher {
return pageopt.NewAssignMatch("sender", t.Sender)
}
// ReceiverMatch
type ReceiverMatch struct {
Receiver *Receiver
}
func (t *ReceiverMatch) New() Matcher {
return pageopt.NewAssignMatch("receiver", t.Receiver)
}
// NoticeTypeMatch
type NoticeTypeMatch struct {
NoticeType *NoticeType
}
func (t *NoticeTypeMatch) New() Matcher {
return pageopt.NewAssignMatch("noticeType", t.NoticeType)
}
type UIDListMatch struct {
UID *uint64
}
func (u *UIDListMatch) New() Matcher {
return pageopt.NewAssignMatch("uidList", u.UID)
}
type UIDListInMatch struct {
UIDList []uint64
}
func (u *UIDListInMatch) New() Matcher {
return pageopt.NewInMatch("uidList", u.UIDList)
}
func List(sort bson.D, skip, limit int64, matchs ...Matcher) ([]NoticeFmt, error) {
filter := pageopt.MergeM(matchs)
opt := (&options.FindOptions{})
if len(sort) != 0 {
opt.SetSort(sort)
}
opt.SetSkip(skip)
opt.SetLimit(limit)
list := make([]NoticeFmt, 0, limit)
return list, coll(nil).Find(&list, filter, opt)
}
func Count(matchs ...Matcher) (int64, error) {
filter := pageopt.MergeM(matchs)
return coll(nil).Count(filter)
}
+142
View File
@@ -0,0 +1,142 @@
package noticefmtmod
import (
"fmt"
"sort"
"time"
"91porn-server/common"
"91porn-server/common/db"
"91porn-server/common/log"
"91porn-server/common/pageopt"
"91porn-server/models"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// M M from Map
type M = bson.M
var mdb *db.MongoDB
const table = models.NoticeFmt
func coll(t *db.MongoTool) *db.MongoTool {
if t == nil {
return mdb.Coll(table)
}
return t.Coll(table)
}
// initIndex 设置index
func initIndex() {
many := []mongo.IndexModel{
{
Keys: bson.D{{Key: "noticeCode", Value: 1}},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{{Key: "sender", Value: -1}},
},
{
Keys: bson.D{{Key: "uid", Value: -1}},
},
{
Keys: bson.D{{Key: "receiver", Value: -1}},
},
{
Keys: bson.D{{Key: "noticeType", Value: -1}},
},
{
Keys: bson.D{{Key: "updatedAt", Value: -1}},
},
{
Keys: bson.D{{Key: "createdAt", Value: -1}},
},
}
if _, err := coll(nil).CreateIndex(many); err != nil {
panic(fmt.Sprintf("%s model set index err ==>[%+v]", table, err))
}
}
func InsertOne(doc InsertDoc) error {
sort.Sort(doc.UIDList)
sort.Sort(doc.SpecifyNotice.SpecifyTimeList)
docM, err := common.ToBsonM(doc)
if err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "InsertOne", table, "ToBsonM", err))
return err
}
now := time.Now()
docM["updatedAt"] = now
docM["createdAt"] = now
if _, err = coll(nil).InsertOne(docM); err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "InsertOne", table, "InsertOne", err))
return err
}
return nil
}
func UpdateOne(id ObjectID, doc UpdateDoc) error {
docM, err := common.ToBsonM(doc)
if err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "UpdateOne", table, "ToBsonM", err))
return err
}
now := time.Now()
docM["updatedAt"] = now
if _, err = coll(nil).UpsertOne(M{"_id": id}, M{"$set": docM}); err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "UpdateOne", table, "UpdateOne", err))
return err
}
return nil
}
func DeleteOne(id ObjectID) error {
filter := M{
"_id": id,
}
if _, err := coll(nil).DeleteOne(filter); err != nil {
log.Error(fmt.Sprintf("[METHOD-%s]==> Model %s %s fail error:%+v:", "DeleteOne", table, "DeleteOne", err))
return err
}
return nil
}
func GetAllUserNotice(matchs ...Matcher) ([]NoticeFmt, error) {
receiver := AllUser
matchs = append(matchs, (&ReceiverMatch{&receiver}).New())
filter := pageopt.MergeM(matchs)
var list []NoticeFmt
return list, coll(nil).Find(&list, filter)
}
func GetSomeUserNotice(uid uint64, matchs ...Matcher) ([]NoticeFmt, error) {
receiver := SomeUser
matchs = append(matchs,
(&ReceiverMatch{&receiver}).New(),
(&UIDListMatch{&uid}).New(),
)
filter := pageopt.MergeM(matchs)
var list []NoticeFmt
return list, coll(nil).Find(&list, filter)
}
func GetNoticeFmtListByUID(uid uint64, matchs ...Matcher) ([]NoticeFmt, error) {
someUserNotice, err := GetSomeUserNotice(uid, matchs...)
if err != nil {
return nil, err
}
allUserNotice, err := GetAllUserNotice(matchs...)
if err != nil {
return nil, err
}
return append(someUserNotice, allUserNotice...), nil
}
func DeleteBeforeCreatedAt(t *db.MongoTool, tm time.Time) error {
_, err := coll(t).DeleteMany(bson.M{"createdAt": bson.M{"$lt": tm}})
return err
}
+169
View File
@@ -0,0 +1,169 @@
package noticefmtmod
import (
"fmt"
"sort"
"time"
"91porn-server/common/timeutil/timeslice"
)
// TimeSlice
type TimeSlice = timeslice.TimeSlice
// UIDSlice
type UIDSlice []uint64
func (u UIDSlice) Len() int {
return len(u)
}
func (u UIDSlice) Swap(i, j int) {
u[i], u[j] = u[j], u[i]
}
func (u UIDSlice) Less(i, j int) bool {
return u[i] < u[j]
}
// 定时通知 从指定的开始时间起,根据设置的时间间隔周期性通知
type RegularNotice struct {
StartAt *time.Time `bson:"regularStartAt,omitempty"` //开始时间
TimeUnit *TimeUnit `bson:"regularTimeUnit,omitempty"` //单位 分钟/小时/天/周
TimeCount *int64 `bson:"regularTimeCount,omitempty"` //量
}
func (t *RegularNotice) NextSendTimeList(current time.Time, lastSendAt time.Time) TimeSlice {
if t.StartAt == nil || t.TimeUnit == nil || t.TimeCount == nil {
return TimeSlice{}
}
if t.StartAt.After(current) {
return TimeSlice{}
}
per := t.TimeUnit.ToDuration() * time.Duration(*t.TimeCount)
if per == 0 {
return TimeSlice{}
}
curDelta := current.Sub(*t.StartAt)
nextSendTime := t.StartAt.Add(curDelta / per * per)
if lastSendAt.Before(nextSendTime) || lastSendAt.IsZero() {
return TimeSlice{nextSendTime}
}
return TimeSlice{}
}
func (t *RegularNotice) Rule() string {
nextSendTime := t.NextSendTimeList(time.Now(), time.Time{})
nextSendTimeStr := "无"
if nextSendTime.Len() != 0 {
nextSendTimeStr = nextSendTime[0].String()
}
return fmt.Sprintf("从 %s 开始, 每 %d %v, 发送消息\n 下次发送时间: %s", t.StartAt, t.TimeCount, t.TimeUnit, nextSendTimeStr)
}
// 计划通知 根据指定的开始时间、结束时间和通知次数发起通知
type PlanNotice struct {
StartAt *time.Time `bson:"planStartAt,omitempty"` //开始时间
EndAt *time.Time `bson:"planEndAt,omitempty"` //结束时间
PlanCount *int64 `bson:"planPlanCount,omitempty"` //执行次数
}
func (t *PlanNotice) NextSendTimeList(current time.Time, lastSendAt time.Time) TimeSlice {
if t.StartAt == nil || t.EndAt == nil || t.PlanCount == nil || *t.PlanCount == 0 {
return TimeSlice{}
}
if t.StartAt.After(current) || current.After(*t.EndAt) {
return TimeSlice{}
}
totalDelta := t.EndAt.Sub(*t.StartAt)
curDelta := t.EndAt.Sub(current)
per := totalDelta / time.Duration(*t.PlanCount)
nextSendTime := t.StartAt.Add(curDelta / per * per)
if lastSendAt.Before(nextSendTime) || lastSendAt.IsZero() {
return TimeSlice{nextSendTime}
}
return TimeSlice{}
}
func (t *PlanNotice) Rule() string {
nextSendTime := t.NextSendTimeList(time.Now(), time.Time{})
nextSendTimeStr := "无"
if nextSendTime.Len() != 0 {
nextSendTimeStr = nextSendTime[0].String()
}
return fmt.Sprintf("从 %s 开始 至 %s 结束, 计划发送 %d 次消息\n 下次发送时间: %s", t.StartAt, t.EndAt, t.PlanCount, nextSendTimeStr)
}
// 规定通知 到达指定的多个时间点时通知
type SpecifyNotice struct {
SpecifyTimeList TimeSlice `bson:"specifyTimeList,omitempty"` //指定执行时间数组
}
func (t *SpecifyNotice) NextSendTimeList(current time.Time, lastSendAt time.Time) TimeSlice {
if len(t.SpecifyTimeList) == 0 {
return TimeSlice{}
}
sort.Sort(t.SpecifyTimeList)
index := sort.Search(t.SpecifyTimeList.Len(), func(i int) bool {
return t.SpecifyTimeList[i].After(current)
})
index = index - 1 //前一个才是已经过去的时间
if index < 0 {
index = 0
}
nextSendTime := t.SpecifyTimeList[index]
if lastSendAt.Before(nextSendTime) || lastSendAt.IsZero() {
return TimeSlice{nextSendTime}
}
return TimeSlice{}
}
func (t *SpecifyNotice) Rule() string {
nextSendTime := t.NextSendTimeList(time.Now(), time.Time{})
nextSendTimeStr := "无"
if nextSendTime.Len() != 0 {
nextSendTimeStr = nextSendTime[0].String()
}
return fmt.Sprintf("指定时间: %+v 发送\n 下次发送时间: %s", t.SpecifyTimeList, nextSendTimeStr)
}
// 注册后通知 用户注册后开始计时, 消耗指定的时间后通知一次
type AfterRegistNotice struct {
TimeUnit *TimeUnit `bson:"afterTimeUnit,omitempty"` //单位 分钟/小时/天/周
TimeCount *int64 `bson:"afterTimeCount,omitempty"` //量
}
func (t *AfterRegistNotice) Rule() string {
return fmt.Sprintf("用户注册后 %d %v 发送", t.TimeCount, t.TimeUnit)
}
type AfterRegistNoticeEx struct {
AfterRegistNotice
CreatedAt time.Time
}
func (a *AfterRegistNoticeEx) NextSendTimeList(current time.Time, lastSendAt time.Time) TimeSlice {
if a.TimeCount == nil {
return TimeSlice{}
}
interval := a.TimeUnit.ToDuration() * time.Duration(*a.TimeCount)
sendTime := a.CreatedAt.Add(interval)
if current.After(sendTime) && lastSendAt.IsZero() {
return TimeSlice{sendTime}
}
return TimeSlice{}
}
type MailNotice struct {
SendAt *time.Time `bson:"mailSendAt,omitempty"` //发送时间
}
func (m *MailNotice) NextSendTimeList(current time.Time, lastSendAt time.Time) TimeSlice {
if m.SendAt == nil {
return TimeSlice{}
}
if current.After(*m.SendAt) && lastSendAt.IsZero() {
return TimeSlice{*m.SendAt}
}
return TimeSlice{}
}
+149
View File
@@ -0,0 +1,149 @@
package noticefmtmod
import (
"time"
"91porn-server/common/db"
"go.mongodb.org/mongo-driver/bson/primitive"
)
//用于通知业务:系统消息,活动助手
// ObjectID
type ObjectID = primitive.ObjectID
// Sender 发送者
type Sender string
const (
//System 系统消息
System Sender = "SYSTEM"
//ActAsst 活动助手
ActAsst Sender = "ACTASST"
//Reward 打赏
Reward Sender = "REWARD"
)
// Receiver 接收者
type Receiver string
const (
//System 所有用户
AllUser Receiver = "ALL_USER"
//ActAsst 部分用户
SomeUser Receiver = "SOME_USER"
)
// TimeUnit 时间单位
type TimeUnit string
const (
Day TimeUnit = "DAY"
Hour TimeUnit = "HOUR"
Minute TimeUnit = "MINUTE"
Second TimeUnit = "SECOND"
)
func (t *TimeUnit) ToDuration() time.Duration {
switch *t {
case Day:
return time.Hour * 24
case Hour:
return time.Hour
case Minute:
return time.Minute
case Second:
return time.Second
default:
return 0
}
}
// 类型
type NoticeType string
const (
RegularNoticeType NoticeType = "REGULAR_NOTICE_TYPE" //定时通知
PlanNoticeType NoticeType = "PLAN_NOTICE_TYPE" //计划通知
SpecifyNoticeType NoticeType = "SPECIFY_NOTICE_TYPE" //规定通知
AfterRegistNoticeType NoticeType = "AFTER_REGIST_NOTICE" //注册后通知
MailNoticeType NoticeType = "MAIL_NOTICE_TYPE" //邮件通知
)
// 通知格式
type NoticeFmt struct {
ID ObjectID `bson:"_id,omitempty"` //_id
NoticeCode string `bson:"noticeCode"` //通知码 唯一标记不同的通知计划,可代替_id,方便复制数据
Sender Sender `bson:"sender"` //发送者
Receiver Receiver `bson:"receiver"` //接受者
UIDList UIDSlice `bson:"uidList,omitempty"` //用户列表
Title string `bson:"title"` //标题
Content string `bson:"content"` //内容
Enable bool `bson:"enable"` //消息开关 true:允许通知Receiver,否则不允许通知Receiver
Remark string `bson:"remark"` //备注
NoticeType NoticeType `bson:"noticeType"` //通知类型
RegularNotice `bson:",inline"` //定时通知 在开始时间和结束时间内,根据指定的时间单位周期性通知
PlanNotice `bson:",inline"` //计划通知 根据指定的开始时间、通知间隔和通知次数发起周期性通知
SpecifyNotice `bson:",inline"` //规定通知 到达指定的多个时间点时通知
AfterRegistNotice `bson:",inline"` //注册后通知 用户注册后开始计时, 消耗指定的时间后通知一次
MailNotice `bson:",inline"` //邮件通知只通知一次
UpdatedAt time.Time `bson:"updatedAt"` //文档更新时间
CreatedAt time.Time `bson:"createdAt"` //文档创建时间
//打赏需要
RewarderName string `bson:"rewarderName,omitempty"`
RewarderPortrait string `bson:"rewarderPortrait,omitempty"`
Coins string `bson:"coins,omitempty"`
VidCover string `bson:"vidCover,omitempty"`
VidObjId primitive.ObjectID `bson:"vidObjId,omitempty"`
}
type InsertDoc struct {
NoticeCode string `bson:"noticeCode"` //通知码 唯一标记不同的通知计划,可代替_id,方便复制数据
Sender Sender `bson:"sender"` //发送者
Receiver Receiver `bson:"receiver"` //接受者
UIDList UIDSlice `bson:"uidList,omitempty"` //用户列表
Title string `bson:"title"` //标题
Content string `bson:"content"` //内容
Enable bool `bson:"enable"` //消息开关 true:允许通知Receiver,否则不允许通知Receiver
Remark string `bson:"remark"` //备注
NoticeType NoticeType `bson:"noticeType"` //通知类型
RegularNotice `bson:",inline"` //定时通知 在开始时间和结束时间内,根据指定的时间单位周期性通知
PlanNotice `bson:",inline"` //计划通知 根据指定的开始时间、通知间隔和通知次数发起周期性通知
SpecifyNotice `bson:",inline"` //规定通知 到达指定的多个时间点时通知
AfterRegistNotice `bson:",inline"` //注册后通知 用户注册后开始计时, 消耗指定的时间后通知一次
MailNotice `bson:",inline"` //邮件通知只通知一次
//打赏需要
RewarderName string `bson:"rewarderName,omitempty"`
RewarderPortrait string `bson:"rewarderPortrait,omitempty"`
Coins string `bson:"coins,omitempty"`
VidCover string `bson:"vidCover,omitempty"`
VidObjId primitive.ObjectID `bson:"vidObjId,omitempty"`
}
type UpdateDoc struct {
Sender *Sender `bson:"sender"` //发送者
Receiver *Receiver `bson:"receiver"` //接受者
UIDList UIDSlice `bson:"uidList,omitempty"` //用户列表
Title *string `bson:"title"` //标题
Content *string `bson:"content"` //内容
Enable *bool `bson:"enable"` //消息开关 true:允许通知Receiver,否则不允许通知Receiver
Remark *string `bson:"remark"` //备注
NoticeType *NoticeType `bson:"noticeType"` //通知类型
RegularNotice `bson:",inline"` //定时通知 在开始时间和结束时间内,根据指定的时间单位周期性通知
PlanNotice `bson:",inline"` //计划通知 根据指定的开始时间、通知间隔和通知次数发起周期性通知
SpecifyNotice `bson:",inline"` //规定通知 到达指定的多个时间点时通知
AfterRegistNotice `bson:",inline"` //注册后通知 用户注册后开始计时, 消耗指定的时间后通知一次
MailNotice `bson:",inline"` //邮件通知 只通知一次
}
func Init() {
mdb = db.Init(table)
initIndex()
}