feat: add sign in reward admin flow

This commit is contained in:
hy001 2026-04-20 22:07:10 +08:00
parent 2ebdd680f5
commit df9a779b62
9 changed files with 317 additions and 67 deletions

View File

@ -118,6 +118,7 @@ type LuckyGiftProviderConfig struct {
func Load() Config {
javaAppBaseURL := getEnvAny([]string{"CHATAPP_JAVA_APP_BASE_URL", "INVITE_JAVA_APP_BASE_URL"}, "http://127.0.0.1:2400")
consoleBaseURL := getEnvAny([]string{"CHATAPP_JAVA_CONSOLE_BASE_URL", "JAVA_CONSOLE_BASE_URL"}, "http://127.0.0.1:2700/console")
return Config{
HTTP: HTTPConfig{
@ -134,10 +135,10 @@ func Load() Config {
},
Java: JavaConfig{
AuthBaseURL: getEnvAny([]string{"CHATAPP_JAVA_AUTH_BASE_URL", "INVITE_JAVA_AUTH_BASE_URL"}, "http://127.0.0.1:2100"),
ConsoleBaseURL: getEnvAny([]string{"CHATAPP_JAVA_CONSOLE_BASE_URL", "JAVA_CONSOLE_BASE_URL"}, "http://127.0.0.1:2700/console"),
ConsoleBaseURL: consoleBaseURL,
DeviceBaseURL: getEnvAny([]string{"CHATAPP_JAVA_DEVICE_BASE_URL", "INVITE_JAVA_DEVICE_BASE_URL"}, "http://127.0.0.1:2400"),
AppBaseURL: javaAppBaseURL,
BridgeBaseURL: getEnvAny([]string{"CHATAPP_JAVA_BRIDGE_BASE_URL", "INVITE_JAVA_BRIDGE_BASE_URL"}, "http://127.0.0.1:2200"),
BridgeBaseURL: getEnvAny([]string{"CHATAPP_JAVA_BRIDGE_BASE_URL", "INVITE_JAVA_BRIDGE_BASE_URL"}, consoleBaseURL),
ExternalBaseURL: getEnvAny([]string{"CHATAPP_JAVA_EXTERNAL_BASE_URL", "INVITE_JAVA_EXTERNAL_BASE_URL"}, "http://127.0.0.1:3000"),
OtherBaseURL: getEnvAny([]string{"CHATAPP_JAVA_OTHER_BASE_URL", "GAME_JAVA_OTHER_BASE_URL", "INVITE_JAVA_APP_BASE_URL"}, javaAppBaseURL),
WalletBaseURL: getEnvAny([]string{"CHATAPP_JAVA_WALLET_BASE_URL", "GAME_JAVA_WALLET_BASE_URL"}, "http://127.0.0.1:2300"),

View File

@ -296,11 +296,11 @@ func (c *Client) EnsureInviteCode(ctx context.Context, authorization string) (In
}
func (c *Client) GrantGold(ctx context.Context, req GrantGoldRequest) error {
return c.postJSON(ctx, c.cfg.Java.BridgeBaseURL+"/internal/resident-activity/invite/grant-gold", req, nil, nil)
return c.postJSON(ctx, c.rewardBridgeBaseURL()+"/internal/resident-activity/invite/grant-gold", req, nil, nil)
}
func (c *Client) GrantProps(ctx context.Context, req GrantPropsRequest) error {
return c.postJSON(ctx, c.cfg.Java.BridgeBaseURL+"/internal/resident-activity/invite/grant-props", req, nil, nil)
return c.postJSON(ctx, c.rewardBridgeBaseURL()+"/internal/resident-activity/invite/grant-props", req, nil, nil)
}
func (c *Client) SendActivityReward(ctx context.Context, req SendActivityRewardRequest) error {
@ -336,6 +336,13 @@ func (c *Client) GetUserProfile(ctx context.Context, userID int64) (UserProfile,
return resp.Body, nil
}
func (c *Client) rewardBridgeBaseURL() string {
if baseURL := strings.TrimRight(strings.TrimSpace(c.cfg.Java.ConsoleBaseURL), "/"); baseURL != "" {
return baseURL
}
return strings.TrimRight(strings.TrimSpace(c.cfg.Java.BridgeBaseURL), "/")
}
func (c *Client) GetUserLanguage(ctx context.Context, userID int64) (string, error) {
endpoint := c.cfg.Java.OtherBaseURL + "/user-profile/client/getLanguage?userId=" + url.QueryEscape(strconv.FormatInt(userID, 10))
var resp resultResponse[string]

View File

@ -3,6 +3,8 @@ package router
import (
"chatapp3-golang/internal/service/signinreward"
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
)
@ -54,6 +56,24 @@ func registerSignInRewardRoutes(
}
writeOK(c, resp)
})
residentGroup.GET("/record/page", func(c *gin.Context) {
cursor, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("cursor", "1")))
limit, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("limit", "20")))
resp, err := signInRewardService.PageRecords(
c.Request.Context(),
c.Query("sysOrigin"),
c.Query("claimDate"),
c.Query("status"),
signinreward.ParseSignInRewardUserID(c.Query("userId")),
cursor,
limit,
)
if err != nil {
writeError(c, err)
return
}
writeOK(c, resp)
})
residentGroup.POST("/config/save", func(c *gin.Context) {
var req signinreward.SaveSignInRewardConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {

View File

@ -0,0 +1,159 @@
package signinreward
import (
"context"
"net/http"
"strconv"
"strings"
"time"
"chatapp3-golang/internal/model"
"gorm.io/gorm"
)
type signInRewardRecordRow struct {
model.SignInRewardLog
Account string `gorm:"column:account"`
UserAvatar string `gorm:"column:user_avatar"`
UserNickname string `gorm:"column:user_nickname"`
CountryCode string `gorm:"column:country_code"`
CountryName string `gorm:"column:country_name"`
}
// PageRecords 返回后台签到记录分页数据。
func (s *SignInRewardService) PageRecords(
ctx context.Context,
sysOrigin string,
claimDate string,
status string,
userID int64,
cursor int,
limit int,
) (*SignInRewardRecordPageResponse, error) {
sysOrigin = normalizeSignInRewardSysOrigin(sysOrigin)
if sysOrigin == "" {
return nil, NewAppError(http.StatusBadRequest, "bad_request", "sysOrigin is required")
}
bundle, err := s.loadConfigBundle(ctx, sysOrigin)
if err != nil {
return nil, err
}
location := resolveSignInRewardLocation(signInRewardDefaultTimezone)
if bundle.Config != nil {
location = resolveSignInRewardLocation(bundle.Config.Timezone)
}
resolvedDate := normalizeSignInRewardClaimDate(claimDate, location)
page, size := normalizeSignInRewardPage(cursor, limit)
query := s.newRecordBaseQuery(ctx, sysOrigin, resolvedDate)
if strings.TrimSpace(status) != "" {
query = query.Where("log.status = ?", strings.ToUpper(strings.TrimSpace(status)))
}
if userID > 0 {
query = query.Where("log.user_id = ?", userID)
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, err
}
var signCount int64
if err := s.newRecordBaseQuery(ctx, sysOrigin, resolvedDate).Count(&signCount).Error; err != nil {
return nil, err
}
var successCount int64
if err := s.newRecordBaseQuery(ctx, sysOrigin, resolvedDate).
Where("log.status = ?", signInRewardStatusSuccess).
Count(&successCount).Error; err != nil {
return nil, err
}
records := make([]SignInRewardRecordView, 0)
if total > 0 {
var rows []signInRewardRecordRow
if err := query.
Select("log.*, user.account, user.user_avatar, user.user_nickname, user.country_code, user.country_name").
Joins("LEFT JOIN user_base_info AS user ON user.id = log.user_id").
Order("log.create_time desc").
Order("log.id desc").
Offset((page - 1) * size).
Limit(size).
Find(&rows).Error; err != nil {
return nil, err
}
records = make([]SignInRewardRecordView, 0, len(rows))
for _, row := range rows {
records = append(records, SignInRewardRecordView{
ID: row.ID,
UserID: row.UserID,
Account: row.Account,
UserAvatar: row.UserAvatar,
UserNickname: row.UserNickname,
CountryCode: row.CountryCode,
CountryName: row.CountryName,
ClaimDate: row.ClaimDate,
DayIndex: row.DayIndex,
StreakCount: row.StreakCount,
RewardGroupID: row.RewardGroupID,
RewardGroupName: row.RewardGroupName,
Status: strings.ToUpper(strings.TrimSpace(row.Status)),
ErrorMessage: strings.TrimSpace(row.ErrorMessage),
CreateTime: formatSignInRewardDateTime(row.CreateTime),
UpdateTime: formatSignInRewardDateTime(row.UpdateTime),
})
}
}
return &SignInRewardRecordPageResponse{
Date: resolvedDate,
Timezone: location.String(),
SignCount: signCount,
SuccessCount: successCount,
Records: records,
Total: total,
}, nil
}
func (s *SignInRewardService) newRecordBaseQuery(ctx context.Context, sysOrigin string, claimDate string) *gorm.DB {
return s.repo.DB.WithContext(ctx).
Table("sign_in_reward_log AS log").
Where("log.sys_origin = ? AND log.claim_date = ?", sysOrigin, claimDate)
}
func normalizeSignInRewardClaimDate(claimDate string, location *time.Location) string {
claimDate = strings.TrimSpace(claimDate)
if claimDate != "" {
return claimDate
}
return time.Now().In(location).Format("2006-01-02")
}
func normalizeSignInRewardPage(cursor int, limit int) (int, int) {
if cursor <= 0 {
cursor = 1
}
if limit <= 0 {
limit = 20
}
if limit > 100 {
limit = 100
}
return cursor, limit
}
// ParseSignInRewardUserID 解析后台分页查询里传入的 userId。
func ParseSignInRewardUserID(raw string) int64 {
value := strings.TrimSpace(raw)
if value == "" {
return 0
}
parsed, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return 0
}
return parsed
}

View File

@ -42,7 +42,6 @@ func (s *SignInRewardService) GetStatus(ctx context.Context, user AuthUser) (*Si
for _, day := range bundle.Days {
days = append(days, SignInRewardDayPayload{
DayIndex: day.DayIndex,
GoldAmount: day.GoldAmount,
RewardGroupID: day.RewardGroupID,
RewardGroupName: day.RewardGroupName,
RewardItems: itemsMap[rewardGroupKey(day.RewardGroupID)],
@ -115,7 +114,7 @@ func (s *SignInRewardService) CheckIn(ctx context.Context, user AuthUser) (*Sign
if err != nil {
return nil, err
}
if dayConfig.GoldAmount <= 0 && dayConfig.RewardGroupID == nil {
if dayConfig.RewardGroupID == nil {
return nil, NewAppError(http.StatusBadRequest, "sign_in_reward_empty", "reward for current day is not configured")
}
@ -263,9 +262,6 @@ func (s *SignInRewardService) ensureTodayLog(
if existing.StreakCount <= 0 {
existing.StreakCount = state.CurrentStreak + 1
}
if existing.GoldAmount <= 0 {
existing.GoldAmount = day.GoldAmount
}
if existing.RewardGroupID == nil {
existing.RewardGroupID = day.RewardGroupID
existing.RewardGroupName = day.RewardGroupName
@ -276,7 +272,7 @@ func (s *SignInRewardService) ensureTodayLog(
Updates(map[string]any{
"day_index": existing.DayIndex,
"streak_count": existing.StreakCount,
"gold_amount": existing.GoldAmount,
"gold_amount": 0,
"reward_group_id": existing.RewardGroupID,
"reward_group_name": strings.TrimSpace(existing.RewardGroupName),
"status": signInRewardStatusPending,
@ -303,7 +299,7 @@ func (s *SignInRewardService) ensureTodayLog(
ClaimDate: state.Today,
DayIndex: day.DayIndex,
StreakCount: state.CurrentStreak + 1,
GoldAmount: day.GoldAmount,
GoldAmount: 0,
RewardGroupID: day.RewardGroupID,
RewardGroupName: day.RewardGroupName,
Status: signInRewardStatusPending,
@ -325,29 +321,6 @@ func (s *SignInRewardService) dispatchCheckInReward(
user AuthUser,
logRecord *model.SignInRewardLog,
) error {
if logRecord.GoldAmount > 0 {
goldEventID := logRecord.EventID + ":gold"
exists, err := s.java.ExistsGoldEvent(ctx, goldEventID)
if err != nil {
return NewAppError(http.StatusBadGateway, "sign_in_reward_wallet_exists_failed", err.Error())
}
if !exists {
if err := s.java.ChangeGoldBalance(ctx, integration.GoldReceiptCommand{
ReceiptType: "INCOME",
UserID: user.UserID,
SysOrigin: config.SysOrigin,
EventID: goldEventID,
Remark: fmt.Sprintf("signInDate=%s dayIndex=%d", logRecord.ClaimDate, logRecord.DayIndex),
Amount: integration.NewPennyAmountPayloadFromDollar(logRecord.GoldAmount),
CloseDelayAsset: false,
OpUserType: "APP",
CustomizeOrigin: signInRewardGoldOrigin,
CustomizeOriginDesc: signInRewardGoldDesc,
}); err != nil {
return NewAppError(http.StatusBadGateway, "sign_in_reward_wallet_failed", err.Error())
}
}
}
if logRecord.RewardGroupID != nil {
if err := s.java.GrantProps(ctx, integration.GrantPropsRequest{
TrackID: logRecord.ID,
@ -422,7 +395,6 @@ func (s *SignInRewardService) buildCheckInResponse(
ClaimDate: logRecord.ClaimDate,
DayIndex: logRecord.DayIndex,
StreakCount: logRecord.StreakCount,
GoldAmount: logRecord.GoldAmount,
RewardGroupID: logRecord.RewardGroupID,
RewardGroupName: logRecord.RewardGroupName,
RewardItems: rewardItems,

View File

@ -102,8 +102,8 @@ func (s *SignInRewardService) SaveConfig(ctx context.Context, req SaveSignInRewa
ID: dayID,
ConfigID: configRow.ID,
DayIndex: item.DayIndex,
GoldAmount: item.GoldAmount,
RewardGroupID: item.RewardGroupID,
GoldAmount: 0,
RewardGroupID: item.RewardGroupID.Int64Ptr(),
RewardGroupName: strings.TrimSpace(item.RewardGroupName),
CreateTime: now,
UpdateTime: now,
@ -139,15 +139,11 @@ func normalizeSaveDays(days []SignInRewardDayConfigInput, enabled bool) ([]SignI
return nil, NewAppError(http.StatusBadRequest, "duplicate_day_index", "dayIndex must be unique")
}
seen[item.DayIndex] = struct{}{}
if item.GoldAmount < 0 {
return nil, NewAppError(http.StatusBadRequest, "invalid_gold_amount", "goldAmount must be greater than or equal to 0")
}
if enabled && item.GoldAmount <= 0 && item.RewardGroupID == nil {
return nil, NewAppError(http.StatusBadRequest, "empty_day_reward", "each enabled day must configure goldAmount or rewardGroupId")
if enabled && item.RewardGroupID.Int64Ptr() == nil {
return nil, NewAppError(http.StatusBadRequest, "empty_day_reward", "each enabled day must configure rewardGroupId")
}
normalized = append(normalized, SignInRewardDayConfigInput{
DayIndex: item.DayIndex,
GoldAmount: item.GoldAmount,
RewardGroupID: item.RewardGroupID,
RewardGroupName: strings.TrimSpace(item.RewardGroupName),
})
@ -195,7 +191,6 @@ func (s *SignInRewardService) loadConfigBundle(ctx context.Context, sysOrigin st
result.Days = append(result.Days, signInRewardDaySnapshot{
ID: row.ID,
DayIndex: row.DayIndex,
GoldAmount: row.GoldAmount,
RewardGroupID: row.RewardGroupID,
RewardGroupName: strings.TrimSpace(row.RewardGroupName),
})
@ -220,7 +215,6 @@ func buildAdminDayPayloads(days []signInRewardDaySnapshot) []SignInRewardDayPayl
for _, item := range days {
payloads = append(payloads, SignInRewardDayPayload{
DayIndex: item.DayIndex,
GoldAmount: item.GoldAmount,
RewardGroupID: item.RewardGroupID,
RewardGroupName: item.RewardGroupName,
RewardItems: []SignInRewardRewardItem{},

View File

@ -4,13 +4,13 @@ import "testing"
func TestNormalizeSaveDays(t *testing.T) {
validDays := []SignInRewardDayConfigInput{
{DayIndex: 7, GoldAmount: 70},
{DayIndex: 1, GoldAmount: 10},
{DayIndex: 3, GoldAmount: 30},
{DayIndex: 2, GoldAmount: 20},
{DayIndex: 4, GoldAmount: 40},
{DayIndex: 5, GoldAmount: 50},
{DayIndex: 6, GoldAmount: 60},
{DayIndex: 7, RewardGroupID: mustFlexibleOptionalInt64(70)},
{DayIndex: 1, RewardGroupID: mustFlexibleOptionalInt64(10)},
{DayIndex: 3, RewardGroupID: mustFlexibleOptionalInt64(30)},
{DayIndex: 2, RewardGroupID: mustFlexibleOptionalInt64(20)},
{DayIndex: 4, RewardGroupID: mustFlexibleOptionalInt64(40)},
{DayIndex: 5, RewardGroupID: mustFlexibleOptionalInt64(50)},
{DayIndex: 6, RewardGroupID: mustFlexibleOptionalInt64(60)},
}
normalized, err := normalizeSaveDays(validDays, true)
@ -26,7 +26,7 @@ func TestNormalizeSaveDays(t *testing.T) {
t.Run("enabled config rejects empty reward day", func(t *testing.T) {
days := append([]SignInRewardDayConfigInput(nil), validDays...)
days[0].GoldAmount = 0
days[0].RewardGroupID = flexibleOptionalInt64{}
if _, err := normalizeSaveDays(days, true); err == nil {
t.Fatalf("normalizeSaveDays() error = nil, want error")
}
@ -34,13 +34,47 @@ func TestNormalizeSaveDays(t *testing.T) {
t.Run("disabled config allows empty reward day", func(t *testing.T) {
days := append([]SignInRewardDayConfigInput(nil), validDays...)
days[0].GoldAmount = 0
days[0].RewardGroupID = flexibleOptionalInt64{}
if _, err := normalizeSaveDays(days, false); err != nil {
t.Fatalf("normalizeSaveDays() error = %v, want nil", err)
}
})
}
func TestFlexibleOptionalInt64(t *testing.T) {
testCases := []struct {
name string
input string
wantNil bool
want int64
}{
{name: "null", input: "null", wantNil: true},
{name: "empty string", input: `""`, wantNil: true},
{name: "number", input: "123", want: 123},
{name: "string number", input: `"456"`, want: 456},
}
for _, testCase := range testCases {
var value flexibleOptionalInt64
if err := value.UnmarshalJSON([]byte(testCase.input)); err != nil {
t.Fatalf("%s: UnmarshalJSON() error = %v", testCase.name, err)
}
if testCase.wantNil {
if value.Int64Ptr() != nil {
t.Fatalf("%s: Int64Ptr() = %v, want nil", testCase.name, *value.Int64Ptr())
}
continue
}
if value.Int64Ptr() == nil || *value.Int64Ptr() != testCase.want {
t.Fatalf("%s: Int64Ptr() = %v, want %d", testCase.name, value.Int64Ptr(), testCase.want)
}
}
}
func mustFlexibleOptionalInt64(value int64) flexibleOptionalInt64 {
return flexibleOptionalInt64{value: &value}
}
func TestSignInRewardCycleHelpers(t *testing.T) {
testCases := []struct {
streak int

View File

@ -2,6 +2,9 @@ package signinreward
import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
@ -20,11 +23,9 @@ const (
signInRewardStatusSuccess = "SUCCESS"
signInRewardStatusFailed = "FAILED"
signInRewardGoldOrigin = "SIGN_IN_REWARD"
signInRewardGoldDesc = "Sign-in reward"
signInRewardPropsOrigin = "SIGN_IN_REWARD"
signInRewardDefaultTimezone = "Asia/Shanghai"
signInRewardDefaultTimezone = "Asia/Riyadh"
)
type AppError = common.AppError
@ -37,8 +38,6 @@ type signInRewardDB interface {
}
type signInRewardGateway interface {
ChangeGoldBalance(ctx context.Context, cmd integration.GoldReceiptCommand) error
ExistsGoldEvent(ctx context.Context, eventID string) (bool, error)
GrantProps(ctx context.Context, req integration.GrantPropsRequest) error
GetRewardGroupDetail(ctx context.Context, groupID int64) (integration.RewardGroupDetail, error)
}
@ -74,10 +73,9 @@ type SaveSignInRewardConfigRequest struct {
// SignInRewardDayConfigInput 是单日配置的入参。
type SignInRewardDayConfigInput struct {
DayIndex int `json:"dayIndex"`
GoldAmount int64 `json:"goldAmount"`
RewardGroupID *int64 `json:"rewardGroupId"`
RewardGroupName string `json:"rewardGroupName"`
DayIndex int `json:"dayIndex"`
RewardGroupID flexibleOptionalInt64 `json:"rewardGroupId"`
RewardGroupName string `json:"rewardGroupName"`
}
// SignInRewardConfigResponse 是后台配置页读取结果。
@ -117,17 +115,45 @@ type SignInRewardCheckInResponse struct {
ClaimDate string `json:"claimDate"`
DayIndex int `json:"dayIndex"`
StreakCount int `json:"streakCount"`
GoldAmount int64 `json:"goldAmount"`
RewardGroupID *int64 `json:"rewardGroupId,omitempty"`
RewardGroupName string `json:"rewardGroupName,omitempty"`
RewardItems []SignInRewardRewardItem `json:"rewardItems"`
Status string `json:"status"`
}
// SignInRewardRecordPageResponse 是后台签到记录分页返回体。
type SignInRewardRecordPageResponse struct {
Date string `json:"date"`
Timezone string `json:"timezone"`
SignCount int64 `json:"signCount"`
SuccessCount int64 `json:"successCount"`
Records []SignInRewardRecordView `json:"records"`
Total int64 `json:"total"`
}
// SignInRewardRecordView 是后台签到记录单条展示结构。
type SignInRewardRecordView struct {
ID int64 `json:"id"`
UserID int64 `json:"userId"`
Account string `json:"account,omitempty"`
UserAvatar string `json:"userAvatar,omitempty"`
UserNickname string `json:"userNickname,omitempty"`
CountryCode string `json:"countryCode,omitempty"`
CountryName string `json:"countryName,omitempty"`
ClaimDate string `json:"claimDate"`
DayIndex int `json:"dayIndex"`
StreakCount int `json:"streakCount"`
RewardGroupID *int64 `json:"rewardGroupId,omitempty"`
RewardGroupName string `json:"rewardGroupName,omitempty"`
Status string `json:"status"`
ErrorMessage string `json:"errorMessage,omitempty"`
CreateTime string `json:"createTime,omitempty"`
UpdateTime string `json:"updateTime,omitempty"`
}
// SignInRewardDayPayload 是单日配置对外展示结构。
type SignInRewardDayPayload struct {
DayIndex int `json:"dayIndex"`
GoldAmount int64 `json:"goldAmount"`
RewardGroupID *int64 `json:"rewardGroupId,omitempty"`
RewardGroupName string `json:"rewardGroupName,omitempty"`
RewardItems []SignInRewardRewardItem `json:"rewardItems"`
@ -162,7 +188,6 @@ type signInRewardConfigSnapshot struct {
type signInRewardDaySnapshot struct {
ID int64
DayIndex int
GoldAmount int64
RewardGroupID *int64
RewardGroupName string
}
@ -187,3 +212,41 @@ func normalizeSignInRewardTimezone(timezone string) string {
}
return timezone
}
type flexibleOptionalInt64 struct {
value *int64
}
func (v *flexibleOptionalInt64) UnmarshalJSON(data []byte) error {
raw := strings.TrimSpace(string(data))
switch raw {
case "", "null", `""`:
v.value = nil
return nil
}
if strings.HasPrefix(raw, `"`) && strings.HasSuffix(raw, `"`) {
raw = strings.Trim(raw, `"`)
raw = strings.TrimSpace(raw)
if raw == "" {
v.value = nil
return nil
}
}
parsed, err := strconv.ParseInt(raw, 10, 64)
if err != nil {
return fmt.Errorf("invalid int64 value: %w", err)
}
v.value = &parsed
return nil
}
func (v flexibleOptionalInt64) MarshalJSON() ([]byte, error) {
if v.value == nil {
return []byte("null"), nil
}
return json.Marshal(*v.value)
}
func (v flexibleOptionalInt64) Int64Ptr() *int64 {
return v.value
}

View File

@ -2,7 +2,7 @@ CREATE TABLE IF NOT EXISTS `sign_in_reward_config` (
`id` bigint NOT NULL COMMENT '主键ID',
`sys_origin` varchar(32) NOT NULL COMMENT '来源系统',
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用',
`timezone` varchar(64) NOT NULL DEFAULT 'Asia/Shanghai' COMMENT '签到时区',
`timezone` varchar(64) NOT NULL DEFAULT 'Asia/Riyadh' COMMENT '签到时区',
`create_time` datetime(3) DEFAULT NULL COMMENT '创建时间',
`update_time` datetime(3) DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),