Skip to content

Commit cfb441d

Browse files
committed
feat: merge push function
1 parent ebf4c60 commit cfb441d

10 files changed

Lines changed: 157 additions & 69 deletions

File tree

cmd/common/main.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -216,15 +216,7 @@ func syncNoticeTask(ctx context.Context) error {
216216
// 进行消息推送
217217
if ok := umeng.EnqueueAsync(func() error {
218218
deeplink := constants.UmengJwchNoticeDeeplink + "?url=" + url.QueryEscape(info.URL)
219-
err = umeng.SendAndroidGroupcastWithGoApp(constants.UmengJwchNotificationTitle, info.Title, "", constants.UmengJwchNoticeTag, "教务处", deeplink)
220-
if err != nil {
221-
logger.WithCtx(ctx).Errorf("notice sync task: failed to send notice to Android: %v", err)
222-
}
223-
224-
err = umeng.SendIOSGroupcast(constants.UmengJwchNotificationTitle, "", info.Title, constants.UmengJwchNoticeTag, "教务处", deeplink)
225-
if err != nil {
226-
logger.WithCtx(ctx).Errorf("notice sync task: failed to send notice to IOS: %v", err)
227-
}
219+
umeng.PushByType(constants.UmengPushTypeTeaching, constants.UmengJwchNotificationTitle, info.Title, "", constants.UmengJwchNoticeTag, "教务处", deeplink)
228220
logger.WithCtx(ctx).Infof("notice sync task: notice send success")
229221
return nil
230222
}); !ok {

config/types.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,8 @@ type XiaomiNoticeTemplate struct {
167167
TemplateID string `mapstructure:"template_id"`
168168
}
169169

170-
type XiaomiNotice struct {
171-
Score XiaomiNoticeTemplate `mapstructure:"score"`
172-
Exam XiaomiNoticeTemplate `mapstructure:"exam"`
173-
Teaching XiaomiNoticeTemplate `mapstructure:"teaching"`
174-
}
170+
// XiaomiNotice 小米推送模板配置,key 为推送类型(score/exam/teaching),对应 pkg/constants 中的 UmengPushType*
171+
type XiaomiNotice map[string]XiaomiNoticeTemplate
175172

176173
type vendors struct {
177174
ChannelActivity string `mapstructure:"channel_activity"`

internal/academic/service/get_scores.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -205,14 +205,7 @@ func (s *AcademicService) sendNotifications(courseName, tag string) (err error)
205205
title := constants.UmengGradeNotificationTitle
206206
text := fmt.Sprintf("%v%v", courseName, constants.UmengGradeNotificationBodySuffix)
207207
description := fmt.Sprintf("成绩更新%v", tag[:12])
208-
err = umeng.SendAndroidGroupcastWithGoApp(title, text, "", tag, description, constants.UmengGradeDeeplink)
209-
if err != nil {
210-
logger.Errorf("task queue: failed to send notice to Android: %v", err)
211-
}
212-
err = umeng.SendIOSGroupcast(title, "", text, tag, description, constants.UmengGradeDeeplink)
213-
if err != nil {
214-
logger.Errorf("task queue: failed to send notice to IOS: %v", err)
215-
}
208+
umeng.PushByType(constants.UmengPushTypeScore, title, text, "", tag, description, constants.UmengGradeDeeplink)
216209

217210
logger.Infof("task queue: send notice to app, tag:%v", tag)
218211
return nil

internal/academic/service/get_scores_test.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
baseContext "github.com/west2-online/fzuhelper-server/pkg/base/context"
3131
"github.com/west2-online/fzuhelper-server/pkg/cache"
3232
academicCache "github.com/west2-online/fzuhelper-server/pkg/cache/academic"
33+
"github.com/west2-online/fzuhelper-server/pkg/constants"
3334
"github.com/west2-online/fzuhelper-server/pkg/db"
3435
academicDB "github.com/west2-online/fzuhelper-server/pkg/db/academic"
3536
dbModel "github.com/west2-online/fzuhelper-server/pkg/db/model"
@@ -615,11 +616,27 @@ func TestAcademicService_sendNotifications(t *testing.T) {
615616
courseName := "数据结构"
616617
tag := "abcdefghijklmnopqrstuvwxyz123456"
617618

618-
// Mock umeng 推送成功
619-
umengAndroidPatch := mockey.Mock(umeng.SendAndroidGroupcastWithGoApp).Return(nil).Build()
619+
// Mock umeng 推送成功,并断言按成绩类型下发
620+
umengAndroidPatch := mockey.Mock(umeng.SendAndroidGroupcastWithGoApp).To(
621+
func(pushType, title, text, ticker, gotTag, description, deeplink string) error {
622+
So(pushType, ShouldEqual, constants.UmengPushTypeScore)
623+
So(title, ShouldEqual, constants.UmengGradeNotificationTitle)
624+
So(text, ShouldEqual, courseName+constants.UmengGradeNotificationBodySuffix)
625+
So(gotTag, ShouldEqual, tag)
626+
So(description, ShouldEqual, fmt.Sprintf("成绩更新%v", tag[:12]))
627+
So(deeplink, ShouldEqual, constants.UmengGradeDeeplink)
628+
return nil
629+
},
630+
).Build()
620631
defer umengAndroidPatch.UnPatch()
621632

622-
umengIOSPatch := mockey.Mock(umeng.SendIOSGroupcast).Return(nil).Build()
633+
iosCalled := false
634+
umengIOSPatch := mockey.Mock(umeng.SendIOSGroupcast).To(
635+
func(title, subtitle, body, tag, description, deeplink string) error {
636+
iosCalled = true
637+
return nil
638+
},
639+
).Build()
623640
defer umengIOSPatch.UnPatch()
624641

625642
ctx := context.Background()
@@ -631,6 +648,7 @@ func TestAcademicService_sendNotifications(t *testing.T) {
631648

632649
// Then: 应该成功发送推送
633650
So(err, ShouldBeNil)
651+
So(iosCalled, ShouldBeTrue)
634652
})
635653

636654
Convey("should handle notification errors gracefully", func() {

internal/course/service/exam_snapshot_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/bytedance/mockey"
2323
"github.com/stretchr/testify/assert"
2424

25+
"github.com/west2-online/fzuhelper-server/pkg/constants"
2526
"github.com/west2-online/fzuhelper-server/pkg/umeng"
2627
"github.com/west2-online/fzuhelper-server/pkg/utils"
2728
"github.com/west2-online/jwch"
@@ -178,7 +179,8 @@ func TestCourseServiceSendExamNotification(t *testing.T) {
178179
for _, tt := range tests {
179180
mockey.PatchConvey(tt.name, t, func() {
180181
mockey.Mock(umeng.SendAndroidGroupcastWithGoApp).To(
181-
func(title, text, ticker, tag, description, deeplink string) error {
182+
func(pushType, title, text, ticker, tag, description, deeplink string) error {
183+
assert.Equal(t, constants.UmengPushTypeExam, pushType)
182184
assert.Equal(t, "fzuhelper://exam-room", deeplink)
183185
return tt.androidErr
184186
},

internal/course/service/get_course_list.go

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import (
3434
"github.com/west2-online/fzuhelper-server/pkg/constants"
3535
"github.com/west2-online/fzuhelper-server/pkg/db/model"
3636
"github.com/west2-online/fzuhelper-server/pkg/errno"
37-
"github.com/west2-online/fzuhelper-server/pkg/logger"
3837
"github.com/west2-online/fzuhelper-server/pkg/taskqueue"
3938
"github.com/west2-online/fzuhelper-server/pkg/umeng"
4039
"github.com/west2-online/fzuhelper-server/pkg/utils"
@@ -266,17 +265,7 @@ func (s *CourseService) sendExamNotification(change courseExamChange) {
266265
title := constants.UmengExamNotificationTitle
267266
text := fmt.Sprintf("%v%v", change.Exam.Name, constants.UmengExamNotificationBodySuffix)
268267
description := fmt.Sprintf("考试信息更新%v", change.Tag[:12])
269-
if err := umeng.SendAndroidGroupcastWithGoApp(
270-
title, text, "", change.Tag, description, constants.UmengExamRoomDeeplink,
271-
); err != nil {
272-
logger.Errorf("CourseService.sendExamNotification: send Android notification failed: %v", err)
273-
}
274-
275-
if err := umeng.SendIOSGroupcast(
276-
title, "", text, change.Tag, description, constants.UmengExamRoomDeeplink,
277-
); err != nil {
278-
logger.Errorf("CourseService.sendExamNotification: send iOS notification failed: %v", err)
279-
}
268+
umeng.PushByType(constants.UmengPushTypeExam, title, text, "", change.Tag, description, constants.UmengExamRoomDeeplink)
280269
}
281270

282271
func (s *CourseService) GetCourseListYjsy(req *course.CourseListRequest, loginData *kitexModel.LoginData) ([]*kitexModel.Course, error) {

pkg/constants/umeng.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ const (
3737
UmengJwchNoticeDeeplink = "fzuhelper://office_notice" // 教务处通知的deeplink
3838
)
3939

40+
// 推送类型,用于按业务场景选择对应的推送模板
41+
const (
42+
UmengPushTypeScore = "score" // 推送类型:成绩通知
43+
UmengPushTypeExam = "exam" // 推送类型:考试通知
44+
UmengPushTypeTeaching = "teaching" // 推送类型:教务处通知
45+
)
46+
4047
const (
4148
UmengXiaomiTemplateKeyword = "keywords1" // 小米模板的参数占位符
4249
UmengGradeNotificationTitle = "成绩更新啦"

pkg/umeng/groupcast.go

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -58,27 +58,18 @@ func getChannelProperties(title, content string) AndroidChannelProperties {
5858
}
5959
}
6060

61-
func getXiaomiNoticeProperties(text, deeplink string, notice config.XiaomiNotice) (string, *XiaomiExtraProperties) {
62-
var template config.XiaomiNoticeTemplate
63-
var keyword string
64-
baseDeeplink, _, _ := strings.Cut(deeplink, "?")
65-
// 这里分割是因为后端传给前端的教务处通知的deeplink是带有url的
66-
// 所以要进行一个分割,让他能够匹配基础的类型(常量包定义)
67-
switch baseDeeplink {
68-
case constants.UmengGradeDeeplink:
69-
template = notice.Score
70-
keyword = strings.TrimSuffix(text, constants.UmengGradeNotificationBodySuffix)
71-
// 从具体内容当中分割出{"keywords1"}
72-
case constants.UmengExamRoomDeeplink:
73-
template = notice.Exam
74-
keyword = strings.TrimSuffix(text, constants.UmengExamNotificationBodySuffix)
75-
case constants.UmengJwchNoticeDeeplink:
76-
template = notice.Teaching
77-
keyword = text
78-
default:
61+
// getXiaomiNoticeProperties 按推送类型从小米模板注册表与配置中选取模板,
62+
// 模板参数由 keyword 提取规则(去掉正文后缀)得到
63+
func getXiaomiNoticeProperties(text, pushType string, notice config.XiaomiNotice) (string, *XiaomiExtraProperties) {
64+
kind, ok := xiaomiTemplateKinds[pushType]
65+
if !ok {
7966
return "", nil
8067
}
81-
68+
template, ok := notice[pushType]
69+
if !ok {
70+
return "", nil
71+
}
72+
keyword := strings.TrimSuffix(text, kind.KeywordSuffix)
8273
if template.ChannelID == "" || template.TemplateID == "" || keyword == "" {
8374
return "", nil
8475
}
@@ -99,11 +90,21 @@ func getXiaomiNoticeProperties(text, deeplink string, notice config.XiaomiNotice
9990
}
10091
}
10192

102-
func SendAndroidGroupcastWithGoApp(title, text, ticker, tag, description, deeplink string) error {
93+
// PushByType 按推送类型同时下发安卓与 iOS 推送,任一端失败仅记录日志,不影响业务(尽力而为)
94+
func PushByType(pushType, title, text, ticker, tag, description, deeplink string) {
95+
if err := SendAndroidGroupcastWithGoApp(pushType, title, text, ticker, tag, description, deeplink); err != nil {
96+
logger.Errorf("umeng.PushByType: %s failed to send Android groupcast: %v", pushType, err)
97+
}
98+
if err := SendIOSGroupcast(title, "", text, tag, description, deeplink); err != nil {
99+
logger.Errorf("umeng.PushByType: %s failed to send IOS groupcast: %v", pushType, err)
100+
}
101+
}
102+
103+
func SendAndroidGroupcastWithGoApp(pushType, title, text, ticker, tag, description, deeplink string) error {
103104
channelProperties := getChannelProperties(title, text)
104105
xiaomiChannelID, xiaomiExtraProperties := getXiaomiNoticeProperties(
105106
text,
106-
deeplink,
107+
pushType,
107108
config.Vendors.XiaomiNotice,
108109
)
109110
if xiaomiChannelID != "" && xiaomiExtraProperties != nil {

pkg/umeng/groupcast_test.go

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,72 @@ package umeng
1919
import (
2020
"testing"
2121

22+
"github.com/bytedance/mockey"
23+
"github.com/stretchr/testify/assert"
24+
2225
"github.com/west2-online/fzuhelper-server/config"
2326
"github.com/west2-online/fzuhelper-server/pkg/constants"
2427
)
2528

29+
func TestPushByType(t *testing.T) {
30+
tests := []struct {
31+
name string
32+
pushType string
33+
androidErr error
34+
iosErr error
35+
}{
36+
{
37+
name: "send to both android and ios",
38+
pushType: constants.UmengPushTypeScore,
39+
},
40+
{
41+
name: "ignore android and ios failures",
42+
pushType: constants.UmengPushTypeExam,
43+
androidErr: assert.AnError,
44+
iosErr: assert.AnError,
45+
},
46+
}
47+
48+
for _, tt := range tests {
49+
t.Run(tt.name, func(t *testing.T) {
50+
var androidPushType string
51+
androidPatch := mockey.Mock(SendAndroidGroupcastWithGoApp).To(
52+
func(pushType, title, text, ticker, tag, description, deeplink string) error {
53+
androidPushType = pushType
54+
return tt.androidErr
55+
},
56+
).Build()
57+
defer androidPatch.UnPatch()
58+
59+
iosCalled := false
60+
iosPatch := mockey.Mock(SendIOSGroupcast).To(
61+
func(title, subtitle, body, tag, description, deeplink string) error {
62+
iosCalled = true
63+
return tt.iosErr
64+
},
65+
).Build()
66+
defer iosPatch.UnPatch()
67+
68+
// PushByType 为尽力而为,两端失败仅记录日志,不应 panic
69+
PushByType(tt.pushType, "title", "text", "ticker", "tag", "description", "deeplink")
70+
71+
assert.Equal(t, tt.pushType, androidPushType)
72+
assert.True(t, iosCalled)
73+
})
74+
}
75+
}
76+
2677
func TestGetXiaomiNoticeProperties(t *testing.T) {
2778
notice := config.XiaomiNotice{
28-
Score: config.XiaomiNoticeTemplate{
79+
constants.UmengPushTypeScore: {
2980
ChannelID: "score-channel",
3081
TemplateID: "P12395",
3182
},
32-
Exam: config.XiaomiNoticeTemplate{
83+
constants.UmengPushTypeExam: {
3384
ChannelID: "exam-channel",
3485
TemplateID: "P12394",
3586
},
36-
Teaching: config.XiaomiNoticeTemplate{
87+
constants.UmengPushTypeTeaching: {
3788
ChannelID: "teaching-channel",
3889
TemplateID: "P12325",
3990
},
@@ -42,44 +93,44 @@ func TestGetXiaomiNoticeProperties(t *testing.T) {
4293
tests := []struct {
4394
name string
4495
text string
45-
deeplink string
96+
pushType string
4697
wantChannelID string
4798
wantTemplate string
4899
wantKeyword string
49100
}{
50101
{
51102
name: "score",
52103
text: "数据结构" + constants.UmengGradeNotificationBodySuffix,
53-
deeplink: constants.UmengGradeDeeplink,
104+
pushType: constants.UmengPushTypeScore,
54105
wantChannelID: "score-channel",
55106
wantTemplate: "P12395",
56107
wantKeyword: "数据结构",
57108
},
58109
{
59110
name: "exam",
60111
text: "数据结构" + constants.UmengExamNotificationBodySuffix,
61-
deeplink: constants.UmengExamRoomDeeplink,
112+
pushType: constants.UmengPushTypeExam,
62113
wantChannelID: "exam-channel",
63114
wantTemplate: "P12394",
64115
wantKeyword: "数据结构",
65116
},
66117
{
67118
name: "teaching",
68119
text: "关于补考安排的通知",
69-
deeplink: constants.UmengJwchNoticeDeeplink + "?url=https%3A%2F%2Fexample.com%2Fnotice",
120+
pushType: constants.UmengPushTypeTeaching,
70121
wantChannelID: "teaching-channel",
71122
wantTemplate: "P12325",
72123
wantKeyword: "关于补考安排的通知",
73124
},
74125
{
75-
name: "unknown deeplink",
76-
deeplink: "fzuhelper://unknown",
126+
name: "unknown push type",
127+
pushType: "unknown",
77128
},
78129
}
79130

80131
for _, tt := range tests {
81132
t.Run(tt.name, func(t *testing.T) {
82-
gotChannelID, gotProperties := getXiaomiNoticeProperties(tt.text, tt.deeplink, notice)
133+
gotChannelID, gotProperties := getXiaomiNoticeProperties(tt.text, tt.pushType, notice)
83134
if gotChannelID != tt.wantChannelID {
84135
t.Fatalf("channel ID = %q, want %q", gotChannelID, tt.wantChannelID)
85136
}

pkg/umeng/xiaomi_template.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
Copyright 2024 The west2-online Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package umeng
18+
19+
import (
20+
"github.com/west2-online/fzuhelper-server/pkg/constants"
21+
)
22+
23+
// XiaomiTemplateKind 描述一种推送类型对应的小米模板 keyword 提取规则
24+
type XiaomiTemplateKind struct {
25+
// KeywordSuffix 从 text 中提取 keywords1 时去掉的正文后缀,空串表示取完整 text
26+
KeywordSuffix string
27+
}
28+
29+
// xiaomiTemplateKinds 推送类型注册表,新增推送类型只需在配置中增加模板并在这里注册一项
30+
var xiaomiTemplateKinds = map[string]XiaomiTemplateKind{
31+
constants.UmengPushTypeScore: {
32+
KeywordSuffix: constants.UmengGradeNotificationBodySuffix,
33+
},
34+
constants.UmengPushTypeExam: {
35+
KeywordSuffix: constants.UmengExamNotificationBodySuffix,
36+
},
37+
constants.UmengPushTypeTeaching: {},
38+
}

0 commit comments

Comments
 (0)