Files
bl/common/utils/help.go
xinian 99af9b6e01
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
feat: 增加采集限购按日、周、月重置功能
2026-03-27 13:17:42 +08:00

211 lines
6.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package utils
import (
"errors"
"fmt"
"math/rand/v2"
"strings"
"time"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/grand"
)
// IsToday 判断给定时间是否是今天
func IsToday(t1 *gtime.Time) bool {
if t1 == nil {
return false
}
t := t1.Time
// 获取当前时间
now := time.Now()
// 比较年、月、日是否相同
return t.Year() == now.Year() &&
t.Month() == now.Month() &&
t.Day() == now.Day()
}
func IsWEEK(t1 *gtime.Time) bool {
if t1 == nil {
return false
}
t := t1.Time
// 获取当前时间
now := time.Now()
_, nweek := now.ISOWeek()
_, tweek := now.ISOWeek()
// 比较年、月、日是否相同
return t.Year() == now.Year() &&
tweek == nweek
}
func IsMon(t1 *gtime.Time) bool {
if t1 == nil {
return false
}
t := t1.Time
// 获取当前时间
now := time.Now()
nweek := now.Month()
tweek := now.Month()
// 比较年、月、日是否相同
return t.Year() == now.Year() &&
tweek == nweek
}
func FindWithIndex[T any](slice []T, predicate func(item T) bool) (int, *T, bool) {
for i := range slice {
if predicate(slice[i]) {
return i, &slice[i], true
}
}
return -1, nil, false
}
func LastFourElements[T any](s []T, n1 int) []T {
n := len(s)
if n <= n1 {
// 切片长度小于等于4时返回整个切片
return s
}
// 切片长度大于4时返回最后4个元素从n-4索引到末尾
return s[n-n1:]
}
func RemoveLast(s string) string {
if s == "" {
return ""
}
runes := []rune(s)
return string(runes[:len(runes)-1])
}
// RandomByWeight 根据整数权重随机选择元素(优化后的泛型版本)
// 入参:
// - elements: 待随机的元素集合(非空)
// - weights: 对应元素的权重非负整数长度需与elements一致长度不匹配/总权重为0时降级为等概率随机
//
// 返回:
// - 随机选中的元素
// - 错误仅当elements为空/权重值为负时返回)
func RandomByWeight[Element any, Weight integer](elements []Element, weights []Weight) (Element, error) {
// 定义泛型零值,用于错误返回
var zero Element
// 1. 核心合法性校验:元素集合不能为空
if len(elements) == 0 {
return zero, errors.New("elements set is empty (cannot random from empty slice)")
}
// 2. 权重数组合法性校验:长度不匹配/为空时,降级为等概率随机(兼容原逻辑)
elemLen := len(elements)
if len(weights) == 0 || len(weights) != elemLen {
return elements[rand.IntN(elemLen)], nil
}
// 3. 校验权重非负并计算总权重统一转为int64避免溢出
var totalWeight int64
// 预转换权重为int64避免重复类型转换
intWeights := make([]int64, elemLen)
for i, w := range weights {
intW := int64(w)
if intW < 0 {
return zero, fmt.Errorf("invalid negative weight at index %d (value: %d)", i, w)
}
intWeights[i] = intW
totalWeight += intW
}
// 4. 总权重为0时降级为等概率随机
if totalWeight == 0 {
return elements[rand.IntN(elemLen)], nil
}
// 5. 计算前缀和权重随机核心逻辑复用预转换的int64权重
prefixWeights := make([]int64, elemLen)
prefixWeights[0] = intWeights[0]
for i := 1; i < elemLen; i++ {
prefixWeights[i] = prefixWeights[i-1] + intWeights[i]
}
// 6. 生成随机数并匹配前缀和用int64避免溢出
randomValue := grand.Intn(int(totalWeight))
for i, sum := range prefixWeights {
if randomValue < int(sum) {
return elements[i], nil
}
}
// 极端兜底理论上不会走到这里randomValue < totalWeight返回第一个元素保证不panic
return elements[0], nil
}
// integer 自定义泛型约束:匹配所有整数类型(扩展原~int的限制
// 包含int/int8/int16/int32/int64/uint/uint8/uint16/uint32/uint64/uintptr
type integer interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
// ************************** 函数2封装函数兼容string型概率泛型**************************
// randomByProbs 兼容原有入参格式:接收任意类型元素数组 + string型概率数组
// 内部完成 string[] -> int[] 的转换,然后调用核心函数 randomByIntProbs
func RandomByProbs[T any](natureSet []T, probs []string) (T, error) {
//var zeroT T
// 1. 若string概率数组为空直接调用核心函数核心函数会处理降级逻辑
if len(probs) == 0 {
return RandomByWeight(natureSet, []int{})
}
// 2. string[] 转换为 int[](使用 gconv.Int 完成转换)
probInts := make([]int, len(probs))
for i, pStr := range probs {
// gconv.Int 灵活转换失败返回0
probInts[i] = gconv.Int(pStr)
}
// 3. 调用核心函数,复用概率计算逻辑
return RandomByWeight(natureSet, probInts)
}
// IsCurrentTimeInRange 判断当前时间是否在 startStr 和 endStr 表示的时间区间内格式HH:MM
// 返回值true=在区间内false=不在区间内error=时间解析失败
func IsCurrentTimeInRange(startStr, endStr string) (bool, error) {
// 1. 解析开始和结束时间字符串为 time.Time 对象(日期用当前日期)
now := time.Now()
location := now.Location() // 使用当前时区,避免时区偏差
// 解析开始时间HH:MM
startTime, err := time.ParseInLocation("15:04", startStr, location)
if err != nil {
return false, fmt.Errorf("解析开始时间 %s 失败:%w", startStr, err)
}
// 解析结束时间HH:MM
endTime, err := time.ParseInLocation("15:04", endStr, location)
if err != nil {
return false, fmt.Errorf("解析结束时间 %s 失败:%w", endStr, err)
}
// 2. 把开始/结束时间的日期替换为当前日期(只保留时分)
startToday := time.Date(now.Year(), now.Month(), now.Day(),
startTime.Hour(), startTime.Minute(), 0, 0, location)
endToday := time.Date(now.Year(), now.Month(), now.Day(),
endTime.Hour(), endTime.Minute(), 0, 0, location)
// 3. 比较当前时间是否在 [startToday, endToday] 区间内
return now.After(startToday) && now.Before(endToday), nil
}
// Format 把 args 按顺序填入 {0},{1},{2}...
func Format(s string, args ...interface{}) string {
for i, arg := range args {
placeholder := "{" + string(rune('0'+i)) + "}"
s = strings.ReplaceAll(s, placeholder, gconv.String(arg))
}
return s
}