From b86a3acd376cc583c761f9b6927c00888964a463 Mon Sep 17 00:00:00 2001
From: jinjianmingming <17610798380@163.com>
Date: Wed, 17 Jul 2024 11:37:24 +0800
Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=E5=BC=95=E5=85=A5=E5=88=B0?=
=?UTF-8?q?=E6=9C=9F=E6=97=B6=E9=97=B4=E6=A6=82=E5=BF=B5,=E5=BD=93?=
=?UTF-8?q?=E9=9D=9Edefault=E7=9A=84=E5=88=86=E7=BB=84=E6=97=B6=E5=B0=86?=
=?UTF-8?q?=E6=9C=89=E5=88=B0=E6=9C=9F=E6=97=B6=E9=97=B4=E6=A6=82=E5=BF=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
fix: 修复旧用户的vip被降级,尽量不对原先用户做变动
---
model/option.go | 3 +
model/user.go | 59 +++
web/berry/package.json | 2 +
.../src/views/User/component/EditModal.js | 371 +++++++++++-------
4 files changed, 287 insertions(+), 148 deletions(-)
diff --git a/model/option.go b/model/option.go
index bed8d4c37d..a62bd5b489 100644
--- a/model/option.go
+++ b/model/option.go
@@ -95,6 +95,9 @@ func SyncOptions(frequency int) {
for {
time.Sleep(time.Duration(frequency) * time.Second)
logger.SysLog("syncing options from database")
+ if config.IsMasterNode {
+ checkAndDowngradeUsers()
+ }
loadOptionsFromDatabase()
}
}
diff --git a/model/user.go b/model/user.go
index 924d72f940..0b06116962 100644
--- a/model/user.go
+++ b/model/user.go
@@ -10,7 +10,9 @@ import (
"github.com/songquanpeng/one-api/common/logger"
"github.com/songquanpeng/one-api/common/random"
"gorm.io/gorm"
+ "log"
"strings"
+ "time"
)
const (
@@ -47,6 +49,7 @@ type User struct {
Group string `json:"group" gorm:"type:varchar(32);default:'default'"`
AffCode string `json:"aff_code" gorm:"type:varchar(32);column:aff_code;uniqueIndex"`
InviterId int `json:"inviter_id" gorm:"type:int;column:inviter_id;index"`
+ ExpirationDate int64 `json:"expiration_date" gorm:"column:expiration_date"` // Expiration date of the user's subscription or account.
}
func GetMaxUserId() int {
@@ -210,6 +213,25 @@ func (user *User) ValidateAndFill() (err error) {
if !okay || user.Status != UserStatusEnabled {
return errors.New("用户名或密码错误,或用户已被封禁")
}
+ // 校验用户是不是非default,如果是非default,判断到期时间如果过期了降级为default
+ if user.ExpirationDate > 0 {
+ // 将时间戳转换为 time.Time 类型
+ expirationTime := time.Unix(user.ExpirationDate, 0)
+ // 获取当前时间
+ currentTime := time.Now()
+
+ // 比较当前时间和到期时间
+ if expirationTime.Before(currentTime) {
+ // 降级为 default
+ user.Group = "default"
+ err := DB.Model(user).Updates(user).Error
+ if err != nil {
+ fmt.Printf("用户: %s, 降级为 default 时发生错误: %v\n", user.Username, err)
+ return err
+ }
+ fmt.Printf("用户: %s, 特权组过期降为 default\n", user.Username)
+ }
+ }
return nil
}
@@ -435,3 +457,40 @@ func GetUsernameById(id int) (username string) {
DB.Model(&User{}).Where("id = ?", id).Select("username").Find(&username)
return username
}
+
+func checkAndDowngradeUsers() {
+ var users []User
+
+ // 查询所有 Group 不为 "default" 的用户
+ // 构建查询条件
+ query := DB.Where("`Group` <> ?", "default"). // Group 不等于 "default"
+ Where("`username` <> ?", "root"). // username 不等于 "root"
+ Where("`expiration_date` IS NOT NULL"). // expiration_date 不为空
+ Where("`expiration_date` != ?", -1) // expiration_date 不等于 -1
+
+ // 执行查询并处理错误
+ if err := query.Find(&users).Error; err != nil {
+ log.Printf("查询用户失败: %v", err)
+ return
+ }
+
+ currentTime := time.Now()
+
+ for _, user := range users {
+ if user.Group != "default" {
+ // 将时间戳转换为 time.Time 类型
+ expirationTime := time.Unix(user.ExpirationDate, 0)
+
+ // 比较当前时间和到期时间
+ if expirationTime.Before(currentTime) {
+ // 降级为 default
+ user.Group = "default"
+ if err := DB.Model(&user).Updates(user).Error; err != nil {
+ log.Printf("更新用户 %s 失败: %v", user.Username, err)
+ } else {
+ fmt.Printf("用户: %s, 特权组过期降为 default\n", user.Username)
+ }
+ }
+ }
+ }
+}
diff --git a/web/berry/package.json b/web/berry/package.json
index f8265ef7bb..f82aa0897c 100644
--- a/web/berry/package.json
+++ b/web/berry/package.json
@@ -17,6 +17,7 @@
"@tabler/icons-react": "^2.44.0",
"apexcharts": "3.35.3",
"axios": "^0.27.2",
+ "date-fns": "^3.6.0",
"dayjs": "^1.11.10",
"formik": "^2.2.9",
"framer-motion": "^6.3.16",
@@ -27,6 +28,7 @@
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-apexcharts": "1.4.0",
+ "react-datepicker": "^7.3.0",
"react-device-detect": "^2.2.2",
"react-dom": "^18.2.0",
"react-perfect-scrollbar": "^1.5.8",
diff --git a/web/berry/src/views/User/component/EditModal.js b/web/berry/src/views/User/component/EditModal.js
index f6b533e2d1..17ee1eaacd 100644
--- a/web/berry/src/views/User/component/EditModal.js
+++ b/web/berry/src/views/User/component/EditModal.js
@@ -2,7 +2,9 @@ import PropTypes from 'prop-types';
import * as Yup from 'yup';
import { Formik } from 'formik';
import { useTheme } from '@mui/material/styles';
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useCallback } from 'react';
+import { format } from 'date-fns';
+
import {
Dialog,
DialogTitle,
@@ -17,7 +19,11 @@ import {
Select,
MenuItem,
IconButton,
- FormHelperText
+ FormHelperText,
+ TextField,
+ Typography,
+ Switch,
+ FormControlLabel
} from '@mui/material';
import Visibility from '@mui/icons-material/Visibility';
@@ -44,6 +50,17 @@ const validationSchema = Yup.object().shape({
is: false,
then: Yup.number().min(0, '额度 不能小于 0'),
otherwise: Yup.number()
+ }),
+ expiration_date: Yup.mixed().when('group', {
+ is: (group) => group !== 'default',
+ then: Yup.mixed().test(
+ 'expiration_date-required',
+ '到期时间 不能为空',
+ function (value) {
+ const { expiration_date } = this.parent;
+ return expiration_date === -1 || !!expiration_date;
+ }
+ ),
})
});
@@ -53,7 +70,8 @@ const originInputs = {
display_name: '',
password: '',
group: 'default',
- quota: 0
+ quota: 0,
+ expiration_date: null
};
const EditModal = ({ open, userId, onCancel, onOk }) => {
@@ -65,6 +83,12 @@ const EditModal = ({ open, userId, onCancel, onOk }) => {
const submit = async (values, { setErrors, setStatus, setSubmitting }) => {
setSubmitting(true);
+ // 将到期时间转换为 Unix 时间戳
+ if (values.expiration_date && values.expiration_date !== -1) {
+ const date = new Date(values.expiration_date);
+ values.expiration_date = Math.floor(date.getTime() / 1000); // 转换为秒级的 Unix 时间戳
+ }
+
let res;
if (values.is_edit) {
res = await API.put(`/api/user/`, { ...values, id: parseInt(userId) });
@@ -95,16 +119,23 @@ const EditModal = ({ open, userId, onCancel, onOk }) => {
event.preventDefault();
};
- const loadUser = async () => {
+ const loadUser = useCallback(async () => {
let res = await API.get(`/api/user/${userId}`);
const { success, message, data } = res.data;
if (success) {
data.is_edit = true;
+
+ // 将 Unix 时间戳转换为日期字符串
+ if (data.expiration_date && data.expiration_date !== -1) {
+ const date = new Date(data.expiration_date * 1000); // 转换为毫秒级的时间戳
+ data.expiration_date = format(date, 'yyyy-MM-dd\'T\'HH:mm:ss'); // 格式化为 datetime-local 格式
+ }
+
setInputs(data);
} else {
showError(message);
}
- };
+ }, [userId]);
const fetchGroups = async () => {
try {
@@ -122,159 +153,203 @@ const EditModal = ({ open, userId, onCancel, onOk }) => {
} else {
setInputs(originInputs);
}
- }, [userId]);
+ }, [userId, loadUser]);
return (
-
);
};
@@ -285,4 +360,4 @@ EditModal.propTypes = {
userId: PropTypes.number,
onCancel: PropTypes.func,
onOk: PropTypes.func
-};
+};
\ No newline at end of file
From 1aba1e4ac394784da8d6b721120cb68a0b7cf9ef Mon Sep 17 00:00:00 2001
From: jinjianmingming <17610798380@163.com>
Date: Thu, 18 Jul 2024 10:39:21 +0800
Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E7=94=A8?=
=?UTF-8?q?=E6=88=B7=E7=AD=89=E7=BA=A7=E5=88=B7=E6=96=B0=E6=B5=8B=E8=AF=95?=
=?UTF-8?q?,=E9=BB=98=E8=AE=A424h=E6=89=AB=E6=8F=8F=E4=B8=80=E6=AC=A1,?=
=?UTF-8?q?=E5=8F=AF=E4=BB=A5=E9=80=9A=E8=BF=87TIMER=5FFREQUENCY=E8=AE=BE?=
=?UTF-8?q?=E7=BD=AE=E6=97=B6=E9=97=B4=E5=8D=95=E4=BD=8D=E6=98=AF=E5=B0=8F?=
=?UTF-8?q?=E6=97=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
common/config/config.go | 1 +
main.go | 3 +++
model/option.go | 11 +++++++---
model/user.go | 47 +++++++++++++----------------------------
4 files changed, 27 insertions(+), 35 deletions(-)
diff --git a/common/config/config.go b/common/config/config.go
index 11da0b967d..59db1086d3 100644
--- a/common/config/config.go
+++ b/common/config/config.go
@@ -99,6 +99,7 @@ var requestInterval, _ = strconv.Atoi(os.Getenv("POLLING_INTERVAL"))
var RequestInterval = time.Duration(requestInterval) * time.Second
var SyncFrequency = env.Int("SYNC_FREQUENCY", 10*60) // unit is second
+var TimerFrequency = env.Int("TIMER_FREQUENCY", 24) // unit is hour
var BatchUpdateEnabled = false
var BatchUpdateInterval = env.Int("BATCH_UPDATE_INTERVAL", 5)
diff --git a/main.go b/main.go
index 67a3cd95a5..3c304abde5 100644
--- a/main.go
+++ b/main.go
@@ -73,6 +73,8 @@ func main() {
go model.SyncOptions(config.SyncFrequency)
go model.SyncChannelCache(config.SyncFrequency)
}
+ go model.ScheduleCheckAndDowngrade(config.TimerFrequency)
+
if os.Getenv("CHANNEL_TEST_FREQUENCY") != "" {
frequency, err := strconv.Atoi(os.Getenv("CHANNEL_TEST_FREQUENCY"))
if err != nil {
@@ -112,4 +114,5 @@ func main() {
if err != nil {
logger.FatalLog("failed to start HTTP server: " + err.Error())
}
+
}
diff --git a/model/option.go b/model/option.go
index a62bd5b489..cee6d2140c 100644
--- a/model/option.go
+++ b/model/option.go
@@ -78,6 +78,14 @@ func InitOptionMap() {
loadOptionsFromDatabase()
}
+func ScheduleCheckAndDowngrade(intervalHours int) {
+ for {
+ logger.SysLog("Check user group levels.")
+ checkAndDowngradeUsers()
+ time.Sleep(time.Duration(intervalHours) * time.Hour)
+ }
+}
+
func loadOptionsFromDatabase() {
options, _ := AllOption()
for _, option := range options {
@@ -95,9 +103,6 @@ func SyncOptions(frequency int) {
for {
time.Sleep(time.Duration(frequency) * time.Second)
logger.SysLog("syncing options from database")
- if config.IsMasterNode {
- checkAndDowngradeUsers()
- }
loadOptionsFromDatabase()
}
}
diff --git a/model/user.go b/model/user.go
index 0b06116962..517fa03ac2 100644
--- a/model/user.go
+++ b/model/user.go
@@ -459,38 +459,21 @@ func GetUsernameById(id int) (username string) {
}
func checkAndDowngradeUsers() {
- var users []User
-
- // 查询所有 Group 不为 "default" 的用户
- // 构建查询条件
- query := DB.Where("`Group` <> ?", "default"). // Group 不等于 "default"
- Where("`username` <> ?", "root"). // username 不等于 "root"
- Where("`expiration_date` IS NOT NULL"). // expiration_date 不为空
- Where("`expiration_date` != ?", -1) // expiration_date 不等于 -1
-
- // 执行查询并处理错误
- if err := query.Find(&users).Error; err != nil {
- log.Printf("查询用户失败: %v", err)
+ // 获取当前时间的 Unix 时间戳
+ currentTime := time.Now().Unix()
+
+ // 构建更新条件并执行更新
+ result := DB.Model(&User{}).
+ Where("`Group` <> ?", "default").
+ Where("`username` <> ?", "root").
+ Where("`expiration_date` IS NOT NULL").
+ Where("`expiration_date` != ?", -1).
+ Where("`expiration_date` < ?", currentTime).
+ Update("Group", "default")
+
+ // 处理错误
+ if result.Error != nil {
+ log.Printf("批量更新用户分组失败: %v", result.Error)
return
}
-
- currentTime := time.Now()
-
- for _, user := range users {
- if user.Group != "default" {
- // 将时间戳转换为 time.Time 类型
- expirationTime := time.Unix(user.ExpirationDate, 0)
-
- // 比较当前时间和到期时间
- if expirationTime.Before(currentTime) {
- // 降级为 default
- user.Group = "default"
- if err := DB.Model(&user).Updates(user).Error; err != nil {
- log.Printf("更新用户 %s 失败: %v", user.Username, err)
- } else {
- fmt.Printf("用户: %s, 特权组过期降为 default\n", user.Username)
- }
- }
- }
- }
}
From 66d31102d3d46e14dcb9b2d3c0ca0ca279eadde0 Mon Sep 17 00:00:00 2001
From: jinjianmingming <17610798380@163.com>
Date: Thu, 18 Jul 2024 22:58:04 +0800
Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=E8=B0=83=E6=95=B4=E4=B8=BA?=
=?UTF-8?q?=E5=AE=9A=E6=97=B6=E6=AF=8F=E6=99=9A00:00=20feat:=20=E6=B7=BB?=
=?UTF-8?q?=E5=8A=A0Redis=E6=9B=B4=E6=96=B0=E7=BC=93=E5=AD=98=E6=93=8D?=
=?UTF-8?q?=E4=BD=9C=20fate:=20expiration=5Fdate=E8=AE=BE=E7=BD=AE?=
=?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=80=BC0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
go.mod | 3 ++
go.sum | 22 ++++++++++
main.go | 2 +-
model/option.go | 19 +++++---
model/user.go | 44 ++++++++++++-------
.../src/views/User/component/EditModal.js | 6 +--
6 files changed, 72 insertions(+), 24 deletions(-)
diff --git a/go.mod b/go.mod
index ada53bc33c..d2d31381b1 100644
--- a/go.mod
+++ b/go.mod
@@ -55,6 +55,7 @@ require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
+ github.com/go-co-op/gocron v1.37.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
@@ -87,6 +88,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/smarty/assertions v1.15.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
@@ -96,6 +98,7 @@ require (
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
+ go.uber.org/atomic v1.9.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
diff --git a/go.sum b/go.sum
index 53db8df289..e414425f1f 100644
--- a/go.sum
+++ b/go.sum
@@ -31,6 +31,7 @@ github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
@@ -67,6 +68,8 @@ github.com/gin-contrib/static v1.1.2 h1:c3kT4bFkUJn2aoRU3s6XnMjJT8J6nNWJkR0Nglqm
github.com/gin-contrib/static v1.1.2/go.mod h1:Fw90ozjHCmZBWbgrsqrDvO28YbhKEKzKp8GixhR4yLw=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
+github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
+github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -116,6 +119,7 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
@@ -156,7 +160,12 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
@@ -177,6 +186,7 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkoukk/tiktoken-go v0.1.7 h1:qOBHXX4PHtvIvmOtyg1EeKlwFRiMKAcoMp4Q+bLQDmw=
@@ -184,7 +194,12 @@ github.com/pkoukk/tiktoken-go v0.1.7/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYde
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
+github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
+github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
+github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
@@ -198,6 +213,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
@@ -217,6 +233,8 @@ go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGX
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
@@ -266,6 +284,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.187.0 h1:Mxs7VATVC2v7CY+7Xwm4ndkX71hpElcvx0D1Ji/p1eo=
google.golang.org/api v0.187.0/go.mod h1:KIHlTc4x7N7gKKuVsdmfBXN13yEEWXWFURWY6SBp2gk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@@ -296,7 +315,10 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/main.go b/main.go
index 3c304abde5..41e7fdbf77 100644
--- a/main.go
+++ b/main.go
@@ -73,7 +73,7 @@ func main() {
go model.SyncOptions(config.SyncFrequency)
go model.SyncChannelCache(config.SyncFrequency)
}
- go model.ScheduleCheckAndDowngrade(config.TimerFrequency)
+ go model.ScheduleCheckAndDowngrade()
if os.Getenv("CHANNEL_TEST_FREQUENCY") != "" {
frequency, err := strconv.Atoi(os.Getenv("CHANNEL_TEST_FREQUENCY"))
diff --git a/model/option.go b/model/option.go
index cee6d2140c..5310725f57 100644
--- a/model/option.go
+++ b/model/option.go
@@ -1,9 +1,11 @@
package model
import (
+ "github.com/go-co-op/gocron"
"github.com/songquanpeng/one-api/common/config"
"github.com/songquanpeng/one-api/common/logger"
billingratio "github.com/songquanpeng/one-api/relay/billing/ratio"
+ "log"
"strconv"
"strings"
"time"
@@ -78,12 +80,19 @@ func InitOptionMap() {
loadOptionsFromDatabase()
}
-func ScheduleCheckAndDowngrade(intervalHours int) {
- for {
- logger.SysLog("Check user group levels.")
- checkAndDowngradeUsers()
- time.Sleep(time.Duration(intervalHours) * time.Hour)
+func ScheduleCheckAndDowngrade() {
+ s := gocron.NewScheduler(time.UTC)
+
+ // 设置每天0点执行
+ _, err := s.Every(1).Day().At("00:00").Do(checkAndDowngradeUsers)
+
+ if err != nil {
+ log.Fatalf("创建调度任务失败: %v", err)
}
+
+ // 开始调度
+ s.StartBlocking()
+ log.Printf("开始调度")
}
func loadOptionsFromDatabase() {
diff --git a/model/user.go b/model/user.go
index 517fa03ac2..cb1d084a9f 100644
--- a/model/user.go
+++ b/model/user.go
@@ -49,7 +49,7 @@ type User struct {
Group string `json:"group" gorm:"type:varchar(32);default:'default'"`
AffCode string `json:"aff_code" gorm:"type:varchar(32);column:aff_code;uniqueIndex"`
InviterId int `json:"inviter_id" gorm:"type:int;column:inviter_id;index"`
- ExpirationDate int64 `json:"expiration_date" gorm:"column:expiration_date"` // Expiration date of the user's subscription or account.
+ ExpirationDate int64 `json:"expiration_date" gorm:"bigint;default:0;column:expiration_date"` // Expiration date of the user's subscription or account.
}
func GetMaxUserId() int {
@@ -214,7 +214,7 @@ func (user *User) ValidateAndFill() (err error) {
return errors.New("用户名或密码错误,或用户已被封禁")
}
// 校验用户是不是非default,如果是非default,判断到期时间如果过期了降级为default
- if user.ExpirationDate > 0 {
+ if !(user.ExpirationDate > 0 && user.Username == "root") {
// 将时间戳转换为 time.Time 类型
expirationTime := time.Unix(user.ExpirationDate, 0)
// 获取当前时间
@@ -459,21 +459,35 @@ func GetUsernameById(id int) (username string) {
}
func checkAndDowngradeUsers() {
- // 获取当前时间的 Unix 时间戳
- currentTime := time.Now().Unix()
-
- // 构建更新条件并执行更新
- result := DB.Model(&User{}).
- Where("`Group` <> ?", "default").
- Where("`username` <> ?", "root").
- Where("`expiration_date` IS NOT NULL").
- Where("`expiration_date` != ?", -1).
- Where("`expiration_date` < ?", currentTime).
- Update("Group", "default")
+ // 获取当前时间的前一天的日期部分
+ //yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
+
+ // 获取昨天的时间戳
+ yesterdayTimestamp := time.Now().AddDate(0, 0, -1).Unix()
+ // Construct the query
+ query := DB.Model(&User{}).
+ Where("`Group` != ?", "default"). // Use single quotes for string literal
+ Where("`username` != ?", "root").
+ Where("`expiration_date` > 0").
+ Where("`expiration_date` <= ?", yesterdayTimestamp)
+ var userList []int
+ query.Select("id").Find(&userList)
+ // 降级更新操作
+ query.Update("Group", "default")
// 处理错误
- if result.Error != nil {
- log.Printf("批量更新用户分组失败: %v", result.Error)
+ if query.Error != nil {
+ log.Printf("批量更新用户分组失败: %v", query.Error)
return
}
+ // 删除已过期用户的Redis缓存
+ if common.RedisEnabled {
+ for _, userId := range userList {
+ err := common.RedisSet(fmt.Sprintf("user_group:%d", userId), "default", time.Duration(UserId2GroupCacheSeconds)*time.Second)
+ if err != nil {
+ log.Printf("更新用户: %d,权益缓存失败, Error: %v", userId, query.Error)
+ }
+ }
+ }
+
}
diff --git a/web/berry/src/views/User/component/EditModal.js b/web/berry/src/views/User/component/EditModal.js
index 17ee1eaacd..16dcc2f0cf 100644
--- a/web/berry/src/views/User/component/EditModal.js
+++ b/web/berry/src/views/User/component/EditModal.js
@@ -128,7 +128,7 @@ const EditModal = ({ open, userId, onCancel, onOk }) => {
// 将 Unix 时间戳转换为日期字符串
if (data.expiration_date && data.expiration_date !== -1) {
const date = new Date(data.expiration_date * 1000); // 转换为毫秒级的时间戳
- data.expiration_date = format(date, 'yyyy-MM-dd\'T\'HH:mm:ss'); // 格式化为 datetime-local 格式
+ data.expiration_date = format(date, 'yyyy-MM-dd'); // 格式化为 date 格式
}
setInputs(data);
@@ -306,7 +306,7 @@ const EditModal = ({ open, userId, onCancel, onOk }) => {
{
shrink: true
}}
inputProps={{
- max: '9999-12-31T23:59:59' // 设置最大日期和时间
+ max: '9999-12-31' // 设置最大日期
}}
aria-describedby="helper-text-channel-expiration_date-label"
disabled={values.expiration_date === -1}
From ade00eed1c0b4cafa90b291aaa9002fa2b828e7d Mon Sep 17 00:00:00 2001
From: jinjianmingming <17610798380@163.com>
Date: Fri, 19 Jul 2024 10:00:58 +0800
Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DUpdate=E6=9C=AA?=
=?UTF-8?q?=E8=83=BD=E9=A2=84=E6=9C=9F=E6=89=A7=E8=A1=8C=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
model/user.go | 39 +++++++++++++++++++++++++--------------
1 file changed, 25 insertions(+), 14 deletions(-)
diff --git a/model/user.go b/model/user.go
index cb1d084a9f..3c9a636462 100644
--- a/model/user.go
+++ b/model/user.go
@@ -459,35 +459,46 @@ func GetUsernameById(id int) (username string) {
}
func checkAndDowngradeUsers() {
- // 获取当前时间的前一天的日期部分
- //yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
-
// 获取昨天的时间戳
yesterdayTimestamp := time.Now().AddDate(0, 0, -1).Unix()
- // Construct the query
+
+ // 获取需要降级的用户ID列表
+ var userList []int
query := DB.Model(&User{}).
- Where("`Group` != ?", "default"). // Use single quotes for string literal
+ Where("`Group` != ?", "default").
Where("`username` != ?", "root").
Where("`expiration_date` > 0").
- Where("`expiration_date` <= ?", yesterdayTimestamp)
- var userList []int
- query.Select("id").Find(&userList)
- // 降级更新操作
- query.Update("Group", "default")
+ Where("`expiration_date` <= ?", yesterdayTimestamp).
+ Select("id").
+ Find(&userList)
- // 处理错误
+ // 处理查询错误
if query.Error != nil {
- log.Printf("批量更新用户分组失败: %v", query.Error)
+ log.Printf("查询用户列表失败: %v", query.Error)
+ return
+ }
+
+ // 如果没有用户需要降级,直接返回
+ if len(userList) == 0 {
return
}
+
+ // 批量降级用户
+ updateQuery := DB.Model(&User{}).Where("id IN ?", userList).Update("Group", "default")
+
+ // 处理更新错误
+ if updateQuery.Error != nil {
+ log.Printf("批量更新用户分组失败: %v", updateQuery.Error)
+ return
+ }
+
// 删除已过期用户的Redis缓存
if common.RedisEnabled {
for _, userId := range userList {
err := common.RedisSet(fmt.Sprintf("user_group:%d", userId), "default", time.Duration(UserId2GroupCacheSeconds)*time.Second)
if err != nil {
- log.Printf("更新用户: %d,权益缓存失败, Error: %v", userId, query.Error)
+ log.Printf("更新用户: %d, 权益缓存失败, Error: %v", userId, err)
}
}
}
-
}