Initial commit: web_hghl_server
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,292 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
//数组求交集,差集,并集
|
||||
/*求并集**/
|
||||
func Union(slice1, slice2 []string) []string {
|
||||
m := make(map[string]int)
|
||||
for _, v := range slice1 {
|
||||
m[v]++
|
||||
}
|
||||
|
||||
for _, v := range slice2 {
|
||||
times, _ := m[v]
|
||||
if times == 0 {
|
||||
slice1 = append(slice1, v)
|
||||
}
|
||||
}
|
||||
return slice1
|
||||
}
|
||||
|
||||
// 求交集
|
||||
func Intersect(slice1, slice2 []string) []string {
|
||||
m := make(map[string]int)
|
||||
nn := make([]string, 0)
|
||||
for _, v := range slice1 {
|
||||
m[v]++
|
||||
}
|
||||
|
||||
for _, v := range slice2 {
|
||||
times, _ := m[v]
|
||||
if times == 1 {
|
||||
nn = append(nn, v)
|
||||
}
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// 求交集
|
||||
func IntersectInt64(slice1, slice2 []int64) []int64 {
|
||||
m := make(map[int64]int)
|
||||
nn := make([]int64, 0)
|
||||
for _, v := range slice1 {
|
||||
m[v]++
|
||||
}
|
||||
|
||||
for _, v := range slice2 {
|
||||
times, _ := m[v]
|
||||
if times == 1 {
|
||||
nn = append(nn, v)
|
||||
}
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// 求差集 slice1-并集
|
||||
func Difference(slice1, slice2 []string) []string {
|
||||
m := make(map[string]int)
|
||||
nn := make([]string, 0)
|
||||
inter := Intersect(slice1, slice2)
|
||||
for _, v := range inter {
|
||||
m[v]++
|
||||
}
|
||||
|
||||
for _, value := range slice1 {
|
||||
times, _ := m[value]
|
||||
if times == 0 {
|
||||
nn = append(nn, value)
|
||||
}
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// 求差集 slice1-并集
|
||||
func DifferenceInt64(slice1, slice2 []int64) []int64 {
|
||||
m := make(map[int64]int)
|
||||
nn := make([]int64, 0)
|
||||
inter := IntersectInt64(slice1, slice2)
|
||||
for _, v := range inter {
|
||||
m[v]++
|
||||
}
|
||||
|
||||
for _, value := range slice1 {
|
||||
times, _ := m[value]
|
||||
if times == 0 {
|
||||
nn = append(nn, value)
|
||||
}
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// 去重
|
||||
func RemoveRepeat(slc []int64) []int64 {
|
||||
result := []int64{} // 存放结果
|
||||
for i := range slc {
|
||||
flag := true
|
||||
for j := range result {
|
||||
if slc[i] == result[j] {
|
||||
flag = false // 存在重复元素,标识为false
|
||||
break
|
||||
}
|
||||
}
|
||||
if flag { // 标识为false,不添加进结果
|
||||
result = append(result, slc[i])
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func IncludeInt32(slc []int32, n int32) bool {
|
||||
for _, v := range slc {
|
||||
if v == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IncludeInt64(slc []int64, n int64) bool {
|
||||
for _, v := range slc {
|
||||
if v == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func RemoveInt64(slc []int64, n int64) []int64 {
|
||||
for i := 0; i < len(slc); {
|
||||
if slc[i] == n {
|
||||
slc = append(slc[:i], slc[i+1:]...)
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
return slc
|
||||
}
|
||||
|
||||
func RemoveString(slc []string, n string) []string {
|
||||
for i := 0; i < len(slc); {
|
||||
if slc[i] == n {
|
||||
slc = append(slc[:i], slc[i+1:]...)
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
return slc
|
||||
}
|
||||
|
||||
// RemoveRepeatString 去重string
|
||||
func RemoveRepeatString(slc []string) []string {
|
||||
result := []string{} // 存放结果
|
||||
for i := range slc {
|
||||
flag := true
|
||||
for j := range result {
|
||||
if slc[i] == result[j] {
|
||||
flag = false // 存在重复元素,标识为false
|
||||
break
|
||||
}
|
||||
}
|
||||
if flag { // 标识为false,不添加进结果
|
||||
result = append(result, slc[i])
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func IncludeString(slc []string, s string) bool {
|
||||
for _, v := range slc {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func EqualInt64s(slice1, slice2 []int64) bool {
|
||||
if len(slice1) != len(slice2) {
|
||||
return false
|
||||
}
|
||||
for _, v1 := range slice1 {
|
||||
find := false
|
||||
for _, v2 := range slice2 {
|
||||
if v1 == v2 {
|
||||
find = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !find {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func EqualInt64WithOrder(slice1, slice2 []int64) bool {
|
||||
if len(slice1) != len(slice2) {
|
||||
return false
|
||||
}
|
||||
for k, v1 := range slice1 {
|
||||
if slice2[k] != v1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func EqualString(slice1, slice2 []string) bool {
|
||||
if len(slice1) != len(slice2) {
|
||||
return false
|
||||
}
|
||||
for _, v1 := range slice1 {
|
||||
find := false
|
||||
for _, v2 := range slice2 {
|
||||
if v1 == v2 {
|
||||
find = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if find == false {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 切片乱序
|
||||
func ShuffleSlice(slice interface{}) {
|
||||
rv := reflect.ValueOf(slice)
|
||||
if rv.Type().Kind() != reflect.Slice {
|
||||
return
|
||||
}
|
||||
|
||||
length := rv.Len()
|
||||
if length < 2 {
|
||||
return
|
||||
}
|
||||
swap := reflect.Swapper(slice)
|
||||
//rand.Seed(time.Now().Unix())
|
||||
for i := length - 1; i >= 0; i-- {
|
||||
j := rand.Intn(length)
|
||||
swap(i, j)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func RemoveOneInt64(slc []int64, n int64) []int64 {
|
||||
for i := 0; i < len(slc); {
|
||||
if slc[i] == n {
|
||||
slc = append(slc[:i], slc[i+1:]...)
|
||||
break
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
return slc
|
||||
}
|
||||
|
||||
// 求最大值
|
||||
func SliceMaxInt64(slc []int64) int64 {
|
||||
maxVal := slc[0]
|
||||
for i := 1; i < len(slc); i++ {
|
||||
//从第二个 元素开始循环比较,如果发现有更大的,则交换
|
||||
if maxVal < slc[i] {
|
||||
maxVal = slc[i]
|
||||
}
|
||||
}
|
||||
return maxVal
|
||||
}
|
||||
|
||||
// 求最大值
|
||||
func SliceMinInt64(slc []int64) int64 {
|
||||
minVal := slc[0]
|
||||
for i := 1; i < len(slc); i++ {
|
||||
//从第二个 元素开始循环比较,如果发现有更小的,则交换
|
||||
if minVal > slc[i] {
|
||||
minVal = slc[i]
|
||||
}
|
||||
}
|
||||
return minVal
|
||||
}
|
||||
|
||||
func ReverseAny(s interface{}) {
|
||||
n := reflect.ValueOf(s).Len()
|
||||
swap := reflect.Swapper(s)
|
||||
for i, j := 0, n-1; i < j; i, j = i+1, j-1 {
|
||||
swap(i, j)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
type ID = primitive.ObjectID
|
||||
|
||||
// ToJsonM 将struct转换为 Json Map
|
||||
func ToJsonM(obj interface{}) (map[string]interface{}, error) {
|
||||
j, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := make(map[string]interface{})
|
||||
if err := json.Unmarshal(j, &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, err
|
||||
}
|
||||
|
||||
// ToBsonM 将struct转换为 Bson Map
|
||||
func ToBsonM(s interface{}) (bson.M, error) {
|
||||
data, err := bson.Marshal(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := make(bson.M)
|
||||
if err := bson.Unmarshal(data, &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func MapToJsonString(m map[string]string) (string, error) {
|
||||
if data, err := json.Marshal(m); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return string(data), nil
|
||||
}
|
||||
}
|
||||
|
||||
func StructToJsonString(s interface{}) (string, error) {
|
||||
j, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
m := make(map[string]interface{})
|
||||
if err := json.Unmarshal(j, &m); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if data, err := json.Marshal(m); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return string(data), nil
|
||||
}
|
||||
}
|
||||
|
||||
// BMapToStruct s is struct ptr
|
||||
func BMapToStruct(s interface{}, m map[string]interface{}) error {
|
||||
bsonBytes, err := bson.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bson.Unmarshal(bsonBytes, s)
|
||||
}
|
||||
|
||||
// JMapToStruct s is struct ptr
|
||||
func JMapToStruct(s interface{}, m map[string]interface{}) error {
|
||||
bsonBytes, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(bsonBytes, s)
|
||||
}
|
||||
|
||||
// JSONStruct2Map 将struct转换为Map
|
||||
func JSONStruct2Map(obj interface{}) (map[string]interface{}, error) {
|
||||
m := make(map[string]interface{})
|
||||
j, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(j, &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, err
|
||||
}
|
||||
|
||||
// Map2JSONStruct 将map转换为Json struct
|
||||
func Map2JSONStruct(v interface{}, m map[string]interface{}) error {
|
||||
j, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(j, v); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func IDArray(ids []string) ([]ID, error) {
|
||||
var idArray []ID
|
||||
for _, id := range ids {
|
||||
_id, err := primitive.ObjectIDFromHex(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idArray = append(idArray, _id)
|
||||
}
|
||||
|
||||
return idArray, nil
|
||||
}
|
||||
|
||||
// 拼接https://
|
||||
func BindHttpSch(path string) string {
|
||||
if strings.HasPrefix(path, "http") {
|
||||
return path
|
||||
} else {
|
||||
return "https://" + path
|
||||
}
|
||||
}
|
||||
|
||||
func BindUrl(hostname string, path ...string) string {
|
||||
if hostname == "" {
|
||||
return strings.TrimLeft(filepath.Join(path...), "/")
|
||||
}
|
||||
if len(path) == 0 {
|
||||
return hostname
|
||||
}
|
||||
su := strings.TrimLeft(filepath.Join(path...), "/")
|
||||
pr := strings.TrimRight(hostname, "/")
|
||||
return pr + "/" + su
|
||||
}
|
||||
|
||||
func ObjectIDs2String(obj []primitive.ObjectID) []string {
|
||||
arr := []string{}
|
||||
if len(obj) == 0 {
|
||||
return arr
|
||||
}
|
||||
for _, o := range obj {
|
||||
arr = append(arr, o.Hex())
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
func String2ObjectID(obj []string) []primitive.ObjectID {
|
||||
arr := []primitive.ObjectID{}
|
||||
for _, o := range obj {
|
||||
oid, err := primitive.ObjectIDFromHex(o)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
arr = append(arr, oid)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
// PercentOf - calculate what percent [number1] is of [number2].
|
||||
// ex. 300 is 12.5% of 2400
|
||||
func PercentOf(part int, total int) float64 {
|
||||
return (float64(part) * float64(100)) / float64(total)
|
||||
}
|
||||
|
||||
func MergeMap(src, dest map[string]interface{}) map[string]interface{} {
|
||||
if len(src) == 0 {
|
||||
return dest
|
||||
}
|
||||
if len(dest) == 0 {
|
||||
return src
|
||||
}
|
||||
if len(src) == 0 && len(dest) == 0 {
|
||||
return make(map[string]interface{})
|
||||
}
|
||||
for k, v := range dest {
|
||||
src[k] = v
|
||||
}
|
||||
return src
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DeepCopy 深copy
|
||||
func DeepCopy(dst, src interface{}) error {
|
||||
var buf bytes.Buffer
|
||||
if err := gob.NewEncoder(&buf).Encode(src); err != nil {
|
||||
return err
|
||||
}
|
||||
return gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(dst)
|
||||
}
|
||||
|
||||
// Interface for delegating copy process to type
|
||||
type Interface interface {
|
||||
DeepCopy() interface{}
|
||||
}
|
||||
|
||||
// Iface is an alias to Copy; this exists for backwards compatibility reasons.
|
||||
func Iface(iface interface{}) interface{} {
|
||||
return Copy(iface)
|
||||
}
|
||||
|
||||
// Copy creates a deep copy of whatever is passed to it and returns the copy
|
||||
// in an interface{}. The returned value will need to be asserted to the
|
||||
// correct type.
|
||||
func Copy(src interface{}) interface{} {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make the interface a reflect.Value
|
||||
original := reflect.ValueOf(src)
|
||||
|
||||
// Make a copy of the same type as the original.
|
||||
cpy := reflect.New(original.Type()).Elem()
|
||||
|
||||
// Recursively copy the original.
|
||||
copyRecursive(original, cpy)
|
||||
|
||||
// Return the copy as an interface.
|
||||
return cpy.Interface()
|
||||
}
|
||||
|
||||
// copyRecursive does the actual copying of the interface. It currently has
|
||||
// limited support for what it can handle. Add as needed.
|
||||
func copyRecursive(original, cpy reflect.Value) {
|
||||
// check for implement deepcopy.Interface
|
||||
if original.CanInterface() {
|
||||
if copier, ok := original.Interface().(Interface); ok {
|
||||
cpy.Set(reflect.ValueOf(copier.DeepCopy()))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handle according to original's Kind
|
||||
switch original.Kind() {
|
||||
case reflect.Ptr:
|
||||
// Get the actual value being pointed to.
|
||||
originalValue := original.Elem()
|
||||
|
||||
// if it isn't valid, return.
|
||||
if !originalValue.IsValid() {
|
||||
return
|
||||
}
|
||||
cpy.Set(reflect.New(originalValue.Type()))
|
||||
copyRecursive(originalValue, cpy.Elem())
|
||||
|
||||
case reflect.Interface:
|
||||
// If this is a nil, don't do anything
|
||||
if original.IsNil() {
|
||||
return
|
||||
}
|
||||
// Get the value for the interface, not the pointer.
|
||||
originalValue := original.Elem()
|
||||
|
||||
// Get the value by calling Elem().
|
||||
copyValue := reflect.New(originalValue.Type()).Elem()
|
||||
copyRecursive(originalValue, copyValue)
|
||||
cpy.Set(copyValue)
|
||||
|
||||
case reflect.Struct:
|
||||
t, ok := original.Interface().(time.Time)
|
||||
if ok {
|
||||
cpy.Set(reflect.ValueOf(t))
|
||||
return
|
||||
}
|
||||
// Go through each field of the struct and copy it.
|
||||
for i := 0; i < original.NumField(); i++ {
|
||||
// The Type's StructField for a given field is checked to see if StructField.PkgPath
|
||||
// is set to determine if the field is exported or not because CanSet() returns false
|
||||
// for settable fields. I'm not sure why. -mohae
|
||||
if original.Type().Field(i).PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
copyRecursive(original.Field(i), cpy.Field(i))
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
if original.IsNil() {
|
||||
return
|
||||
}
|
||||
// Make a new slice and copy each element.
|
||||
cpy.Set(reflect.MakeSlice(original.Type(), original.Len(), original.Cap()))
|
||||
for i := 0; i < original.Len(); i++ {
|
||||
copyRecursive(original.Index(i), cpy.Index(i))
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
if original.IsNil() {
|
||||
return
|
||||
}
|
||||
cpy.Set(reflect.MakeMap(original.Type()))
|
||||
for _, key := range original.MapKeys() {
|
||||
originalValue := original.MapIndex(key)
|
||||
copyValue := reflect.New(originalValue.Type()).Elem()
|
||||
copyRecursive(originalValue, copyValue)
|
||||
copyKey := Copy(key.Interface())
|
||||
cpy.SetMapIndex(reflect.ValueOf(copyKey), copyValue)
|
||||
}
|
||||
|
||||
default:
|
||||
cpy.Set(original)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package utils
|
||||
|
||||
import "os"
|
||||
|
||||
// CreateEmptyFile 创建一个固定size大小的空文件
|
||||
// 如果seek文件失败,则删除文件,并返回错误信息
|
||||
func CreateEmptyFile(name string, size int64) (f *os.File, err error) {
|
||||
defer func() {
|
||||
if err != nil && f != nil {
|
||||
f.Close()
|
||||
os.Remove(name)
|
||||
}
|
||||
}()
|
||||
f, err = os.OpenFile(name, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = f.Seek(size-1, 0); err != nil {
|
||||
return
|
||||
}
|
||||
_, err = f.Write([]byte{0})
|
||||
return
|
||||
}
|
||||
|
||||
func WriteFile(data []byte, filename string) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
os.Remove(filename)
|
||||
}
|
||||
}()
|
||||
|
||||
pi := PathForInfo(filename)
|
||||
if pi.Dir != "" {
|
||||
if err = os.MkdirAll(pi.Dir, 0755); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = f.Write(data)
|
||||
f.Close()
|
||||
return
|
||||
}
|
||||
func PathExists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package utils
|
||||
|
||||
// // MakeCrc
|
||||
// func MakeCrc(s string) uint32 {
|
||||
// return crc32.ChecksumIEEE([]byte(s))
|
||||
// }
|
||||
|
||||
// func Md5V(str string) string {
|
||||
// h := md5.New()
|
||||
// h.Write([]byte(str))
|
||||
// return hex.EncodeToString(h.Sum(nil))
|
||||
// }
|
||||
@@ -0,0 +1,283 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var once sync.Once
|
||||
var _client *http.Client
|
||||
|
||||
func GetHttpClient() *http.Client {
|
||||
once.Do(func() {
|
||||
_client = &http.Client{
|
||||
Timeout: time.Minute * 1, //设置超时时间
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
TLSHandshakeTimeout: 10 * time.Second, //限制 TLS握手的时间
|
||||
ResponseHeaderTimeout: 60 * time.Second, //限制读取response header的时间,默认 timeout + 5*time.Second
|
||||
ExpectContinueTimeout: 1 * time.Second, //限制client在发送包含 Expect: 100-continue的header到收到继续发送body的response之间的时间等待。
|
||||
MaxIdleConns: 100, //所有host的连接池最大连接数量,默认无穷大
|
||||
MaxIdleConnsPerHost: 300, //每个host的连接池最大空闲连接数,默认2
|
||||
MaxConnsPerHost: 100, //每个host的最大连接数量
|
||||
IdleConnTimeout: 3 * time.Minute, //how long an idle connection is kept in the connection pool.
|
||||
},
|
||||
}
|
||||
})
|
||||
return _client
|
||||
}
|
||||
|
||||
func getBytes(request interface{}) ([]byte, error) {
|
||||
var body []byte
|
||||
switch t := request.(type) {
|
||||
case []byte:
|
||||
body = t
|
||||
case string:
|
||||
body = []byte(t)
|
||||
default:
|
||||
var err error
|
||||
body, err = json.Marshal(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
type OnHttpResponse func(res *http.Response) error
|
||||
|
||||
func HttpRequest(req *http.Request, onResponse OnHttpResponse) error {
|
||||
client := GetHttpClient()
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if onResponse != nil {
|
||||
err = onResponse(res)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func HttpMethod(url, method string, header http.Header, body io.Reader, onResponse OnHttpResponse) error {
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if header != nil {
|
||||
req.Header = header
|
||||
}
|
||||
return HttpRequest(req, onResponse)
|
||||
}
|
||||
|
||||
func HttpGet(url string, header http.Header, body io.Reader, onResponse OnHttpResponse) error {
|
||||
return HttpMethod(url, http.MethodGet, header, body, onResponse)
|
||||
}
|
||||
|
||||
func HttpPost(url string, header http.Header, body io.Reader, onResponse OnHttpResponse) error {
|
||||
return HttpMethod(url, http.MethodPost, header, body, onResponse)
|
||||
}
|
||||
|
||||
func HttpDelete(url string, header http.Header, body io.Reader, onResponse OnHttpResponse) error {
|
||||
return HttpMethod(url, http.MethodDelete, header, body, onResponse)
|
||||
}
|
||||
|
||||
func HttpPostRequestJson(url string, request interface{}, resp interface{}) error {
|
||||
header := make(http.Header)
|
||||
header.Set("Content-Type", "application/json")
|
||||
body, err := getBytes(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return HttpPost(url, header, bytes.NewReader(body), func(res *http.Response) error {
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("http request (%s) error %s", url, res.Status)
|
||||
}
|
||||
content, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(content, resp)
|
||||
})
|
||||
}
|
||||
|
||||
func POSTJson(url string, headers map[string]string, data interface{}) (*http.Response, error) {
|
||||
header := make(http.Header)
|
||||
for k, v := range headers {
|
||||
header.Set(k, v)
|
||||
}
|
||||
body, err := getBytes(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequest("POST", url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header = header
|
||||
client := GetHttpClient()
|
||||
return client.Do(req)
|
||||
}
|
||||
|
||||
func GetWithParam(url string, headers map[string]string, params ...interface{}) (*http.Response, error) {
|
||||
header := make(http.Header)
|
||||
for k, v := range headers {
|
||||
header.Set(k, v)
|
||||
}
|
||||
for _, p := range params {
|
||||
url = addParams(url, toUrlValues(p))
|
||||
}
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header = header
|
||||
client := GetHttpClient()
|
||||
return client.Do(req)
|
||||
}
|
||||
|
||||
func GetWithJResp(bind interface{}, url string, headers map[string]string, params ...interface{}) (int, error) {
|
||||
header := make(http.Header)
|
||||
for k, v := range headers {
|
||||
header.Set(k, v)
|
||||
}
|
||||
for _, p := range params {
|
||||
url = addParams(url, toUrlValues(p))
|
||||
}
|
||||
code := http.StatusBadRequest
|
||||
err := HttpGet(url, header, nil, func(res *http.Response) error {
|
||||
code = res.StatusCode
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("http request (%s) error %s", url, res.Status)
|
||||
}
|
||||
content, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(content, bind)
|
||||
})
|
||||
return code, err
|
||||
}
|
||||
|
||||
func POSTWithJResp(bind interface{}, url string, headers map[string]string, params interface{}) (int, error) {
|
||||
header := make(http.Header)
|
||||
for k, v := range headers {
|
||||
header.Set(k, v)
|
||||
}
|
||||
body, err := getBytes(params)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, err
|
||||
}
|
||||
code := http.StatusBadRequest
|
||||
err = HttpPost(url, header, bytes.NewReader(body), func(res *http.Response) error {
|
||||
code = res.StatusCode
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("http request (%s) error %s", url, res.Status)
|
||||
}
|
||||
content, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(content, bind)
|
||||
})
|
||||
return code, err
|
||||
}
|
||||
|
||||
func POSTJsonWithJResp(bind interface{}, url string, headers map[string]string, data interface{}) (int, error) {
|
||||
header := make(http.Header)
|
||||
for k, v := range headers {
|
||||
header.Set(k, v)
|
||||
}
|
||||
header.Set("Content-Type", "application/json")
|
||||
body, err := getBytes(data)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, err
|
||||
}
|
||||
code := http.StatusBadRequest
|
||||
err = HttpPost(url, header, bytes.NewReader(body), func(res *http.Response) error {
|
||||
code = res.StatusCode
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("http request (%s) error %s", url, res.Status)
|
||||
}
|
||||
content, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(content, bind)
|
||||
})
|
||||
return code, err
|
||||
}
|
||||
|
||||
func DeleteWithJResp(bind interface{}, url string, headers map[string]string, params interface{}) (int, error) {
|
||||
header := make(http.Header)
|
||||
for k, v := range headers {
|
||||
header.Set(k, v)
|
||||
}
|
||||
body, err := getBytes(params)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, err
|
||||
}
|
||||
code := http.StatusBadRequest
|
||||
err = HttpDelete(url, header, bytes.NewReader(body), func(res *http.Response) error {
|
||||
code = res.StatusCode
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("http request (%s) error %s", url, res.Status)
|
||||
}
|
||||
content, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(content, bind)
|
||||
})
|
||||
return code, err
|
||||
}
|
||||
|
||||
// Add params to a url string.
|
||||
func addParams(url_ string, params url.Values) string {
|
||||
if len(params) == 0 {
|
||||
return url_
|
||||
}
|
||||
|
||||
if !strings.Contains(url_, "?") {
|
||||
url_ += "?"
|
||||
}
|
||||
|
||||
if strings.HasSuffix(url_, "?") || strings.HasSuffix(url_, "&") {
|
||||
url_ += params.Encode()
|
||||
} else {
|
||||
url_ += "&" + params.Encode()
|
||||
}
|
||||
|
||||
return url_
|
||||
}
|
||||
|
||||
func toUrlValues(v interface{}) url.Values {
|
||||
switch t := v.(type) {
|
||||
case url.Values:
|
||||
return t
|
||||
case map[string][]string:
|
||||
return url.Values(t)
|
||||
case map[string]string:
|
||||
rst := make(url.Values)
|
||||
for k, v := range t {
|
||||
rst.Add(k, v)
|
||||
}
|
||||
return rst
|
||||
case nil:
|
||||
return make(url.Values)
|
||||
default:
|
||||
panic("Invalid value")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
const (
|
||||
userIDKey = "userId"
|
||||
ipKey = "ip"
|
||||
uaKey = "ua"
|
||||
)
|
||||
|
||||
type IPClaims struct {
|
||||
Ip string
|
||||
Expire time.Duration
|
||||
UA string
|
||||
}
|
||||
|
||||
func GenerateIpToken(claims IPClaims, secret string) (string, error) {
|
||||
return generateToken(jwt.MapClaims{
|
||||
ipKey: claims.Ip,
|
||||
uaKey: claims.UA,
|
||||
"exp": Now().Add(claims.Expire).Unix(),
|
||||
"issuer": "com.butterfly",
|
||||
"sub": "asign",
|
||||
"aud": "public",
|
||||
}, []byte(secret))
|
||||
}
|
||||
|
||||
func ParseIpToken(tokenString string, secret string) (pass bool, ip, ua string, err error) {
|
||||
t, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(secret), nil
|
||||
})
|
||||
var isExpire *bool
|
||||
if vErr, ok := err.(*jwt.ValidationError); ok {
|
||||
e := vErr.Errors&jwt.ValidationErrorExpired > 0
|
||||
isExpire = &e
|
||||
}
|
||||
if err != nil || isExpire == nil {
|
||||
return
|
||||
}
|
||||
var ok bool
|
||||
var jc jwt.MapClaims
|
||||
jc, ok = t.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
err = errors.New("parse token fail")
|
||||
return
|
||||
}
|
||||
ip, ok = jc[ipKey].(string)
|
||||
if !ok {
|
||||
err = errors.New("parse token fail")
|
||||
return
|
||||
}
|
||||
ua, ok = jc[uaKey].(string)
|
||||
if !ok {
|
||||
err = errors.New("parse token fail")
|
||||
return
|
||||
}
|
||||
pass = true
|
||||
return
|
||||
}
|
||||
|
||||
type UserClaims struct {
|
||||
UserID int64
|
||||
Expire time.Duration
|
||||
}
|
||||
|
||||
func generateToken(claims jwt.MapClaims, secret []byte) (string, error) {
|
||||
return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secret)
|
||||
}
|
||||
|
||||
func GenerateUserToken(claims UserClaims, secret string) (string, error) {
|
||||
return generateToken(jwt.MapClaims{
|
||||
userIDKey: claims.UserID,
|
||||
"exp": Now().Add(claims.Expire).Unix(),
|
||||
"issuer": "com.butterfly",
|
||||
"sub": "asign",
|
||||
"aud": "public",
|
||||
}, []byte(secret))
|
||||
}
|
||||
|
||||
// ParseToken 解析Token
|
||||
func ParseUserToken(token string, secret string) (userId int64, newToken string, err error) {
|
||||
t, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(secret), nil
|
||||
})
|
||||
var isExpire *bool
|
||||
if vErr, ok := err.(jwt.ValidationError); ok {
|
||||
e := vErr.Errors&jwt.ValidationErrorExpired > 0
|
||||
isExpire = &e
|
||||
}
|
||||
if err != nil && isExpire == nil {
|
||||
return
|
||||
}
|
||||
|
||||
jc, ok := t.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
err = errors.New("unknown token error1")
|
||||
return
|
||||
}
|
||||
|
||||
id, ok := jc[userIDKey].(float64)
|
||||
if !ok {
|
||||
err = errors.New("unknown token error1")
|
||||
return
|
||||
}
|
||||
userId = int64(id)
|
||||
if isExpire != nil && *isExpire {
|
||||
newToken, err = GenerateUserToken(UserClaims{UserID: userId, Expire: 30 * 24 * time.Hour}, secret)
|
||||
return
|
||||
}
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
|
||||
// Strips 'Bearer ' prefix from bearer token string
|
||||
func StripBearerPrefixFromTokenString(tok string) string {
|
||||
if len(tok) > 6 && strings.ToUpper(tok[0:7]) == "BEARER " {
|
||||
return tok[7:]
|
||||
} else {
|
||||
return tok
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CountDigits 有几位数字
|
||||
func CountDigits(i int) (count int) {
|
||||
for i != 0 {
|
||||
i /= 10
|
||||
count++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func RandInt(min int, max int) int {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
return min + rand.Intn(max-min)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package utils
|
||||
|
||||
func GetPageRange(totalLen, pageNum, pageSize int) (start, end int) {
|
||||
if pageNum < 1 {
|
||||
pageNum = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
start = (pageNum - 1) * pageSize
|
||||
if start > totalLen {
|
||||
start = totalLen
|
||||
}
|
||||
end = start + pageSize
|
||||
if end > totalLen {
|
||||
end = totalLen
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/PuerkitoBio/purell"
|
||||
)
|
||||
|
||||
// PathSegment slug 优先,空则回退数字 id。
|
||||
func PathSegment(slug string, id int64) string {
|
||||
if s := strings.TrimSpace(slug); s != "" {
|
||||
return s
|
||||
}
|
||||
if id > 0 {
|
||||
return strconv.FormatInt(id, 10)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// PathInfo 路径信息
|
||||
type PathInfo struct {
|
||||
Dir string // 目录
|
||||
Ext string // 扩展名
|
||||
Filename string // 没有扩展名的文件名名称
|
||||
FullFileName string // 文件全名
|
||||
}
|
||||
|
||||
// PathForInfo 解析路径
|
||||
// 返回文件,路径,文件名等信息
|
||||
func PathForInfo(path string) (pi PathInfo) {
|
||||
if path == "" {
|
||||
return
|
||||
}
|
||||
ext := filepath.Ext(path)
|
||||
dir, name := filepath.Split(path)
|
||||
|
||||
pi.Dir = dir
|
||||
pi.Ext = ext
|
||||
pi.Filename = strings.TrimRight(name, ext)
|
||||
pi.FullFileName = name
|
||||
return
|
||||
}
|
||||
|
||||
// JoinUrlPath 域名与uri拼接(走 purell 标准化,会剥掉尾部斜杠)。
|
||||
// 适用于 CDN 文件链接(m3u8 分段、vtt、静态图等)这类"不需要尾部斜杠"的场景。
|
||||
// 需要保留尾部斜杠的页面 URL(sitemap/rss/indexnow)请使用 JoinUrlPathKeepSlash。
|
||||
func JoinUrlPath(host string, uri string) (urlpath string) {
|
||||
urlpath = joinRawUrlPath(host, uri)
|
||||
if urlpath == "" {
|
||||
return
|
||||
}
|
||||
// 标准化 URL
|
||||
normalized, err := purell.NormalizeURLString(urlpath, purell.FlagsUsuallySafeGreedy)
|
||||
if err != nil {
|
||||
return urlpath
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
// JoinUrlPathKeepSlash 域名与uri拼接,保留 uri 原始的尾部斜杠,不做任何标准化。
|
||||
// sitemap/rss/indexnow 的页面 URL 必须与路由注册的 /archives/:id/ 等完全一致,
|
||||
// 否则爬虫每次访问都会被 gin 的 RedirectTrailingSlash 301 到带 / 的地址,
|
||||
// 浪费抓取预算并造成重定向链。
|
||||
func JoinUrlPathKeepSlash(host string, uri string) string {
|
||||
return joinRawUrlPath(host, uri)
|
||||
}
|
||||
|
||||
// JoinUrlPathAppendEnd 域名与uri拼接 追加/结尾
|
||||
func JoinUrlPathAppendEnd(host string, uri string) (urlpath string) {
|
||||
if uri == "" {
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case strings.HasSuffix(host, "/") && strings.HasPrefix(uri, "/"):
|
||||
urlpath = host[0:len(host)-1] + uri
|
||||
case !strings.HasSuffix(host, "/") && !strings.HasPrefix(uri, "/"):
|
||||
urlpath = host + "/" + uri
|
||||
default:
|
||||
urlpath = host + uri
|
||||
}
|
||||
// 标准化 URL
|
||||
normalized, err := purell.NormalizeURLString(urlpath, purell.FlagsUsuallySafeGreedy)
|
||||
if err != nil {
|
||||
return urlpath
|
||||
}
|
||||
if !strings.HasSuffix(normalized, "/") {
|
||||
normalized = fmt.Sprintf("%s/", normalized)
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
// joinRawUrlPath 纯拼接,不做任何标准化。
|
||||
func joinRawUrlPath(host string, uri string) (urlpath string) {
|
||||
if uri == "" {
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case strings.HasSuffix(host, "/") && strings.HasPrefix(uri, "/"):
|
||||
urlpath = host[0:len(host)-1] + uri
|
||||
case !strings.HasSuffix(host, "/") && !strings.HasPrefix(uri, "/"):
|
||||
urlpath = host + "/" + uri
|
||||
default:
|
||||
urlpath = host + uri
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"time"
|
||||
"videoweb/pub/common/enum/redisconst"
|
||||
sErr "videoweb/pub/common/errors"
|
||||
"videoweb/pub/redis"
|
||||
)
|
||||
|
||||
const (
|
||||
expireTime = 30 * 24 * time.Hour
|
||||
)
|
||||
|
||||
type TokenRedis struct {
|
||||
}
|
||||
|
||||
func NewTokenRedis() *TokenRedis {
|
||||
return &TokenRedis{}
|
||||
}
|
||||
|
||||
func (tr *TokenRedis) Save(userId int64, token string) (err error) {
|
||||
if err = tr.Delete(userId); err != nil {
|
||||
return
|
||||
}
|
||||
err = redis.Set(redisconst.AppUserTokenKey(userId), token, expireTime)
|
||||
if err != nil {
|
||||
err = sErr.MakeWithErr(sErr.RedisSet, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (tr *TokenRedis) Exists(userId int64, token string) (has bool, err error) {
|
||||
tokenbytes, err := tr.Get(userId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
has = bytes.Compare(tokenbytes, []byte(token)) == 0
|
||||
return
|
||||
}
|
||||
|
||||
func (tr *TokenRedis) Get(userId int64) (token []byte, err error) {
|
||||
token, err = redis.GetBytes(redisconst.AppUserTokenKey(userId))
|
||||
if err != nil {
|
||||
err = sErr.MakeWithErr(sErr.RedisGet, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (tr *TokenRedis) Delete(userId int64) (err error) {
|
||||
err = redis.Del(redisconst.AppUserTokenKey(userId))
|
||||
if err != nil {
|
||||
err = sErr.MakeWithErr(sErr.RedisDel, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func ShellCmd(cmdStr string) (string, error) {
|
||||
cmd := exec.Command("/bin/bash", "-c", cmdStr)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
ec := cmd.Run()
|
||||
|
||||
return out.String(), ec
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"golang.org/x/exp/constraints"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func SliceConstruct[T any](fn func() T) T {
|
||||
return fn()
|
||||
}
|
||||
|
||||
type SliceWalkCallback func(index int) bool // true 继续
|
||||
func SliceWalk(length int, cb SliceWalkCallback) {
|
||||
for i := 0; i < length; i++ {
|
||||
if ok := cb(i); ok {
|
||||
continue
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type SliceIncludeFilter func(index int) bool // 返回 true 继续, false 中断
|
||||
func SliceInclude(length int, filter SliceIncludeFilter) (int, bool) {
|
||||
for i := 0; i < length; i++ {
|
||||
if ok := filter(i); ok {
|
||||
return i, true
|
||||
}
|
||||
}
|
||||
return -1, false
|
||||
}
|
||||
|
||||
type SliceConstructNFilter[T any] func(index int) (T, bool) // 返回 是否保留,项值
|
||||
func SliceConstructN[T any](length int, filter SliceConstructNFilter[T]) []T {
|
||||
ret := make([]T, 0)
|
||||
for i := 0; i < length; i++ {
|
||||
if item, keep := filter(i); keep {
|
||||
ret = append(ret, item)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// SliceUnique 使用泛型实现切片去重
|
||||
func SliceUnique[T comparable](s []T) []T {
|
||||
seen := make(map[T]bool)
|
||||
j := 0
|
||||
for _, v := range s {
|
||||
if _, ok := seen[v]; !ok {
|
||||
seen[v] = true
|
||||
s[j] = v
|
||||
j++
|
||||
}
|
||||
}
|
||||
return s[:j]
|
||||
}
|
||||
|
||||
// SlicesDifference 求两个集合的差集
|
||||
func SlicesDifference[T comparable](a, b []T) []T {
|
||||
// 使用 map 存储集合 b 的元素,方便快速判断元素是否存在
|
||||
seen := make(map[T]struct{}, len(b))
|
||||
for _, v := range b {
|
||||
seen[v] = struct{}{}
|
||||
}
|
||||
|
||||
// 遍历集合 a,将不在集合 b 中的元素放入新的切片
|
||||
diff := make([]T, 0)
|
||||
for _, v := range a {
|
||||
if _, ok := seen[v]; !ok {
|
||||
diff = append(diff, v)
|
||||
}
|
||||
}
|
||||
|
||||
return diff
|
||||
}
|
||||
|
||||
func RemoveSlicesElement[T comparable](slice []T, element T) []T {
|
||||
// 使用两个指针,一个遍历,一个用来记录有效元素的位置
|
||||
j := 0
|
||||
for i := range slice {
|
||||
if slice[i] != element {
|
||||
slice[j] = slice[i]
|
||||
j++
|
||||
}
|
||||
}
|
||||
// 返回截取后的切片
|
||||
return slice[:j]
|
||||
}
|
||||
|
||||
func SlicesIntersect[T constraints.Ordered | ~string](slice1, slice2 []T) []T {
|
||||
dict := make(map[T]int)
|
||||
ret := make([]T, 0)
|
||||
for _, v1 := range slice1 {
|
||||
dict[v1]++
|
||||
}
|
||||
for _, v2 := range slice2 {
|
||||
if _, exists := dict[v2]; exists {
|
||||
ret = append(ret, v2)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func IsSliceEqual[T constraints.Ordered](src []T, dst []T) bool {
|
||||
sort.Slice(src, func(i, j int) bool { return src[i] < src[j] })
|
||||
sort.Slice(dst, func(i, j int) bool { return dst[i] < dst[j] })
|
||||
if len(src) != len(dst) {
|
||||
return false
|
||||
}
|
||||
for i := range src {
|
||||
if src[i] != dst[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const slugTitleRuneLimit = 10
|
||||
|
||||
var (
|
||||
reSlugFC2Code = regexp.MustCompile(`(?i)fc2[-_ ]?ppv[-_ ]?\d{5,12}`)
|
||||
reSlugNumLetterCode = regexp.MustCompile(`(?i)\d{3}[a-z]{2,6}[-_ ]?\d{2,7}`)
|
||||
reSlugLetterNumCode = regexp.MustCompile(`(?i)[a-z]{2,8}[-_ ]?\d{2,7}`)
|
||||
reSlugLeadingYear = regexp.MustCompile(`^20\d{2}`)
|
||||
reSlugLeadingBracket = regexp.MustCompile(`^[【\[]([^】\]]+)[】\]]|^[((]([^))]+)[))]|^「([^」]+)」`)
|
||||
)
|
||||
|
||||
// 标题开头常见的无意义前缀,长词优先。
|
||||
var slugTitlePrefixNoise = []string{
|
||||
"中文字幕", "中文翻譯", "中文翻译", "无码流出", "無碼流出",
|
||||
"无码破解", "無碼破解", "无修正", "無修正", "完整版", "抢先版",
|
||||
"高清版", "字幕版",
|
||||
}
|
||||
|
||||
// 技术规格、版本标记,单独出现在标题开头时去掉。
|
||||
var slugTitlePrefixTokens = []string{
|
||||
"1080P", "2160P", "720P", "360P", "FHD", "UHD", "4K", "8K", "HD",
|
||||
"FANZA", "MGS", "最新", "高清", "流出", "破解", "字幕", "中字",
|
||||
}
|
||||
|
||||
// 关键词里过于通用、几乎每条都有的词,不单独拿来做 slug。
|
||||
var slugGenericKeywords = map[string]struct{}{
|
||||
"中文字幕": {}, "中文翻译": {}, "中文翻譯": {}, "无码流出": {}, "無碼流出": {},
|
||||
"无码": {}, "無碼": {}, "有码": {}, "有碼": {}, "无修正": {}, "無修正": {},
|
||||
"日本": {}, "国产": {}, "國產": {}, "素人": {}, "人妻": {}, "熟女": {},
|
||||
"中文": {}, "字幕": {}, "高清": {}, "最新": {}, "流出": {}, "破解": {},
|
||||
"hd": {}, "4k": {}, "fhd": {}, "fanza": {},
|
||||
}
|
||||
|
||||
// SlugSourceFromKeywordsOrTitle 从标题和关键词提取适合转拼音的 slug 源文本。
|
||||
// 优先用标题里的番号或去掉无意义前缀后的有效片段,不再固定取第一个关键词。
|
||||
func SlugSourceFromKeywordsOrTitle(keywords []string, title string) string {
|
||||
title = strings.TrimSpace(title)
|
||||
if code := firstContentCode(title, keywords); code != "" {
|
||||
return code
|
||||
}
|
||||
if slice := meaningfulTitleSlice(cleanTitleForSlug(title)); slice != "" {
|
||||
return slice
|
||||
}
|
||||
if kw := pickDistinctKeyword(keywords); kw != "" {
|
||||
return kw
|
||||
}
|
||||
if title != "" {
|
||||
return GetPrefix(title, slugTitleRuneLimit)
|
||||
}
|
||||
return firstNonEmptyKeyword(keywords)
|
||||
}
|
||||
|
||||
func firstContentCode(title string, keywords []string) string {
|
||||
if code := extractContentCode(title); code != "" {
|
||||
return code
|
||||
}
|
||||
for _, kw := range keywords {
|
||||
if code := extractContentCode(kw); code != "" {
|
||||
return code
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractContentCode(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
if m := reSlugFC2Code.FindString(s); m != "" {
|
||||
return m
|
||||
}
|
||||
if m := reSlugNumLetterCode.FindString(s); m != "" {
|
||||
return m
|
||||
}
|
||||
if m := reSlugLetterNumCode.FindString(s); m != "" && !isQualityToken(m) {
|
||||
return m
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func isQualityToken(s string) bool {
|
||||
s = strings.ToUpper(strings.TrimSpace(s))
|
||||
s = strings.ReplaceAll(s, " ", "")
|
||||
switch s {
|
||||
case "1080P", "2160P", "720P", "360P", "4K", "8K", "HD", "FHD", "UHD":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func cleanTitleForSlug(title string) string {
|
||||
s := strings.TrimSpace(title)
|
||||
for i := 0; i < 8 && s != ""; i++ {
|
||||
next := stripOnceTitlePrefix(s)
|
||||
if next == s {
|
||||
break
|
||||
}
|
||||
s = next
|
||||
}
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
|
||||
func stripOnceTitlePrefix(s string) string {
|
||||
s = strings.TrimLeftFunc(s, func(r rune) bool {
|
||||
return unicode.IsSpace(r) || strings.ContainsRune(`-_~|/.,;::、·•*#@`, r)
|
||||
})
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
if loc := reSlugLeadingBracket.FindStringSubmatch(s); len(loc) > 0 {
|
||||
inner := firstSubmatch(loc)
|
||||
whole := loc[0]
|
||||
if extractContentCode(inner) != "" {
|
||||
return strings.TrimSpace(inner + s[len(whole):])
|
||||
}
|
||||
if isNoiseText(inner) || runeLen(inner) <= 4 {
|
||||
return strings.TrimSpace(s[len(whole):])
|
||||
}
|
||||
}
|
||||
lower := strings.ToLower(s)
|
||||
for _, prefix := range slugTitlePrefixNoise {
|
||||
if strings.HasPrefix(s, prefix) || strings.HasPrefix(lower, strings.ToLower(prefix)) {
|
||||
return strings.TrimSpace(s[len([]byte(prefixMatch(s, prefix))):])
|
||||
}
|
||||
}
|
||||
for _, token := range slugTitlePrefixTokens {
|
||||
if consumed, ok := consumeLeadingToken(s, token); ok {
|
||||
return consumed
|
||||
}
|
||||
}
|
||||
if loc := reSlugLeadingYear.FindStringIndex(s); loc != nil && loc[0] == 0 {
|
||||
rest := strings.TrimSpace(s[loc[1]:])
|
||||
if rest != "" {
|
||||
return rest
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func prefixMatch(s, prefix string) string {
|
||||
if strings.HasPrefix(s, prefix) {
|
||||
return prefix
|
||||
}
|
||||
lower := strings.ToLower(s)
|
||||
p := strings.ToLower(prefix)
|
||||
if strings.HasPrefix(lower, p) {
|
||||
return s[:len(prefix)]
|
||||
}
|
||||
return prefix
|
||||
}
|
||||
|
||||
func consumeLeadingToken(s, token string) (string, bool) {
|
||||
if len(s) < len(token) {
|
||||
return s, false
|
||||
}
|
||||
if !strings.HasPrefix(strings.ToLower(s), strings.ToLower(token)) {
|
||||
return s, false
|
||||
}
|
||||
rest := s[len(token):]
|
||||
if rest == "" {
|
||||
return "", true
|
||||
}
|
||||
r := []rune(rest)[0]
|
||||
if unicode.IsLetter(r) || unicode.IsDigit(r) {
|
||||
return s, false
|
||||
}
|
||||
return strings.TrimSpace(rest), true
|
||||
}
|
||||
|
||||
func firstSubmatch(m []string) string {
|
||||
for i := 1; i < len(m); i++ {
|
||||
if strings.TrimSpace(m[i]) != "" {
|
||||
return strings.TrimSpace(m[i])
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func meaningfulTitleSlice(title string) string {
|
||||
title = strings.TrimSpace(title)
|
||||
if title == "" {
|
||||
return ""
|
||||
}
|
||||
return GetPrefix(title, slugTitleRuneLimit)
|
||||
}
|
||||
|
||||
func pickDistinctKeyword(keywords []string) string {
|
||||
best := ""
|
||||
bestScore := 0
|
||||
for _, kw := range keywords {
|
||||
kw = strings.TrimSpace(kw)
|
||||
if kw == "" || isGenericKeyword(kw) {
|
||||
continue
|
||||
}
|
||||
score := runeLen(kw)
|
||||
if score < 3 {
|
||||
continue
|
||||
}
|
||||
if score > bestScore {
|
||||
best = kw
|
||||
bestScore = score
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
func firstNonEmptyKeyword(keywords []string) string {
|
||||
for _, kw := range keywords {
|
||||
if s := strings.TrimSpace(kw); s != "" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func isGenericKeyword(s string) bool {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return true
|
||||
}
|
||||
return isGenericKeywordExact(s) || isNoiseText(s)
|
||||
}
|
||||
|
||||
func isNoiseText(s string) bool {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return true
|
||||
}
|
||||
if isGenericKeywordExact(s) || isQualityToken(s) {
|
||||
return true
|
||||
}
|
||||
for _, prefix := range slugTitlePrefixNoise {
|
||||
if s == prefix {
|
||||
return true
|
||||
}
|
||||
}
|
||||
parts := strings.FieldsFunc(s, func(r rune) bool {
|
||||
return strings.ContainsRune(` /|、,,-_+`, r)
|
||||
})
|
||||
if len(parts) > 1 {
|
||||
for _, p := range parts {
|
||||
if !isNoiseText(p) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isGenericKeywordExact(s string) bool {
|
||||
if _, ok := slugGenericKeywords[s]; ok {
|
||||
return true
|
||||
}
|
||||
_, ok := slugGenericKeywords[strings.ToLower(s)]
|
||||
return ok
|
||||
}
|
||||
|
||||
func runeLen(s string) int {
|
||||
return len([]rune(s))
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"math/rand/v2"
|
||||
"strings"
|
||||
|
||||
"github.com/gosimple/slug"
|
||||
)
|
||||
|
||||
// BinarySearch BinarySearch
|
||||
func BinarySearch(s []string, t string, n int) (idx int) {
|
||||
idx = -1
|
||||
if n == 0 {
|
||||
return
|
||||
}
|
||||
start, end := 0, n-1
|
||||
for start <= end {
|
||||
mid := (start + end) >> 1
|
||||
if s[mid] < t {
|
||||
start = mid + 1
|
||||
} else if s[mid] > t {
|
||||
end = mid - 1
|
||||
} else {
|
||||
idx = mid
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var YesOrNo = map[bool]string{
|
||||
true: "是",
|
||||
false: "否",
|
||||
}
|
||||
|
||||
func GetRandomLetters(n int) string {
|
||||
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = letters[rand.IntN(len(letters))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func GetPrefix(s string, n int) string {
|
||||
runes := []rune(s)
|
||||
if len(runes) <= n {
|
||||
return s
|
||||
}
|
||||
return string(runes[:n])
|
||||
}
|
||||
|
||||
func GetSlugInfo(text string) string {
|
||||
return SanitizeSlug(slug.Make(text))
|
||||
}
|
||||
|
||||
// SanitizeSlug 去掉 slug 中的横杠等特殊符号,只保留字母和数字
|
||||
func SanitizeSlug(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
var b strings.Builder
|
||||
b.Grow(len(s))
|
||||
for _, r := range s {
|
||||
if r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' {
|
||||
b.WriteRune(r)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// IsSlugAllDigits 判断 slug 是否全为数字
|
||||
func IsSlugAllDigits(s string) bool {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
for _, r := range s {
|
||||
if r < '0' || r > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MakeUniqueSlug 生成唯一 slug;exists 返回 true 表示已被占用。生成结果不能全是数字。
|
||||
func MakeUniqueSlug(source string, exists func(string) bool) string {
|
||||
pinyin := GetSlugInfo(source)
|
||||
if pinyin == "" {
|
||||
pinyin = GetRandomLetters(5)
|
||||
} else if IsSlugAllDigits(pinyin) {
|
||||
pinyin = pinyin + GetRandomLetters(3)
|
||||
}
|
||||
finalSlug := pinyin
|
||||
for exists(finalSlug) {
|
||||
finalSlug = pinyin + GetRandomLetters(3)
|
||||
}
|
||||
return finalSlug
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func Struct2Map(obj interface{}) map[string]interface{} {
|
||||
t := reflect.TypeOf(obj)
|
||||
v := reflect.ValueOf(obj)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
v = reflect.Indirect(v)
|
||||
}
|
||||
var data = make(map[string]interface{})
|
||||
//func (v Value) NumField() int, 返回v持有的结构体类型值的字段数,如果v的Kind不是Struct会panic
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
n := t.Field(i).Name
|
||||
vv := v.Field(i).Interface()
|
||||
data[Capitalize(n)] = vv
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func Map2Struct(obj interface{}, mapInfo map[string]interface{}) interface{} {
|
||||
err := mapstructure.Decode(mapInfo, obj)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
// Capitalize 字符首字母大写
|
||||
func Capitalize(str string) string {
|
||||
var upperStr string
|
||||
vv := []rune(str) // 后文有介绍
|
||||
for i := 0; i < len(vv); i++ {
|
||||
if i == 0 {
|
||||
if vv[i] >= 65 && vv[i] <= 96 { // 后文有介绍
|
||||
vv[i] += 32 // string的码表相差32位
|
||||
upperStr += string(vv[i])
|
||||
} else {
|
||||
fmt.Println("Not begins with uppercase letter,")
|
||||
return str
|
||||
}
|
||||
} else {
|
||||
upperStr += string(vv[i])
|
||||
}
|
||||
}
|
||||
return upperStr
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"videoweb/pub/pubdef"
|
||||
)
|
||||
|
||||
var (
|
||||
LocBJ = mustBeijingLocation()
|
||||
)
|
||||
|
||||
func mustBeijingLocation() *time.Location {
|
||||
loc, err := time.LoadLocation("Asia/Shanghai")
|
||||
if err != nil || loc == nil {
|
||||
return time.FixedZone("CST", 8*3600)
|
||||
}
|
||||
return loc
|
||||
}
|
||||
|
||||
func init() {
|
||||
time.Local = LocBJ
|
||||
}
|
||||
|
||||
func Now() time.Time {
|
||||
return time.Now().In(LocBJ)
|
||||
}
|
||||
|
||||
func NowMillisecond() int64 {
|
||||
return Now().UnixNano() / int64(time.Millisecond)
|
||||
}
|
||||
|
||||
func BeginOfToday() time.Time {
|
||||
t := Now()
|
||||
return BeginOfTime(t)
|
||||
}
|
||||
|
||||
func BeginOfTime(t time.Time) time.Time {
|
||||
year, month, day := t.Date()
|
||||
return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
|
||||
func GetNextMonthTime(t time.Time) time.Time {
|
||||
year, month, day := t.Date()
|
||||
return time.Date(year, month+1, day, 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
func GetNextQuarterTime(t time.Time) time.Time {
|
||||
year, month, day := t.Date()
|
||||
return time.Date(year, month+3, day, 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
|
||||
func GetNextYearTime(t time.Time) time.Time {
|
||||
year, month, day := t.Date()
|
||||
return time.Date(year+1, month, day, 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
|
||||
func GetForeverTime() time.Time {
|
||||
t := time.Now()
|
||||
year, month, day := t.Date()
|
||||
return time.Date(year+999, month, day, 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
|
||||
func IsoWeek() string {
|
||||
year, week := time.Now().ISOWeek()
|
||||
return fmt.Sprintf("%d%02d", year, week)
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func EndOfToday() time.Time {
|
||||
t := Now()
|
||||
return EndOfTime(t)
|
||||
}
|
||||
|
||||
func EndOfTime(t time.Time) time.Time {
|
||||
year, month, day := t.Date()
|
||||
return time.Date(year, month, day, 23, 59, 59, 0, t.Location())
|
||||
}
|
||||
|
||||
func GetDateString(t time.Time) string {
|
||||
return t.In(LocBJ).Format(pubdef.DateISO)
|
||||
}
|
||||
|
||||
func GetTodayDate(now time.Time) string {
|
||||
return GetDateString(now.In(time.Local))
|
||||
}
|
||||
|
||||
func GetYesterdayDate(now time.Time) string {
|
||||
return GetDateString(now.Local().AddDate(0, 0, -1))
|
||||
}
|
||||
|
||||
func GetDayBeforeYesterdayDate(now time.Time) string {
|
||||
return GetDateString(now.Local().AddDate(0, 0, -2))
|
||||
}
|
||||
|
||||
func GetDayRange(t time.Time) (dayStart time.Time, dayEnd time.Time) {
|
||||
dayStart = BeginOfTime(t)
|
||||
dayEnd = EndOfTime(t)
|
||||
return
|
||||
}
|
||||
|
||||
// GetDayRangeByDate 返回一天的开始和结束时间, 00:00:00 - 23:59:59
|
||||
func GetDayRangeByDate(date string) (dayStart time.Time, dayEnd time.Time, err error) {
|
||||
var d time.Time
|
||||
d, err = time.ParseInLocation(pubdef.DateISO, date, LocBJ)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dayStart, dayEnd = GetDayRange(d)
|
||||
return
|
||||
}
|
||||
|
||||
func GetDayEnd(date string) time.Time {
|
||||
d, _ := time.ParseInLocation(pubdef.DateISO, date, LocBJ)
|
||||
return EndOfTime(d)
|
||||
}
|
||||
|
||||
func GetDayBegin(date string) time.Time {
|
||||
d, _ := time.ParseInLocation(pubdef.DateISO, date, LocBJ)
|
||||
return BeginOfTime(d)
|
||||
}
|
||||
|
||||
func GetDayEndByTime(date string) time.Time {
|
||||
d, _ := time.ParseInLocation(pubdef.TimeISO, date, LocBJ)
|
||||
return EndOfTime(d)
|
||||
}
|
||||
|
||||
func GetDayBeginByTime(date string) time.Time {
|
||||
d, _ := time.ParseInLocation(pubdef.TimeISO, date, LocBJ)
|
||||
return BeginOfTime(d)
|
||||
}
|
||||
|
||||
func BeginWithThisMonth() time.Time {
|
||||
t := Now()
|
||||
year, month, _ := t.Date()
|
||||
return time.Date(year, month, 1, 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
|
||||
func BeginOfMonth(t time.Time) time.Time {
|
||||
year, month, _ := t.In(LocBJ).Date()
|
||||
return time.Date(year, month, 1, 0, 0, 0, 0, LocBJ)
|
||||
}
|
||||
|
||||
func BeginOfYear(t time.Time) time.Time {
|
||||
year, _, _ := t.In(LocBJ).Date()
|
||||
return time.Date(year, 1, 1, 0, 0, 0, 0, LocBJ)
|
||||
}
|
||||
|
||||
func EndOfMonth(t time.Time) time.Time {
|
||||
return BeginOfMonth(t).AddDate(0, 1, 0).Add(-time.Nanosecond)
|
||||
}
|
||||
|
||||
func EndOfLastMonth(t time.Time) time.Time {
|
||||
return BeginOfMonth(t).Add(-time.Nanosecond)
|
||||
}
|
||||
|
||||
func EndOfLastMonthDate(t time.Time) string {
|
||||
return GetDateString(EndOfLastMonth(t))
|
||||
}
|
||||
|
||||
func EndOfLastWeek(t time.Time) time.Time {
|
||||
return BeginOfWeekByTime(t).Add(-time.Nanosecond)
|
||||
}
|
||||
|
||||
func EndOfLastWeekDate(t time.Time) string {
|
||||
return GetDateString(EndOfLastWeek(t))
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func BeginOfWeek() time.Time {
|
||||
return BeginOfWeekByTime(Now())
|
||||
}
|
||||
|
||||
func WeekDateRange(t time.Time) (string, string) {
|
||||
b := BeginOfWeekByTime(t)
|
||||
e := EndOfWeekByTime(t)
|
||||
return GetDateString(b), GetDateString(e)
|
||||
}
|
||||
|
||||
func MonthDateRange(t time.Time) (string, string) {
|
||||
b := BeginOfMonth(t)
|
||||
e := EndOfMonth(t)
|
||||
return GetDateString(b), GetDateString(e)
|
||||
}
|
||||
|
||||
func BeginOfWeekByTime(t time.Time) time.Time {
|
||||
t = t.In(LocBJ)
|
||||
bt := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, LocBJ)
|
||||
weekday := int(t.Weekday())
|
||||
weekStartDayInt := int(time.Monday)
|
||||
if weekday < weekStartDayInt {
|
||||
weekday = weekday + 7 - weekStartDayInt
|
||||
} else {
|
||||
weekday = weekday - weekStartDayInt
|
||||
}
|
||||
return bt.AddDate(0, 0, -weekday)
|
||||
}
|
||||
|
||||
func EndOfWeekByTime(t time.Time) time.Time {
|
||||
return BeginOfWeekByTime(t).AddDate(0, 0, 7).Add(-time.Nanosecond)
|
||||
}
|
||||
|
||||
func BeginTodayMillsecond() int64 {
|
||||
return BeginOfToday().UnixNano() / int64(time.Millisecond)
|
||||
}
|
||||
|
||||
// ServerCurrMillisecond 获得时间毫秒数 13位
|
||||
func ServerCurrMillisecond(t time.Time) int64 {
|
||||
return t.UnixNano() / 1000000
|
||||
}
|
||||
|
||||
func BeginWithTime(t time.Time) time.Time {
|
||||
year, month, day := t.Date()
|
||||
return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
|
||||
}
|
||||
|
||||
func EndWithTime(t time.Time) time.Time {
|
||||
year, month, day := t.Date()
|
||||
return time.Date(year, month, day, 23, 59, 59, 0, t.Location())
|
||||
}
|
||||
|
||||
func IsToday(t time.Time) bool {
|
||||
if t.IsZero() {
|
||||
return false
|
||||
}
|
||||
today := Now()
|
||||
|
||||
return t.Year() == today.Year() &&
|
||||
t.Month() == today.Month() &&
|
||||
t.Day() == today.Day()
|
||||
}
|
||||
|
||||
func IsSameYearAndMonth(t1, t2 time.Time) bool {
|
||||
return t1.Year() == t2.Year() &&
|
||||
t1.Month() == t2.Month()
|
||||
}
|
||||
|
||||
// 获取月的第一天
|
||||
func GetFirstDateOfMonth(d time.Time) time.Time {
|
||||
d = d.AddDate(0, 0, -d.Day()+1)
|
||||
return GetZeroTime(d)
|
||||
}
|
||||
|
||||
// 获取月的最后一天
|
||||
func GetLastDateOfMonth(d time.Time) time.Time {
|
||||
return GetFirstDateOfMonth(d).AddDate(0, 1, -1)
|
||||
}
|
||||
|
||||
// 获取周的星期一(星期一~星期日)
|
||||
func GetMondayOfWeek(d time.Time) time.Time {
|
||||
offset := (int(d.Weekday()) + 6) % 7
|
||||
d = d.AddDate(0, 0, -offset)
|
||||
return GetZeroTime(d)
|
||||
}
|
||||
|
||||
// 获取周的星期日(星期一~星期日)
|
||||
func GetSundayOfWeek(d time.Time) time.Time {
|
||||
offset := 6 - (int(d.Weekday())+6)%7
|
||||
d = d.AddDate(0, 0, offset)
|
||||
return GetZeroTime(d)
|
||||
}
|
||||
|
||||
// 获取某一天的0点时间
|
||||
func GetZeroTime(d time.Time) time.Time {
|
||||
return time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location())
|
||||
}
|
||||
|
||||
// 获取当前整点时间
|
||||
func GetWholeTime(d time.Time) time.Time {
|
||||
return time.Date(d.Year(), d.Month(), d.Day(), d.Hour(), 0, 0, 0, d.Location())
|
||||
}
|
||||
|
||||
func MustParseTime(s string) time.Time {
|
||||
layouts := []string{
|
||||
time.RFC3339,
|
||||
pubdef.DateISO,
|
||||
pubdef.TimeISO,
|
||||
}
|
||||
for _, layout := range layouts {
|
||||
t, err := time.ParseInLocation(layout, s, time.Local)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
return t
|
||||
}
|
||||
panic(fmt.Errorf("cannot parse %s as time.Time", s))
|
||||
}
|
||||
|
||||
// 查看两个时间差了多少天,差一秒都算一天
|
||||
func TimeSub(t1, t2 time.Time) int64 {
|
||||
t1 = time.Date(t1.Year(), t1.Month(), t1.Day(), 0, 0, 0, 0, time.Local)
|
||||
t2 = time.Date(t2.Year(), t2.Month(), t2.Day(), 0, 0, 0, 0, time.Local)
|
||||
|
||||
return int64(t1.Sub(t2).Hours() / 24)
|
||||
}
|
||||
func RecentMinute(tim time.Time, scaleMinute int64) time.Time {
|
||||
minute := int64(tim.Minute())
|
||||
lave := minute % scaleMinute
|
||||
formatMinute := minute - lave
|
||||
return time.Date(tim.Year(), tim.Month(), tim.Day(), tim.Hour(), int(formatMinute), 0, 0, tim.Location())
|
||||
}
|
||||
func GetTimeString(t time.Time) string {
|
||||
return t.In(LocBJ).Format(pubdef.TimeISO)
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_GetDayRangeByDate(t *testing.T) {
|
||||
begin, end, _ := GetDayRangeByDate("2022-04-08")
|
||||
fmt.Println(begin, end)
|
||||
}
|
||||
|
||||
func Test_GetDayRange(t *testing.T) {
|
||||
now := time.Now()
|
||||
fmt.Println(GetDayRange(now))
|
||||
}
|
||||
|
||||
func Test_GetDateString(t *testing.T) {
|
||||
fmt.Println(GetDateString(time.Now()))
|
||||
}
|
||||
|
||||
func Test_GetTodayDate(t *testing.T) {
|
||||
fmt.Println(GetTodayDate(time.Now()))
|
||||
}
|
||||
|
||||
func Test_GetYesterdayDate(t *testing.T) {
|
||||
fmt.Println(GetYesterdayDate(time.Now()))
|
||||
}
|
||||
|
||||
func Test_GetDayBeforeYesterdayDate(t *testing.T) {
|
||||
fmt.Println(GetDayBeforeYesterdayDate(time.Now()))
|
||||
}
|
||||
|
||||
func Test_BeginOfYear(t *testing.T) {
|
||||
got := BeginOfYear(time.Date(2026, 9, 12, 15, 4, 5, 0, LocBJ))
|
||||
want := time.Date(2026, 1, 1, 0, 0, 0, 0, LocBJ)
|
||||
if !got.Equal(want) {
|
||||
t.Fatalf("BeginOfYear = %v, want %v", got, want)
|
||||
}
|
||||
|
||||
// UTC 2025-12-31 16:30 已是北京时间 2026-01-01 00:30,应按 2026 年切。
|
||||
utc := time.Date(2025, 12, 31, 16, 30, 0, 0, time.UTC)
|
||||
if got = BeginOfYear(utc); !got.Equal(want) {
|
||||
t.Fatalf("BeginOfYear(UTC year boundary) = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_BeginOfMonthBeijing(t *testing.T) {
|
||||
// UTC 2026-08-31 16:30 已是北京时间 2026-09-01 00:30,应按 9 月切。
|
||||
utc := time.Date(2026, 8, 31, 16, 30, 0, 0, time.UTC)
|
||||
want := time.Date(2026, 9, 1, 0, 0, 0, 0, LocBJ)
|
||||
if got := BeginOfMonth(utc); !got.Equal(want) {
|
||||
t.Fatalf("BeginOfMonth(UTC month boundary) = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_BeginOfWeekByTime(t *testing.T) {
|
||||
{
|
||||
// 如果当前是周日,获取到的结果应该是这周周一而不是下周周一
|
||||
now := time.Date(2022, 4, 3, 1, 0, 0, 0, time.Local)
|
||||
actual := BeginOfWeekByTime(now)
|
||||
expect := time.Date(2022, 3, 28, 0, 0, 0, 0, time.Local)
|
||||
if !reflect.DeepEqual(expect, actual) {
|
||||
t.Fatalf("fail")
|
||||
}
|
||||
}
|
||||
{
|
||||
now := time.Date(2022, 3, 28, 0, 0, 0, 0, time.Local)
|
||||
actual := BeginOfWeekByTime(now)
|
||||
expect := time.Date(2022, 3, 28, 0, 0, 0, 0, time.Local)
|
||||
if !reflect.DeepEqual(expect, actual) {
|
||||
t.Fatalf("fail")
|
||||
}
|
||||
}
|
||||
{
|
||||
// UTC 2026-09-06 16:30 已是北京时间周一 00:30,应按本周一切。
|
||||
utc := time.Date(2026, 9, 6, 16, 30, 0, 0, time.UTC)
|
||||
want := time.Date(2026, 9, 7, 0, 0, 0, 0, LocBJ)
|
||||
if got := BeginOfWeekByTime(utc); !got.Equal(want) {
|
||||
t.Fatalf("BeginOfWeekByTime(UTC week boundary) = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
{
|
||||
// UTC 2026-09-06 15:30 仍是北京时间周日 23:30,应落在上一周周一。
|
||||
utc := time.Date(2026, 9, 6, 15, 30, 0, 0, time.UTC)
|
||||
want := time.Date(2026, 8, 31, 0, 0, 0, 0, LocBJ)
|
||||
if got := BeginOfWeekByTime(utc); !got.Equal(want) {
|
||||
t.Fatalf("BeginOfWeekByTime(UTC sunday) = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetDayEnd(t *testing.T) {
|
||||
fmt.Println(GetDayEnd("2020-04-09"))
|
||||
}
|
||||
|
||||
func Test_GetDayBegin(t *testing.T) {
|
||||
fmt.Println(GetDayBegin("2020-04-09"))
|
||||
}
|
||||
|
||||
func Test_EndOfWeekByTime(t *testing.T) {
|
||||
now := time.Now()
|
||||
fmt.Println(EndOfWeekByTime(now))
|
||||
}
|
||||
|
||||
func Test_EndOfMonth(t *testing.T) {
|
||||
now := time.Now()
|
||||
fmt.Println(BeginOfMonth(now))
|
||||
fmt.Println(EndOfMonth(now))
|
||||
}
|
||||
|
||||
func Test_WeekDateRange(t *testing.T) {
|
||||
fmt.Println(WeekDateRange(time.Now()))
|
||||
}
|
||||
|
||||
func Test_MonthDateRange(t *testing.T) {
|
||||
fmt.Println(MonthDateRange(time.Now()))
|
||||
}
|
||||
|
||||
func Test_EndOfLastWeek(t *testing.T) {
|
||||
fmt.Println(EndOfLastWeek(time.Now()))
|
||||
}
|
||||
|
||||
func Test_EndOfLastMonth(t *testing.T) {
|
||||
fmt.Println(EndOfLastMonth(time.Now()))
|
||||
}
|
||||
|
||||
func Test_EndOfLastWeekDate(t *testing.T) {
|
||||
fmt.Println(EndOfLastWeekDate(time.Now()))
|
||||
}
|
||||
|
||||
func Test_EndOfLastMonthDate(t *testing.T) {
|
||||
fmt.Println(EndOfLastMonthDate(time.Now()))
|
||||
}
|
||||
|
||||
func TestMustParseTime(t *testing.T) {
|
||||
type args struct {
|
||||
s string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want time.Time
|
||||
}{
|
||||
{
|
||||
name: "case1",
|
||||
args: args{
|
||||
s: "2022-04-25",
|
||||
},
|
||||
want: time.Date(2022, 4, 25, 0, 0, 0, 0, time.Local),
|
||||
},
|
||||
{
|
||||
name: "case2",
|
||||
args: args{
|
||||
s: "2022-04-25 11:12:13",
|
||||
},
|
||||
want: time.Date(2022, 4, 25, 11, 12, 13, 0, time.Local),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := MustParseTime(tt.args.s); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("MustParseTime() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"videoweb/pub/common/enum/consts"
|
||||
)
|
||||
|
||||
func UASplit(useragent string) (phoneModel string, phoneSys string, appVer string) {
|
||||
if useragent == "" {
|
||||
return
|
||||
}
|
||||
infos := strings.Split(useragent, ";")
|
||||
if len(infos) != 3 {
|
||||
return
|
||||
}
|
||||
phoneinfos := strings.Split(strings.Trim(infos[1], " "), " ")
|
||||
if len(phoneinfos) == 2 {
|
||||
phoneModel = strings.Trim(phoneinfos[0], " ")
|
||||
phoneSys = strings.Trim(phoneinfos[1], " ")
|
||||
}
|
||||
appVer = strings.Trim(infos[2], " ")
|
||||
return
|
||||
}
|
||||
|
||||
func UADeviceType(ua string) string {
|
||||
if strings.Contains(ua, "iPhone") {
|
||||
return consts.IOS
|
||||
}
|
||||
return consts.Android
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
"videoweb/pub/common/log"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
func RecoverError(onError func(err error)) {
|
||||
if r := recover(); r != nil {
|
||||
err := fmt.Errorf("internal error: %v", r)
|
||||
if onError != nil {
|
||||
onError(err)
|
||||
}
|
||||
log.Error(fmt.Sprintf("[Panic] RecoverError err: %+v\n", r) + string(debug.Stack()))
|
||||
}
|
||||
}
|
||||
|
||||
func GetRandom(min, max int) int64 {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
return int64(rand.Intn(max-min+1) + min)
|
||||
}
|
||||
func EnsureRange[T constraints.Ordered](value, min, max T) T {
|
||||
if value < min {
|
||||
value = min
|
||||
}
|
||||
if value > max {
|
||||
value = max
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func InRange[T constraints.Ordered](value, min, max T) bool {
|
||||
if value < min {
|
||||
return false
|
||||
}
|
||||
if value > max {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Shuffle 使用 Fisher-Yates 算法对切片进行随机排序
|
||||
func Shuffle[T any](slice []T) {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
n := len(slice)
|
||||
for i := n - 1; i > 0; i-- {
|
||||
j := rand.Intn(i + 1)
|
||||
slice[i], slice[j] = slice[j], slice[i]
|
||||
}
|
||||
}
|
||||
func GetRandomMacAddress() string {
|
||||
buf := make([]byte, 6)
|
||||
rand.Read(buf)
|
||||
buf[0] |= 2
|
||||
mac := fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5])
|
||||
return strings.ToUpper(mac)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package utils
|
||||
|
||||
import "regexp"
|
||||
|
||||
// mobile verify
|
||||
func VerifyMobileFormat(mobileNum string) bool {
|
||||
regular := "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}$"
|
||||
reg := regexp.MustCompile(regular)
|
||||
return reg.MatchString(mobileNum)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getSubVersions(ver string) []int {
|
||||
var vers [4]int
|
||||
vv := strings.Split(ver, ".")
|
||||
for i, v := range vv {
|
||||
vers[i], _ = strconv.Atoi(v)
|
||||
}
|
||||
return vers[:]
|
||||
}
|
||||
|
||||
// VersionCompare
|
||||
func VersionCompare(a, b string) int {
|
||||
|
||||
aa := getSubVersions(a)
|
||||
bb := getSubVersions(b)
|
||||
|
||||
for i := 0; i < len(aa) && i < len(bb); i++ {
|
||||
|
||||
if aa[i] == bb[i] {
|
||||
continue
|
||||
}
|
||||
|
||||
if aa[i] < bb[i] {
|
||||
return -1
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
if len(aa) == len(bb) {
|
||||
return 0
|
||||
}
|
||||
if len(aa) < len(bb) {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVersionCompare(t *testing.T) {
|
||||
|
||||
fmt.Printf("2.10.9 vs 2.11 = %d\n", VersionCompare("2.10.9", "2.11"))
|
||||
fmt.Printf("2.11.9 vs 2.11 = %d\n", VersionCompare("2.11.9", "2.11"))
|
||||
fmt.Printf("1.10.9 vs 2.11 = %d\n", VersionCompare("1.10.9", "2.11"))
|
||||
fmt.Printf("2.10.9 vs 2.10.1 = %d\n", VersionCompare("2.10.9", "2.11.1"))
|
||||
fmt.Printf("2.10.9 vs 2.10.10 = %d\n", VersionCompare("2.10.9", "2.10.10"))
|
||||
fmt.Printf("2.10.9 vs 2.10.9.0 = %d\n", VersionCompare("2.10.9", "2.10.9.0"))
|
||||
fmt.Printf("1 vs 1.0.0.0 = %d\n", VersionCompare("2.10.9", "2.10.9.0"))
|
||||
fmt.Printf("2.10.9 vs 2.10.9.-1 = %d\n", VersionCompare("2.10.9", "2.10.9.-1"))
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user