chore: 初始开源发布
iotlcm-admin-svr:管理后台服务。 - Go module:iotlcm → git.5iot.com/iotlcm-public/iotlcm-admin-svr(729 文件) - 凭据与地址脱敏:JWT signingKey(含代码里的 MustGet 默认值)、 MySQL DSN、Redis 口令、告警邮件发件人邮箱全部改为占位; 私网 IP → 文档地址段;库名 xiujie_admin → iotlcm - 真实手机号/邮箱 → 合法 11 位编造号 / example.com - 移除内部资产:CI 触发器、过时 DDL(sql/db_iotlcm.sql)、 私有依赖 go get 行、明文 registry 登录口令 - 启动 banner 统一 IOTLCM 方块字 + 社区惯例署名 - Dockerfile 改用公共基础镜像 - 拼错的 RADEME.md → README.md,README 重写 - 需要真实数据库的用例改为可用 IOTLCM_TEST_WITH_DB=1 显式启用 - 补 Apache-2.0 LICENSE,LF 行尾 + .gitattributes 数据库 schema 不随本仓发布(生产 DDL 已过时),README 已说明。
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
// package utility
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package utility
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/crypto/gmd5"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/mileusna/useragent"
|
||||
"golang.org/x/exp/rand"
|
||||
)
|
||||
|
||||
func Md5(str string) string {
|
||||
return gmd5.MustEncryptString(str)
|
||||
}
|
||||
|
||||
// 密码加密
|
||||
func PasswordEncrypt(password string, salt string) string {
|
||||
return Md5(password + salt)
|
||||
}
|
||||
|
||||
// 解析时间
|
||||
func ParseDuration(d string) (time.Duration, error) {
|
||||
d = strings.TrimSpace(d)
|
||||
dr, err := time.ParseDuration(d)
|
||||
if err == nil {
|
||||
return dr, nil
|
||||
}
|
||||
if strings.Contains(d, "d") {
|
||||
index := strings.Index(d, "d")
|
||||
|
||||
hour, _ := strconv.Atoi(d[:index])
|
||||
dr = time.Hour * 24 * time.Duration(hour)
|
||||
ndr, err := time.ParseDuration(d[index+1:])
|
||||
if err != nil {
|
||||
return dr, nil
|
||||
}
|
||||
return dr + ndr, nil
|
||||
}
|
||||
|
||||
dv, err := strconv.ParseInt(d, 10, 64)
|
||||
return time.Duration(dv), err
|
||||
}
|
||||
|
||||
// 随机生成5位字符
|
||||
func RandomString(n int) string {
|
||||
letters := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = letters[rand.Intn(len(letters))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// 获取客户端IP
|
||||
func GetClientIp(ctx context.Context) string {
|
||||
if ctx == nil {
|
||||
return ""
|
||||
}
|
||||
if ghttp.RequestFromCtx(ctx) == nil {
|
||||
return ""
|
||||
}
|
||||
return ghttp.RequestFromCtx(ctx).GetClientIp()
|
||||
}
|
||||
|
||||
// 获取user-agent
|
||||
func GetUserAgent(ctx context.Context) string {
|
||||
return ghttp.RequestFromCtx(ctx).Header.Get("User-Agent")
|
||||
}
|
||||
|
||||
// 获取操作系统
|
||||
func GetOs(ctx context.Context) string {
|
||||
userAgent := GetUserAgent(ctx)
|
||||
ua := useragent.Parse(userAgent)
|
||||
return ua.OS
|
||||
}
|
||||
|
||||
// 获取浏览器
|
||||
func GetBrowser(ctx context.Context) string {
|
||||
userAgent := GetUserAgent(ctx)
|
||||
ua := useragent.Parse(userAgent)
|
||||
return ua.Name
|
||||
}
|
||||
|
||||
// 获取ip所属城市
|
||||
func GetCityByIp(ip string) string {
|
||||
parsed := net.ParseIP(strings.Trim(strings.TrimSpace(ip), "[]"))
|
||||
if parsed == nil {
|
||||
return ""
|
||||
}
|
||||
if parsed.IsPrivate() || parsed.IsLoopback() || parsed.IsLinkLocalUnicast() || parsed.IsLinkLocalMulticast() || parsed.IsUnspecified() {
|
||||
return "内网IP"
|
||||
}
|
||||
return cityLookup.Get(parsed.String())
|
||||
}
|
||||
|
||||
// 时间戳转 yyyy-MM-dd HH:mm:ss
|
||||
func TimeStampToDateTime(timeStamp int64) string {
|
||||
tm := gtime.NewFromTimeStamp(timeStamp)
|
||||
return tm.Format("Y-m-d H:i:s")
|
||||
}
|
||||
|
||||
// 时间戳转 yyyy-MM-dd
|
||||
func TimeStampToDate(timeStamp int64) string {
|
||||
tm := gtime.NewFromTimeStamp(timeStamp)
|
||||
return tm.Format("Y-m-d")
|
||||
}
|
||||
|
||||
// 服务端ip
|
||||
func GetLocalIP() (ip string, err error) {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
ipAddr, ok := addr.(*net.IPNet)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if ipAddr.IP.IsLoopback() {
|
||||
continue
|
||||
}
|
||||
if !ipAddr.IP.IsGlobalUnicast() {
|
||||
continue
|
||||
}
|
||||
return ipAddr.IP.String(), nil
|
||||
}
|
||||
return
|
||||
}
|
||||
func GetPublicIP() (ip string, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
return lookupPublicIP(ctx)
|
||||
}
|
||||
|
||||
// 获取两个字符串之间的字符串
|
||||
func GetBetweenStr(str, start, end string) (result string) {
|
||||
startIndex := strings.Index(str, start)
|
||||
if startIndex == -1 {
|
||||
return ""
|
||||
}
|
||||
// 更新startIndex为开始字符串之后的位置
|
||||
startIndex += len(start)
|
||||
|
||||
// 找到结束字符串的位置,从更新后的startIndex开始查找
|
||||
endIndex := strings.Index(str[startIndex:], end)
|
||||
if endIndex == -1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// 切片操作获取中间的字符串
|
||||
result = str[startIndex : startIndex+endIndex]
|
||||
return
|
||||
}
|
||||
|
||||
// A和B两个数组,A数组中存在,B数组中不存在
|
||||
func ArrayRightDiff(a, b []int64) []int64 {
|
||||
diff := make([]int64, 0)
|
||||
for _, v := range a {
|
||||
if !slices.Contains(b, v) {
|
||||
diff = append(diff, v)
|
||||
}
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
type fileInfo struct {
|
||||
name string
|
||||
size int64
|
||||
}
|
||||
|
||||
// WalkDir 获取目录下文件的名称和大小
|
||||
func WalkDir(dirname string) ([]fileInfo, error) {
|
||||
var fileInfos []fileInfo
|
||||
err := filepath.Walk(dirname, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
fileInfos = append(fileInfos, fileInfo{name: path, size: info.Size()})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return fileInfos, err
|
||||
}
|
||||
func DirSize(dirname string) string {
|
||||
var (
|
||||
s int64
|
||||
files, _ = WalkDir(dirname)
|
||||
)
|
||||
for _, n := range files {
|
||||
s += n.size
|
||||
}
|
||||
return FileSize(s)
|
||||
}
|
||||
func MergeAbs(path string, fileName ...string) string {
|
||||
var paths = []string{gfile.RealPath(path)}
|
||||
paths = append(paths, fileName...)
|
||||
return gfile.Join(paths...)
|
||||
}
|
||||
|
||||
// FileSize 字节的单位转换 保留两位小数
|
||||
func FileSize(fileSize int64) string {
|
||||
units := []string{"B", "KB", "MB", "GB", "TB", "EB"}
|
||||
var size = float64(fileSize)
|
||||
var i int
|
||||
for i = 0; size > 1024; i++ {
|
||||
size /= 1024
|
||||
}
|
||||
return fmt.Sprintf("%.2f %s", size, units[i])
|
||||
}
|
||||
|
||||
// UniqueSlice 切片去重
|
||||
func UniqueSlice[K comparable](languages []K) []K {
|
||||
result := make([]K, 0, len(languages))
|
||||
temp := map[K]struct{}{}
|
||||
for _, item := range languages {
|
||||
if _, ok := temp[item]; !ok {
|
||||
temp[item] = struct{}{}
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user