This commit is contained in:
2025-07-07 20:11:59 +08:00
parent ab0fdbc447
commit 06e3aa2eb3
2009 changed files with 193082 additions and 0 deletions

View File

@ -0,0 +1,491 @@
package coryCommon
import (
"encoding/json"
"errors"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/gclient"
"github.com/gogf/gf/v2/os/gctx"
"github.com/tiger1103/gfast-cache/cache"
commonService "github.com/tiger1103/gfast/v3/internal/app/common/service"
"github.com/tiger1103/gfast/v3/library/liberr"
tool "github.com/tiger1103/gfast/v3/utility/coryUtils"
"golang.org/x/net/context"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
)
/**
功能:
文字识别百度API识别身份证、银行卡内容、人脸检测、人脸对比
*/
// OcrReq 请求体参数 身份证和银行卡都是此结构体,
type OcrReq struct {
Image string `json:"image"` // 二选一图像数据base64编码后进行urlencode需去掉编码头data:image/jpeg;base64, 要求base64编码和urlencode后大小不超过4M最短边至少15px最长边最大4096px,支持jpg/jpeg/png/bmp格式
Url string `json:"url"` // 二选一图片完整URLURL长度不超过1024字节URL对应的图片base64编码后大小不超过4M最短边至少15px最长边最大4096px,支持jpg/jpeg/png/bmp格式当image字段存在时url字段失效 请注意关闭URL防盗链
IdCardSide string `json:"id_card_side"` // 身份证需要此参数人头面填写front国徽面填写back 银行卡不需要
DetectPhoto bool `json:"detect_photo"` // 是否检测身份证进行裁剪默认不检测。可选值true-检测身份证并返回证照的 base64 编码及位置信息
}
/*
1、clientId 必须参数应用的APIKey
2、clientSecret 必须参数应用的Secret Key
3、存在redis的数据前缀
*/
var clientId = ""
var clientSecret = ""
var cacheRedis = "zmGoBaiDuOcrAccessToken"
func init() {
one, err := g.Cfg().Get(gctx.New(), "baiDuYun.clientId")
if err != nil {
fmt.Println("百度云API未找到")
}
two, err := g.Cfg().Get(gctx.New(), "baiDuYun.clientSecret")
if err != nil {
fmt.Println("百度云Secret未找到")
}
clientId = one.String()
clientSecret = two.String()
}
/*
IDCardInfo 获取身份证相关数据
*/
type IDCardInfo struct {
WordsResult WordsResult `json:"words_result"`
WordsResultNum int `json:"words_result_num"`
IDCardNumberType int `json:"idcard_number_type"`
ImageStatus string `json:"image_status"`
LogID int64 `json:"log_id"`
Photo string `json:"photo"`
}
type WordsResult struct {
Name Field `json:"姓名"`
Nation Field `json:"民族"`
Address Field `json:"住址"`
CitizenIdentification Field `json:"公民身份号码"`
Birth Field `json:"出生"`
Gender Field `json:"性别"`
ExpirationDate Field `json:"失效日期"`
IssuingAuthority Field `json:"签发机关"`
IssueDate Field `json:"签发日期"`
}
type Field struct {
Location Location `json:"location"`
Words string `json:"words"`
}
type Location struct {
Top int `json:"top"`
Left int `json:"left"`
Width int `json:"width"`
Height int `json:"height"`
}
func ImgOCR(vr OcrReq) (m map[string]interface{}) {
//请求路径+token
baseUrl := "https://aip.baidubce.com/rest/2.0/ocr/v1/idcard"
//先从缓存里面捞取token,如果没得就重新获取token
var atStr = redisCacheStr()
// 构造 URL 参数
params := url.Values{}
params.Set("access_token", atStr)
// 构造完整的请求 URL
requestURL := fmt.Sprintf("%s?%s", baseUrl, params.Encode())
response, err := gclient.New().ContentJson().ContentType("application/x-www-form-urlencoded").Post(gctx.New(), requestURL, vr)
if err != nil {
return
}
var dataInfo = strings.ReplaceAll(response.ReadAllString(), " ", "")
//解析数据
bodyData := []byte(dataInfo)
var idCardInfo IDCardInfo
err = json.Unmarshal(bodyData, &idCardInfo)
if err != nil {
fmt.Println("Failed to parse JSON data:", err)
return
}
m = make(map[string]interface{})
//身份证正反面颠倒了,直接返回空
if idCardInfo.ImageStatus == "reversed_side" {
return
}
result := idCardInfo.WordsResult
//if idCardInfo.Photo != "" {
// m["pacePhoto"] = idCardInfo.Photo
//}
if result.Name.Words != "" {
m["userName"] = result.Name.Words
}
if result.Nation.Words != "" {
m["sfzNation"] = result.Nation.Words
}
if result.Address.Words != "" {
m["sfzSite"] = result.Address.Words
}
if result.CitizenIdentification.Words != "" {
m["sfzNumber"] = result.CitizenIdentification.Words
}
if result.Birth.Words != "" {
str, _ := tool.New().TimeCycle(result.Birth.Words)
m["sfzBirth"] = str
}
if result.Gender.Words != "" {
var se = result.Gender.Words
if se == "男" {
se = "1"
} else if se == "女" {
se = "2"
} else {
se = "3"
}
m["sex"] = se
}
if result.ExpirationDate.Words != "" {
str, err := tool.New().TimeCycle(result.ExpirationDate.Words)
if err == nil {
m["sfzEnd"] = str
} else {
str := result.ExpirationDate.Words
m["sfzEnd"] = str
}
}
if result.IssuingAuthority.Words != "" {
m["IssuingAuthority"] = result.IssuingAuthority.Words
}
if result.IssueDate.Words != "" {
str, _ := tool.New().TimeCycle(result.IssueDate.Words)
m["sfzStart"] = str
}
return m
}
/*
BankData 获取银行卡相关数据
针对卡号、有效期、发卡行、卡片类型、持卡人5个关键字段进行结构化识别识别准确率超过99%
*/
type BankData struct {
ValidDate string `json:"valid_date"` //有效期
BankCardNumber string `json:"bank_card_number"` //银行卡卡号
BankName string `json:"bank_name"` //银行名,不能识别时为空
BankCardType int `json:"bank_card_type"` //银行卡类型0不能识别; 1借记卡; 2贷记卡原信用卡大部分为贷记卡; 3准贷记卡; 4预付费卡
HolderName string `json:"holder_name"` //持卡人姓名,不能识别时为空
}
type Result struct {
Res BankData `json:"result"` //具体数据
Direction int `json:"direction"` //图像方向。 - - 1未定义; - 0正向; - 1逆时针90度; - 2逆时针180度; - 3逆时针270度
LogID int64 `json:"log_id"` //请求标识码,随机数,唯一。
}
func ImgYhkOCR(vr OcrReq) (m map[string]interface{}) {
m = make(map[string]interface{})
//请求路径+token
baseUrl := "https://aip.baidubce.com/rest/2.0/ocr/v1/bankcard"
//先从缓存里面捞取token
var atStr = redisCacheStr()
// 构造 URL 参数
params := url.Values{}
params.Set("access_token", atStr)
// 构造完整的请求 URL
requestURL := fmt.Sprintf("%s?%s", baseUrl, params.Encode())
response, err := gclient.New().ContentJson().ContentType("application/x-www-form-urlencoded").Post(gctx.New(), requestURL, vr)
if err != nil {
return
}
//解析数据
allString := response.ReadAllString()
bodyData := []byte(allString)
var result Result
err = json.Unmarshal(bodyData, &result)
if err != nil {
fmt.Println("Failed to parse JSON data:", err)
return
}
if result.Res.ValidDate != "" {
m["ValidDate"] = result.Res.ValidDate
}
if result.Res.BankCardNumber != "" {
m["yhkNumber"] = strings.ReplaceAll(result.Res.BankCardNumber, " ", "")
}
if result.Res.BankName != "" {
m["yhkOpeningBank"] = result.Res.BankName
}
if result.Res.BankCardType >= 0 {
m["BankCardType"] = result.Res.BankCardType
}
if result.Res.HolderName != "" {
m["yhkCardholder"] = result.Res.HolderName
}
return m
}
/*
HumanFaceReq 请求参数 人脸识别+人脸检测
*/
type HumanFaceReq struct {
Image string `json:"image"` //图片此处填写base64
ImageType string `json:"image_type" gf:"default:BASE64" ` //图片类型-BASE64-URL-BASE64此封装固定用base64
FaceField string `json:"face_field" gf:"default:face_type,quality"` //包括age,expression,face_shape,gender,glasses,landmark,landmark150, quality,eye_status,emotion,face_type,mask,spoofing信息 逗号分隔. 默认只返回face_token、人脸框、概率和旋转角度
}
/*
HumanFaceRep 返回参数 人脸识别
*/
type HumanFaceRep struct {
ErrorCode int `json:"error_code"`
ErrorMsg string `json:"error_msg"`
LogId int `json:"log_id"`
Timestamp int `json:"timestamp"`
Cached int `json:"cached"`
Result struct {
FaceNum int `json:"face_num"`
FaceList []struct {
FaceToken string `json:"face_token"`
Location struct {
Left float64 `json:"left"`
Top float64 `json:"top"`
Width int `json:"width"`
Height int `json:"height"`
Rotation int `json:"rotation"`
} `json:"location"`
FaceProbability float64 `json:"face_probability"`
Angle struct {
Yaw float64 `json:"yaw"`
Pitch float64 `json:"pitch"`
Roll float64 `json:"roll"`
} `json:"angle"`
FaceType struct {
Type string `json:"type"`
Probability float64 `json:"probability"`
} `json:"face_type"`
Quality struct {
Occlusion struct {
LeftEye float64 `json:"left_eye"`
RightEye float64 `json:"right_eye"`
Nose float64 `json:"nose"`
Mouth float64 `json:"mouth"`
LeftCheek float64 `json:"left_cheek"`
RightCheek float64 `json:"right_cheek"`
ChinContour float64 `json:"chin_contour"`
} `json:"occlusion"`
Blur float64 `json:"blur"`
Illumination float64 `json:"illumination"`
Completeness int64 `json:"completeness"`
} `json:"quality"`
} `json:"face_list"`
} `json:"result"`
}
func HumanFace(hf *HumanFaceReq) (err error) {
ctx := gctx.New()
err = g.Try(ctx, func(ctx context.Context) {
err = nil
//1、请求地址+token
url := "https://aip.baidubce.com/rest/2.0/face/v3/detect?access_token=" + redisCacheStr()
marshal, err := json.Marshal(hf)
if err != nil {
liberr.ErrIsNil(ctx, err)
return
}
//3、准备请求
payload := strings.NewReader(string(marshal))
client := &http.Client{}
req, err := http.NewRequest("POST", url, payload)
if err != nil {
liberr.ErrIsNil(ctx, err)
return
}
//4、设置请求头
req.Header.Add("Content-Type", "application/json")
//5、发送请求
res, err := client.Do(req)
if err != nil {
liberr.ErrIsNil(ctx, err)
return
}
defer res.Body.Close()
//6、返回数据
body, err := io.ReadAll(res.Body)
if err != nil {
liberr.ErrIsNil(ctx, err)
return
}
var aaa = body
//解析数据
var result HumanFaceRep
err = json.Unmarshal(aaa, &result)
if err != nil {
liberr.ErrIsNil(ctx, err)
return
}
if result.ErrorCode != 0 {
if result.ErrorMsg == "pic not has face" {
err = errors.New("这张照片没有人脸")
liberr.ErrIsNil(ctx, err)
} else {
err = errors.New(result.ErrorMsg)
liberr.ErrIsNil(ctx, err)
}
return
}
//1、人脸置信度范围【0~1】代表这是一张人脸的概率0最小、1最大。其中返回0或1时数据类型为Integer
dataInfo := result.Result.FaceList[0]
reliabilityOne := dataInfo.FaceProbability
if reliabilityOne != 1.0 {
err = errors.New("识别不清晰!")
}
//2、判断是否真是人脸 human: 真实人脸 cartoon: 卡通人脸
reliabilityTwo := dataInfo.FaceType.Type
if reliabilityTwo == "cartoon" {
err = errors.New("请传入真实人脸!")
} else {
//判断可信度 置信度范围0~1
reliabilityThree := dataInfo.FaceType.Probability
if reliabilityThree < 0.8 {
err = errors.New("请勿化妆太过夸张!")
}
}
//3、人脸模糊程度范围[0~1]0表示清晰1表示模糊
reliabilityFour := dataInfo.Quality.Blur
if reliabilityFour >= 0.1 {
err = errors.New("人脸过于模糊!")
}
//4、光线太暗 0~255 值越大光线越好
reliabilityFive := dataInfo.Quality.Illumination
if reliabilityFive < 80.0 {
err = errors.New("光线太暗!")
}
//5、人脸是否完整 1完整 0不完整
reliabilitySix := dataInfo.Quality.Completeness
if reliabilitySix != 1 {
err = errors.New("请确定人脸在图框内!")
}
return
})
return
}
// ComparisonRep 人脸检测返回数据
type ComparisonRep struct {
ErrorCode int `json:"error_code"`
ErrorMsg string `json:"error_msg"`
LogId int `json:"log_id"`
Timestamp int `json:"timestamp"`
Cached int `json:"cached"`
Result struct {
Score float64 `json:"score"` //人脸相似度得分推荐阈值80分
FaceList []struct {
FaceToken string `json:"face_token"`
} `json:"face_list"`
} `json:"result"`
}
func Comparison(arrObject []*HumanFaceReq) (score float64, err error) {
//1、请求地址+token
url := "https://aip.baidubce.com/rest/2.0/face/v3/match?access_token=" + redisCacheStr()
//2、请求参数转字符串json
marshal, err := json.Marshal(arrObject)
if err != nil {
return
}
payload := strings.NewReader(string(marshal))
//3、准备post请求
client := &http.Client{}
req, err := http.NewRequest("POST", url, payload)
if err != nil {
fmt.Println(err)
return
}
//4、设置请求头
req.Header.Add("Content-Type", "application/json")
//5、发送请求关闭连接
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer res.Body.Close()
//6、数据结果
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
return
}
//7、解析数据
var result ComparisonRep
err = json.Unmarshal(body, &result)
if err != nil {
fmt.Println("Failed to parse JSON data:", err)
return
}
score = result.Result.Score
return score, err
}
/*
AccessTokenResponse 获取Access_token,有效期(秒为单位有效期30天)
*/
type AccessTokenResponse struct {
AccessToken string `json:"access_token"`
}
func AccessTokenFunc() (str string) {
url := "https://aip.baidubce.com/oauth/2.0/token?client_id=" + clientId + "&client_secret=" + clientSecret + "&grant_type=client_credentials"
payload := strings.NewReader(``)
client := &http.Client{}
req, err := http.NewRequest("POST", url, payload)
if err != nil {
fmt.Println(err)
return
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
var tokenResponse AccessTokenResponse
json.Unmarshal(body, &tokenResponse)
if err != nil {
fmt.Println(err)
return
}
return tokenResponse.AccessToken
}
// 缓存捞取token,如果没得就重新获取token
func redisCacheStr() (atStr string) {
atStr = ""
ctx := gctx.New()
prefix := g.Cfg().MustGet(ctx, "system.cache.prefix").String()
gfCache := cache.New(prefix)
at := commonService.Cache().Get(ctx, gfCache.CachePrefix+cacheRedis)
if at == nil || at.String() == "" {
atStr = AccessTokenFunc()
//存储到redis时间为29天
commonService.Cache().Set(ctx, cacheRedis, atStr, time.Hour*24*29)
} else {
atStr = at.String()
}
//atStr = "24.c8814d3fc7961820f0e23ee9d80cf96c.2592000.1696671067.282335-38777216"
return atStr
}

View File

@ -0,0 +1,104 @@
package coryCommon
import (
"encoding/base64"
"errors"
"fmt"
"github.com/gogf/gf/v2/os/gfile"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"strings"
"time"
)
// Base64ToImgFunc 将base64转成图片保存在本地
func Base64ToImgFunc(base64Str string, numTyoe string, cdPath string) (outputPath string, err error) {
// 获取当前时间+随机数得到文件名
currentTime := time.Now()
timestamp := currentTime.UnixNano() / int64(time.Millisecond)
randomNum := rand.Intn(1000)
uniqueFileName := fmt.Sprintf("%d_%d", timestamp, randomNum)
// 用户指定的本地文件路径
//ynr := Ynr(Portrait + "/")
ynr := Ynr(cdPath + "/")
path := ynr + uniqueFileName + ".png"
path = filepath.ToSlash(path)
// Base64编码的图像字符串
b64 := "data:image/png;"
base64Image := ""
if strings.Contains(base64Str, "base64,") { // 判断是否有【base64,】如果有就替换
base64Image = strings.Replace(base64Str, base64Str[:strings.Index(base64Str, "base64,")], b64, 1)
} else {
base64Image = b64 + "base64," + base64Str
}
// 调用函数将Base64图像保存到指定路径
err = SaveBase64ImageToFile(base64Image, path)
if err != nil {
return
} else {
if numTyoe == "1" {
outputPath = strings.Replace(path, "resource/public", "file", 1)
return
} else if numTyoe == "2" {
outputPath = strings.Replace(path, "resource/public", "wxfile", 1)
return
} else {
err = errors.New("第二参数只能为1 or 2")
return
}
}
}
func Base64ToFileFunc(base64Str string, filePath string, suffix string, numTyoe string) (outputPath string, err error) {
// 获取当前时间+随机数得到文件名
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
uniqueFileName := fmt.Sprintf("%d_%d", timestamp, rand.Intn(1000))
// 用户指定的本地文件路径,filePath路径最后必须是/
path := filepath.ToSlash(filePath + uniqueFileName + suffix)
// 调用函数将Base64图像保存到指定路径
err = SaveBase64ImageToFile(base64Str, path)
if err != nil {
return
} else {
if numTyoe == "1" {
outputPath = strings.Replace(path, "resource/public", "file", 1)
return
} else if numTyoe == "2" {
outputPath = strings.Replace(path, "resource/public", "wxfile", 1)
return
} else {
err = errors.New("第二参数只能为1 or 2")
return
}
}
}
// SaveBase64ImageToFile 将Base64编码的图像保存到指定的本地文件路径
func SaveBase64ImageToFile(base64Image string, outputPath string) error {
if len(outputPath) > 0 && outputPath[0] == '/' {
outputPath = outputPath[1:]
}
getwd, _ := os.Getwd()
outputPath = gfile.Join(getwd, outputPath)
outputPath = strings.ReplaceAll(outputPath, "\\", "/")
// 1. 解码Base64字符串
parts := strings.Split(base64Image, ",")
if len(parts) != 2 {
return errors.New("Base64字符串格式不正确")
}
data, err := base64.StdEncoding.DecodeString(parts[1])
if err != nil {
return errors.New("转码错误!")
}
// 2. 将字节数组保存为图像文件
err = ioutil.WriteFile(outputPath, data, 0644)
if err != nil {
return errors.New("报错图像失败!")
}
return nil
}

View File

@ -0,0 +1,107 @@
package coryCommon
import (
"context"
"errors"
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
"reflect"
"strconv"
)
func New() *coryCom {
return &coryCom{}
}
type coryCom struct{}
// CreateByOrUpdateBy 专门用来反射结构体中的创建人和更新人,然后返回回去,避免重复造轮子去写重复代码
/**
*使用实例代码
* by := coryCommon.New().CreateByOrUpdateBy(ctx, res)
* infoRes := by.(model.BusCompanyInfoRes)
* res = &infoRes
*
*其中 updateByFieldVal.Set(reflect.ValueOf(updateByValue.Interface().(*gvar.Var).String())) 必须类型一致
*/
func (s *coryCom) CreateByOrUpdateBy(ctx context.Context, data interface{}) (dataRes interface{}) {
// 使用反射获取 data 的值和类型信息
val := reflect.ValueOf(data)
typ := reflect.TypeOf(data)
// 判断 data 是否为指针类型,并获取实际值
if val.Kind() == reflect.Ptr {
val = val.Elem()
typ = typ.Elem()
}
flagCreate := true
flagUpdate := true
// 获取 createBy 字段在结构体中的索引
createByField, ok := typ.FieldByName("CreateBy")
if !ok {
flagCreate = false
//return data // 如果结构体中不存在 createBy 字段,则直接返回原始值
createByField2, ok2 := typ.FieldByName("CreatedBy")
if ok2 {
createByField = createByField2
flagCreate = true
}
}
updateByField, ok := typ.FieldByName("UpdateBy")
if !ok {
flagUpdate = false
//return data // 如果结构体中不存在 createBy 字段,则直接返回原始值
updateByField2, ok2 := typ.FieldByName("UpdatedBy")
if ok2 {
updateByField = updateByField2
flagCreate = true
}
}
if flagCreate {
// 判断 createBy 字段的类型是否为 string
if createByField.Type.Kind() != reflect.String {
// 如果 createBy 字段的类型不是 string请根据需要进行相应的处理或返回错误
// 此处假设 createBy 必须为 string 类型,如果不是则返回错误
return errors.New("createBy字段类型不匹配")
}
// 获取原始的 createBy 字段的值并获取创建人
createId := val.FieldByIndex(createByField.Index).String()
ve := SelectByString(ctx, IsNumeric(createId), createId)
// 设置 createBy 字段的值为指定参数
createByFieldVal := val.FieldByIndex(createByField.Index)
createByFieldVal.SetString(ve)
}
if flagUpdate {
// 判断 updateBy 字段的类型是否为 string
if updateByField.Type.Kind() != reflect.String {
// 如果 createBy 字段的类型不是 string请根据需要进行相应的处理或返回错误
// 此处假设 createBy 必须为 string 类型,如果不是则返回错误
return errors.New("updateBy字段类型不匹配")
}
// 获取原始的 createBy 字段的值并获取创建人
updateId := val.FieldByIndex(updateByField.Index).String()
ve := SelectByString(ctx, IsNumeric(updateId), updateId)
// 设置 createBy 字段的值为指定参数
updateByFieldVal := val.FieldByIndex(updateByField.Index)
updateByFieldVal.SetString(ve)
}
// 返回修改后的结构体
return val.Interface()
}
func IsNumeric(str string) bool {
_, err := strconv.ParseFloat(str, 64)
return err == nil
}
// SelectByString 根据字符串查询到具体人对应的名称
func SelectByString(ctx context.Context, flag bool, str string) (ve string) {
if flag {
value, _ := dao.SysUser.Ctx(ctx).Fields("user_nickname").Where("id", str).Value()
ve = value.String()
} else {
value, _ := dao.BusConstructionUser.Ctx(ctx).Fields("user_name").Where("openid", str).Value()
ve = value.String()
}
return
}

View File

@ -0,0 +1,17 @@
package camera
type LoginCamera struct {
URLToken string `json:"URLToken"`
TokenTimeout int64 `json:"TokenTimeout"`
}
type ChannelSnapReq struct {
Serial string `json:"serial" dc:"设备编号"`
Stime int64 `json:"stime" dc:"快照时间, 从录像截取指定时间的历史快照, now 表示取实时快照, 即抓图允许值: now, YYYYMMDDHHmmss"`
Format int64 `json:"format" dc:"stime 快照格式 允许值: jpeg, png"`
}
type ChannelSnapRes struct {
Serial string `json:"serial" dc:"设备编号"`
Stime int64 `json:"stime" dc:"快照时间, 从录像截取指定时间的历史快照, now 表示取实时快照, 即抓图允许值: now, YYYYMMDDHHmmss"`
Format int64 `json:"format" dc:"stime 快照格式 允许值: jpeg, png"`
}

View File

@ -0,0 +1,303 @@
package camera
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/go-redis/redis/v8"
"github.com/gogf/gf/v2/crypto/gmd5"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/tiger1103/gfast-cache/cache"
"github.com/tiger1103/gfast/v3/api/v1/common/coryCommon"
"github.com/tiger1103/gfast/v3/api/v1/system"
commonService "github.com/tiger1103/gfast/v3/internal/app/common/service"
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
"github.com/tiger1103/gfast/v3/internal/app/system/model"
"github.com/tiger1103/gfast/v3/internal/app/system/model/do"
"github.com/tiger1103/gfast/v3/internal/app/system/service"
"github.com/tiger1103/gfast/v3/third/arithmetic/SpartaApi"
"io"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
// LoginCameraFunc 登录获取 身份凭证
func LoginCameraFunc(ctx context.Context) (token string) {
api, _ := g.Cfg().Get(ctx, "LiveGBS.safety.api")
acc, _ := g.Cfg().Get(ctx, "LiveGBS.safety.acc")
pas, _ := g.Cfg().Get(ctx, "LiveGBS.safety.pas")
key := "loginCamera"
//从缓存捞取key
prefix := g.Cfg().MustGet(ctx, "system.cache.prefix").String()
gfCache := cache.New(prefix)
get := commonService.Cache().Get(ctx, gfCache.CachePrefix+key)
if get != nil && get.String() != "" {
token = get.String()
return token
} else {
account := acc.String()
password := gmd5.MustEncryptString(pas.String())
uri := api.String() + "api/v1/login?username=" + account + "&password=" + password + "&url_token_only=true"
response, err := g.Client().Get(gctx.New(), uri)
if err != nil {
return
}
var lc *LoginCamera
err = json.Unmarshal([]byte(response.ReadAllString()), &lc)
if err != nil {
return
}
//将token存储到redis中tiken默认时间为秒实际计算为7天,(这里少100秒,防止token过期还存在redis中)
commonService.Cache().Set(ctx, key, lc.URLToken, time.Duration(lc.TokenTimeout-100)*time.Second)
token = lc.URLToken
return token
}
}
var Rdb *redis.Client
// camera.DYFunc()
// 发布小程序需要关闭 camera.DYFunc()
// DYFunc 连接redis
func DYFunc() {
ctx := gctx.New()
err := g.Try(ctx, func(ctx context.Context) {
fmt.Println("redis订阅已开启")
// 创建第一个 Redis 连接
address, _ := g.Cfg().Get(ctx, "LiveGBS.redis.address")
password, _ := g.Cfg().Get(ctx, "LiveGBS.redis.password")
// 创建一个 Redis 客户端连接
options := &redis.Options{
Addr: address.String(), // 替换为你的 Redis 地址和端口
Password: password.String(), // 替换为你的 Redis 密码
DB: 1, // 替换为你的 Redis 数据库索引
}
Rdb = redis.NewClient(options)
// 创建一个订阅频道
channelName := "device" // 替换为你要订阅的频道名
pubsub := Rdb.Subscribe(context.Background(), channelName)
// 处理订阅消息的 Goroutine
go func() {
for {
msg, err := pubsub.ReceiveMessage(context.Background())
if err != nil {
log.Println("Error receiving message:", err)
time.Sleep(time.Second) // 出错时等待一段时间后重试
continue
}
var strData = msg.Payload
log.Println("Received message:", strData)
//1、获取到数据然后查看是否''拼接(先不管拼接),不是''就直接操作数据库
split := strings.Split(strData, ":")
if len(split) < 2 {
onOrOff := strings.Split(split[0], " ")[1]
if strings.EqualFold(onOrOff, "on") {
onOrOff = "1"
} else {
onOrOff = "0"
}
_, err = dao.QianqiCamera.Ctx(ctx).Where("code", split[0]).Update(g.Map{"country_state": onOrOff})
//if err != nil {
// id, _ := result.LastInsertId()
// if id > 0 {
// dao.BusCameraChannel.Ctx(ctx).Where("country_id", id).Update(g.Map{"status": onOrOff})
// }
//}
}
time.Sleep(time.Second * 3)
}
}()
})
fmt.Println("订阅错误问题:", err)
}
// ChannelSnapFunc 快照
func ChannelSnapFunc(ctx context.Context, id int64, serial string, code string, projectId int64) (err error) {
//获取预制位,前提是 PresetEnable == true
pb, err := PeeeresettingBitFunc(gctx.New(), serial, code)
if err != nil {
return err
}
for _, pi := range pb.PresetItemList {
if pi.PresetEnable == true {
//0、预置位
err = PresettingBitFunc(ctx, serial, code, "goto", pi.PresetID, "")
//1、请求接口得到二进制数据
suffix := "png"
api, _ := g.Cfg().Get(ctx, "LiveGBS.safety.api")
tokens := LoginCameraFunc(ctx)
uri := api.String() + "api/v1/device/channelsnap?serial=" + serial + "&code=" + code + "&stime=now&format=" + suffix + "&token=" + tokens
response, err := g.Client().Get(gctx.New(), uri)
if err != nil {
return err
}
//2、生成时间文件夹生成文件名然后同一斜杠得到完成路径
ht := coryCommon.Helmet
str := coryCommon.Ynr(ht)
fn := coryCommon.FileName("helmet")
dir, err := os.Getwd()
str = dir + "/" + str + fn + "." + suffix
str = filepath.ToSlash(str)
//3、创建一个文件来保存图片数据将响应体中的图片数据复制到文件中
file, err := os.Create(str)
if err != nil {
err = errors.New("创建文件出错")
return err
}
// 创建一个缓冲区,用于读取数据(从 body 中读取数据)
var re = response
var by = re.Body
lenStr, err := io.Copy(file, by)
if err != nil {
err = errors.New("复制图片数据到文件出错")
return err
} else {
file.Close()
}
if lenStr < 10240 {
file.Close() // 关闭文件
os.Remove(str)
return err
}
//4、《斯巴达》调用算法接口圈出未带安全帽的人
req := SpartaApi.RecognizeReq{
CapUrl: str,
//RecType: "head smoke belt waste excavator Roller Truck_crane Loader Submersible_drilling_rig Sprinkler Truck_mounted_crane Truck",
RecType: "head smoke",
Async: "False",
CallBackUrl: "",
AreaHigh: "",
}
mp, flag, num, err := SpartaApi.CommonAlgorithmFunc(ctx, &req)
if err != nil {
os.Remove(str)
return err
}
////4、《ys7》调用算法接口圈出未带安全帽的人
//flag, num, err := ys7.InitYs7(str)
//if err != nil {
// os.Remove(str)
// return err
//}
//5、flag为true表示有违规数据
if flag {
// 使用range遍历map
mpkStr := ""
mpvStr := ""
for key, value := range mp {
mpkStr = mpkStr + key + ","
mpvStr = mpvStr + value + "、"
}
mpkStr = strings.TrimRight(mpkStr, ",")
mpvStr = strings.TrimRight(mpvStr, "、")
//5、生成数据存储到数据表中识别记录
path := strings.ReplaceAll(ht, "/resource/public/", coryCommon.GetWd)
currentTime := time.Now()
dateString := currentTime.Format("2006-01-02")
path = path + dateString + "/" + fn + "." + suffix
addReq := system.BusTourAddReq{
ProjectId: projectId,
TourCategory: "2",
TourType: "1",
Picture: path,
Describe: mpvStr,
Num: num,
TableName: dao.QianqiCamera.Table(),
TableId: id,
}
service.BusTour().Add(ctx, &addReq)
//6、生成数据存储到违规记录里面
var bvl model.BusViolationLevelInfoRes
err = dao.BusViolationLevel.Ctx(ctx).Where("tour_type", mpkStr).Fields("id,grade").Scan(&bvl)
recordAddReq := do.BusViolationRecord{
ProjectId: projectId,
LevelId: bvl.Id,
Level: bvl.Grade,
TourType: mpkStr,
DataSource: "camera",
//Picture: path,
//WxOrPc: "1",
}
dao.BusViolationRecord.Ctx(ctx).Insert(recordAddReq)
}
time.Sleep(20 * time.Second)
}
}
return err
}
/*
PresettingBitFunc 设备控制-预置位控制
serial: 国标号
code: 通道号
command: 指令
preset: 预置位编号
name: 预置位名称
*/
func PresettingBitFunc(ctx context.Context, serial string, code string, command string, preset int, name string) (err error) {
if name != "" && (name == "set" || name == "goto" || name == "remove") {
err = errors.New("控制指令允许值: set, goto, remove")
return err
}
if !(preset > 0 && preset <= 255) {
err = errors.New("预置位编号范围为1~255")
return err
}
tokens := LoginCameraFunc(ctx)
api, _ := g.Cfg().Get(ctx, "LiveGBS.safety.api")
uri := api.String() + "api/v1/control/preset?serial=" + serial +
"&code=" + code +
"&command=" + command +
"&preset=" + strconv.Itoa(preset) +
"&token=" + tokens
if name != "" {
uri = uri + "&name=" + name
}
g.Client().Get(gctx.New(), uri)
return err
}
func PeeeresettingBitFunc(ctx context.Context, serial string, code string) (pb *PeeeresettingBitEntity, err error) {
tokens := LoginCameraFunc(ctx)
api, _ := g.Cfg().Get(ctx, "LiveGBS.safety.api")
uri := api.String() + "api/v1/device/fetchpreset?serial=" + serial +
"&code=" + code +
"&token=" + tokens +
"&fill=false"
ft, errft := g.Client().Get(gctx.New(), uri)
var str = ft.ReadAllString()
err = json.Unmarshal([]byte(str), &pb)
if err != nil {
if strings.Contains(str, "offline") {
err = errors.New("当前设备不在线!")
} else if strings.Contains(str, "not found") {
err = errors.New("当前设备沒找到!")
} else {
err = errft
}
return
}
return
}
type PeeeresettingBitEntity struct {
DeviceID string `json:"DeviceID"`
Result string `json:"Result"`
SumNum int `json:"SumNum"`
PresetItemList []*PeeeresettingBitListEntity `json:"PresetItemList"`
}
type PeeeresettingBitListEntity struct {
PresetID int `json:"PresetID"`
PresetName string `json:"PresetName"`
PresetEnable bool `json:"PresetEnable"`
}

View File

@ -0,0 +1,58 @@
package coryCommon
const Global = "xny.yj-3d.com"
var GlobalPath = "http://" + Global + ":7363"
var GlobalFile = "/file"
// ==========================错误类型==========================
// SYSERRHINT 系统错误
var syserrhint = "系统错误,请联系管理员!"
// ImportFile 系统错误
var ImportFile = "导入文件有误!"
// ===========================文件===========================
var GetWd = "/file/" // GetWd 静态文件路径
var LargeFileClt = "/resource/public/clt/" // LargeFileClt 文件上传地址-【倾斜模型/光伏板】
var LargeFileShp = "/resource/public/shp/" // LargeFileShp 文件上传地址-shp 红线
var ProjectSplitTable = "/resource/public/projectSplitTable/" // ProjectSplitTable 项目划分表临时文件
var Temporary = "/resource/public/temporary" // Temporary 临时文件存放地址
var LargeFilePrivacy = "/resource/public/privacy/" // LargeFilePrivacy 身份证银行卡的存储位置
var Portrait = "/resource/public/portrait" // Portrait 人像 - 人脸存储的地方(人脸对比的人脸和打卡的人脸都存放在此处)
var Helmet = "/resource/public/upload_file/" // Helmet 公共位置
var GisModelLib = "/resource/public/mx/" // GisModelLib 模型库
var Uav = "/resource/public/uav/" // uav 无人机资源
var UavMerge = "resource/public/tif" // uav 无人机资源大图合并资源
/*
网盘位置
*/
var Template = "/resource/public/masterMask"
var Template2 = "/resource/public/masterMask/dataFolder" //(工程资料)资料文件保存的位置
var Template3 = "/resource/public/masterMask/sourceData" //(工程资料)源数据文件保存的位置
var Report = "/resource/public/networkDisk/report" //(网盘)科研及专题报告
var ProductionDrawing = "/resource/public/networkDisk/productionDrawing" //(网盘)施工图
var Completion = "/resource/public/networkDisk/completion" //(网盘)竣工图
var QualityMeeting = "/resource/public/networkDisk/qualityMeeting" //(网盘)质量会议
var SafetyMeeting = "/resource/public/networkDisk/safetyMeeting" //(网盘)安全会议
// ==========================文件后缀==========================
// PictureSuffix 常见图片后缀
var PictureSuffix = "JPEG|JPG|HEIF|PNG"
// ==========================项目常量==========================
// gispath gis文件
var gispath = "gisfile/"
// commonpath common文件
var commonpath = "commonfile/"
// addFilePath GIS路径
var addFilePath = "/yjearth4.0/static/source/"
// suffix 文件后缀名称
var suffix = "clt,pdf,zip"

View File

@ -0,0 +1,277 @@
package coryCommon
import (
"math"
"strconv"
)
// WGS84坐标系即地球坐标系国际上通用的坐标系。
// GCJ02坐标系即火星坐标系WGS84坐标系经加密后的坐标系。Google Maps高德在用。
// BD09坐标系即百度坐标系GCJ02坐标系经加密后的坐标系。
const (
X_PI = math.Pi * 3000.0 / 180.0
OFFSET = 0.00669342162296594323
AXIS = 6378245.0
)
// BD09toGCJ02 百度坐标系->火星坐标系
func BD09toGCJ02(lon, lat float64) (float64, float64) {
x := lon - 0.0065
y := lat - 0.006
z := math.Sqrt(x*x+y*y) - 0.00002*math.Sin(y*X_PI)
theta := math.Atan2(y, x) - 0.000003*math.Cos(x*X_PI)
gLon := z * math.Cos(theta)
gLat := z * math.Sin(theta)
return gLon, gLat
}
// GCJ02toBD09 火星坐标系->百度坐标系
func GCJ02toBD09(lon, lat float64) (float64, float64) {
z := math.Sqrt(lon*lon+lat*lat) + 0.00002*math.Sin(lat*X_PI)
theta := math.Atan2(lat, lon) + 0.000003*math.Cos(lon*X_PI)
bdLon := z*math.Cos(theta) + 0.0065
bdLat := z*math.Sin(theta) + 0.006
return bdLon, bdLat
}
// WGS84toGCJ02 WGS84坐标系->火星坐标系
func WGS84toGCJ02(lon, lat float64) (float64, float64) {
if isOutOFChina(lon, lat) {
return lon, lat
}
mgLon, mgLat := delta(lon, lat)
return mgLon, mgLat
}
// GCJ02toWGS84 火星坐标系->WGS84坐标系
func GCJ02toWGS84(lon, lat float64) (float64, float64) {
if isOutOFChina(lon, lat) {
return lon, lat
}
mgLon, mgLat := delta(lon, lat)
return lon*2 - mgLon, lat*2 - mgLat
}
// BD09toWGS84 百度坐标系->WGS84坐标系
func BD09toWGS84(lon, lat float64) (float64, float64) {
lon, lat = BD09toGCJ02(lon, lat)
return GCJ02toWGS84(lon, lat)
}
// WGS84toBD09 WGS84坐标系->百度坐标系
func WGS84toBD09(lon, lat float64) (float64, float64) {
lon, lat = WGS84toGCJ02(lon, lat)
return GCJ02toBD09(lon, lat)
}
func delta(lon, lat float64) (float64, float64) {
dlat := transformlat(lon-105.0, lat-35.0)
dlon := transformlng(lon-105.0, lat-35.0)
radlat := lat / 180.0 * math.Pi
magic := math.Sin(radlat)
magic = 1 - OFFSET*magic*magic
sqrtmagic := math.Sqrt(magic)
dlat = (dlat * 180.0) / ((AXIS * (1 - OFFSET)) / (magic * sqrtmagic) * math.Pi)
dlon = (dlon * 180.0) / (AXIS / sqrtmagic * math.Cos(radlat) * math.Pi)
mgLat := lat + dlat
mgLon := lon + dlon
return mgLon, mgLat
}
func transformlat(lon, lat float64) float64 {
var ret = -100.0 + 2.0*lon + 3.0*lat + 0.2*lat*lat + 0.1*lon*lat + 0.2*math.Sqrt(math.Abs(lon))
ret += (20.0*math.Sin(6.0*lon*math.Pi) + 20.0*math.Sin(2.0*lon*math.Pi)) * 2.0 / 3.0
ret += (20.0*math.Sin(lat*math.Pi) + 40.0*math.Sin(lat/3.0*math.Pi)) * 2.0 / 3.0
ret += (160.0*math.Sin(lat/12.0*math.Pi) + 320*math.Sin(lat*math.Pi/30.0)) * 2.0 / 3.0
return ret
}
func transformlng(lon, lat float64) float64 {
var ret = 300.0 + lon + 2.0*lat + 0.1*lon*lon + 0.1*lon*lat + 0.1*math.Sqrt(math.Abs(lon))
ret += (20.0*math.Sin(6.0*lon*math.Pi) + 20.0*math.Sin(2.0*lon*math.Pi)) * 2.0 / 3.0
ret += (20.0*math.Sin(lon*math.Pi) + 40.0*math.Sin(lon/3.0*math.Pi)) * 2.0 / 3.0
ret += (150.0*math.Sin(lon/12.0*math.Pi) + 300.0*math.Sin(lon/30.0*math.Pi)) * 2.0 / 3.0
return ret
}
func isOutOFChina(lon, lat float64) bool {
return !(lon > 73.66 && lon < 135.05 && lat > 3.86 && lat < 53.55)
}
/*
===========================================================================================
===========================================================================================
===========================================================================================
===========================================================================================
===========================================================================================
===========================================================================================
===========================================================================================
*/
// GPSUtil is a utility class for GPS calculations.
// 小写方法是私有方法,大写方法是公有方法 可根据需要调整
func News() *GPSUtil {
return &GPSUtil{}
}
type GPSUtil struct {
}
const (
pi = 3.1415926535897932384626 // 圆周率
x_pi = 3.14159265358979324 * 3000.0 / 180.0 // 圆周率对应的经纬度偏移
a = 6378245.0 // 长半轴
ee = 0.00669342162296594323 // 扁率
)
func (receiver *GPSUtil) transformLat(x, y float64) float64 {
ret := -100.0 + 2.0*x + 3.0*y + 0.2*y*y + 0.1*x*y + 0.2*math.Sqrt(math.Abs(x))
ret += (20.0*math.Sin(6.0*x*pi) + 20.0*math.Sin(2.0*x*pi)) * 2.0 / 3.0
ret += (20.0*math.Sin(y*pi) + 40.0*math.Sin(y/3.0*pi)) * 2.0 / 3.0
ret += (160.0*math.Sin(y/12.0*pi) + 320*math.Sin(y*pi/30.0)) * 2.0 / 3.0
return ret
}
func (receiver *GPSUtil) transformlng(x, y float64) float64 {
ret := 300.0 + x + 2.0*y + 0.1*x*x + 0.1*x*y + 0.1*math.Sqrt(math.Abs(x))
ret += (20.0*math.Sin(6.0*x*pi) + 20.0*math.Sin(2.0*x*pi)) * 2.0 / 3.0
ret += (20.0*math.Sin(x*pi) + 40.0*math.Sin(x/3.0*pi)) * 2.0 / 3.0
ret += (150.0*math.Sin(x/12.0*pi) + 300.0*math.Sin(x/30.0*pi)) * 2.0 / 3.0
return ret
}
func (receiver *GPSUtil) outOfChina(lat, lng float64) bool {
if lng < 72.004 || lng > 137.8347 {
return true
}
if lat < 0.8293 || lat > 55.8271 {
return true
}
return false
}
func (receiver *GPSUtil) transform(lat, lng float64) []float64 {
if receiver.outOfChina(lat, lng) {
return []float64{lat, lng}
}
dLat := receiver.transformLat(lng-105.0, lat-35.0)
dlng := receiver.transformlng(lng-105.0, lat-35.0)
radLat := lat / 180.0 * pi
magic := math.Sin(radLat)
magic = 1 - ee*magic*magic
SqrtMagic := math.Sqrt(magic)
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * SqrtMagic) * pi)
dlng = (dlng * 180.0) / (a / SqrtMagic * math.Cos(radLat) * pi)
mgLat := lat + dLat
mglng := lng + dlng
return []float64{mgLat, mglng}
}
// WGS84_To_Gcj02 84 to 火星坐标系 (GCJ-02) World Geodetic System ==> Mars Geodetic System
// @param lat
// @param lng
// @return
func (receiver *GPSUtil) WGS84_To_Gcj02(lat, lng float64) []float64 {
if receiver.outOfChina(lat, lng) {
return []float64{lat, lng}
}
dLat := receiver.transformLat(lng-105.0, lat-35.0)
dlng := receiver.transformlng(lng-105.0, lat-35.0)
radLat := lat / 180.0 * pi
magic := math.Sin(radLat)
magic = 1 - ee*magic*magic
SqrtMagic := math.Sqrt(magic)
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * SqrtMagic) * pi)
dlng = (dlng * 180.0) / (a / SqrtMagic * math.Cos(radLat) * pi)
mgLat := lat + dLat
mglng := lng + dlng
return []float64{mgLat, mglng}
}
// GCJ02_To_WGS84
// 火星坐标系 (GCJ-02) to WGS84
// @param lng
// @param lat
// @return
func (receiver *GPSUtil) GCJ02_To_WGS84(lng, lat float64) []float64 {
gps := receiver.transform(lat, lng)
lngtitude := lng*2 - gps[1]
latitude := lat*2 - gps[0]
return []float64{lngtitude, latitude}
}
/**
* 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换算法 将 GCJ-02 坐标转换成 BD-09 坐标
*
* @param lat
* @param lng
*/
func (receiver *GPSUtil) gcj02_To_Bd09(lat, lng float64) []float64 {
x := lng
y := lat
z := math.Sqrt(x*x+y*y) + 0.00002*math.Sin(y*x_pi)
theta := math.Atan2(y, x) + 0.000003*math.Cos(x*x_pi)
templng := z*math.Cos(theta) + 0.0065
tempLat := z*math.Sin(theta) + 0.006
gps := []float64{tempLat, templng}
return gps
}
/**
* * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换算法 * * 将 BD-09 坐标转换成GCJ-02 坐标 * * @param
* bd_lat * @param bd_lng * @return
*/
func (receiver *GPSUtil) bd09_To_Gcj02(lat, lng float64) []float64 {
x := lng - 0.0065
y := lat - 0.006
z := math.Sqrt(x*x+y*y) - 0.00002*math.Sin(y*x_pi)
theta := math.Atan2(y, x) - 0.000003*math.Cos(x*x_pi)
templng := z * math.Cos(theta)
tempLat := z * math.Sin(theta)
gps := []float64{tempLat, templng}
return gps
}
/**将WGS84转为bd09
* @param lat
* @param lng
* @return
*/
func (receiver *GPSUtil) WGS84_To_bd09(lat, lng float64) []float64 {
gcj02 := receiver.WGS84_To_Gcj02(lat, lng)
bd09 := receiver.gcj02_To_Bd09(gcj02[0], gcj02[1])
return bd09
}
func (receiver *GPSUtil) bd09_To_WGS84(lat, lng float64) []float64 {
gcj02 := receiver.bd09_To_Gcj02(lat, lng)
WGS84 := receiver.GCJ02_To_WGS84(gcj02[0], gcj02[1])
//保留小数点后六位
WGS84[0] = receiver.retain6(WGS84[0])
WGS84[1] = receiver.retain6(WGS84[1])
return WGS84
}
/**保留小数点后六位
* @param num
* @return
*/
func (receiver *GPSUtil) retain6(num float64) float64 {
value, _ := strconv.ParseFloat(strconv.FormatFloat(num, 'f', 6, 64), 64)
return value
}

View File

@ -0,0 +1,329 @@
package coryCommon
import (
"archive/zip"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/tiger1103/gfast/v3/internal/app/system/model"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
"time"
)
// CreateZipFile 生成一个压缩文件夹,然后将指定文件夹的数据(文件及文件夹)存放到压缩文件下
func CreateZipFile(sourceDir, zipFile string) error {
zipFileToCreate, err := os.Create(zipFile)
if err != nil {
return err
}
defer zipFileToCreate.Close()
zipWriter := zip.NewWriter(zipFileToCreate)
defer zipWriter.Close()
err = filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
return addFileToZip(zipWriter, path, sourceDir)
})
if err != nil {
return err
}
return nil
}
func addFileToZip(zipWriter *zip.Writer, filePath, baseDir string) error {
fileToZip, err := os.Open(filePath)
if err != nil {
return err
}
defer fileToZip.Close()
info, err := fileToZip.Stat()
if err != nil {
return err
}
// 获取文件相对路径
relPath, err := filepath.Rel(baseDir, filePath)
if err != nil {
return err
}
// 替换路径分隔符确保在压缩文件中使用正斜杠
relPath = strings.ReplaceAll(relPath, `\`, "/")
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = relPath
if info.IsDir() {
header.Name += "/"
header.Method = zip.Store // Directory
} else {
header.Method = zip.Deflate // File
}
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return err
}
if !info.IsDir() {
_, err = io.Copy(writer, fileToZip)
if err != nil {
return err
}
}
return nil
}
// MultifileDownload 【功能:多文件下载】===【参数relativelyTemporaryPath相对路径、filesToCopy需要放在压缩包下载的文件】
func MultifileDownload(relativelyTemporaryPath string, filesToCopy []string) (path string, err error) {
//网络资源下载到本地
for i := range filesToCopy {
url := filesToCopy[i]
pathParts := strings.Split(url, "/")
fileName := pathParts[len(pathParts)-1]
filePath := filepath.ToSlash(GetCWD() + Temporary + "/" + fileName)
err = DownloadFile(url, filePath)
if err != nil {
return "", err
}
filesToCopy[i] = filePath
}
// 1、创建临时压缩包
zipFile, zipWriter, err := createTempZip(relativelyTemporaryPath)
if err != nil {
fmt.Println("Error creating temp zip:", err)
return "", err
}
defer func() {
zipWriter.Close()
zipFile.Close()
//暂时不删除、创建了个每月定时清除临时文件的定时器
//for i := range filesToCopy {
// delFile(filesToCopy[i], 0) //删除临时文件
//}
//go delFile(zipFile.Name(), 20) // 删除临时压缩文件20秒后执行防止文件还没下载完成就给删除了
}()
// 2、复制文件夹到压缩包
for _, filePath := range filesToCopy {
err := copyFileToZip(zipWriter, filePath)
if err != nil {
fmt.Printf("Error copying %s to zip: %s\n", filePath, err)
return "", err
}
}
path = strings.ReplaceAll(filepath.ToSlash(zipFile.Name()), filepath.ToSlash(GetCWD())+"/resource/public", "/file") //如果服务器同步需要注意wxfile
return
}
// 创建临时压缩包,并且提供写入数据
func createTempZip(relativelyTemporaryPath string) (*os.File, *zip.Writer, error) {
cwd := GetCWD() + relativelyTemporaryPath
zipFile, err := os.CreateTemp(cwd, "temp_zip_*.zip") //*自动分配
if err != nil {
return nil, nil, err
}
zipWriter := zip.NewWriter(zipFile)
return zipFile, zipWriter, nil
}
// 复制文件到压缩包
func copyFileToZip(zipWriter *zip.Writer, filePath string) error {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
// 获取文件信息
info, err := file.Stat()
if err != nil {
return err
}
// 创建zip文件中的文件头
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
// 指定文件名
header.Name = filepath.Base(filePath)
// 创建zip文件中的文件
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return err
}
// 复制文件内容到zip文件中
_, err = io.Copy(writer, file)
return err
}
// delFile 删除文件
func delFile(file string, second int) {
if second > 0 {
time.Sleep(time.Duration(second) * time.Second)
}
if err := os.Remove(file); err != nil {
fmt.Println("Failed to delete temporary file:", err)
}
}
// DownloadFile URL资源下载到自定位置
func DownloadFile(url, localPath string) error {
// 发起HTTP GET请求
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
// 检查响应状态码
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("HTTP request failed with status code: %d", resp.StatusCode)
}
// 创建本地文件
file, err := os.Create(localPath)
if err != nil {
return err
}
defer file.Close()
// 将HTTP响应体的内容拷贝到本地文件
_, err = io.Copy(file, resp.Body)
if err != nil {
return err
}
return nil
}
// RemoveAllFilesInDirectory 删除指定目录下的所有文件 例子:"D:\\Cory\\go\\中煤\\zmkg-back\\resource\\public\\temporary"
func RemoveAllFilesInDirectory(directoryPath string) error {
// 获取指定目录下的所有文件和子目录
files, err := filepath.Glob(filepath.Join(directoryPath, "*"))
if err != nil {
return err
}
// 遍历所有文件并删除
for _, file := range files {
if err := os.RemoveAll(file); err != nil {
return err
}
fmt.Println("Deleted:", file)
}
return nil
}
// GetFiles 获取目录下所有文件(包括文件夹中的文件)
func GetFiles(folder string) (filesList []string) {
files, _ := ioutil.ReadDir(folder)
for _, file := range files {
if file.IsDir() {
GetFiles(folder + "/" + file.Name())
} else {
filesList = append(filesList, file.Name())
}
}
return
}
// GetAllFile 获取目录下直属所有文件(不包括文件夹及其中的文件)
func GetAllFile(pathname string) (s []string, err error) {
rd, err := ioutil.ReadDir(pathname)
if err != nil {
fmt.Println("read dir fail:", err)
return s, err
}
for _, fi := range rd {
if !fi.IsDir() {
fullName := pathname + "/" + fi.Name()
s = append(s, fullName)
}
}
return s, nil
}
// Xcopy 复制文件
func Xcopy(source, target string) (err error) {
// 打开源文件
sourceFile, err := os.Open(source)
if err != nil {
return err
}
defer sourceFile.Close()
// 创建目标文件
destinationFile, err := os.Create(target)
if err != nil {
return err
}
defer destinationFile.Close()
// 使用 io.Copy() 函数复制文件内容
_, err = io.Copy(destinationFile, sourceFile)
return err
}
// CustomizationMultifileDownload 定制下载(安全考试专用)
func CustomizationMultifileDownload(relativelyTemporaryPath string, mw []*model.ModelWeChatPdfWoRes) (path string, err error) {
//1、创建文件夹
paht := filepath.ToSlash(GetCWD() + "/" + Ynr(Temporary+"/"))
folder := paht + FileName("aqks") //文件夹名
folder = filepath.ToSlash(folder)
folder = folder[0 : len(folder)-1]
err = os.MkdirAll(folder, 0777)
if err != nil {
return
}
//2、网络资源下载到本地
for i := range mw {
url := mw[i].Path
str := folder + "/" + mw[i].UserName + mw[i].Openid
os.MkdirAll(str, 0777) //根据名字创建子目录
//url看看是几个文件
fileNum := strings.Split(url, ",")
for j := range fileNum {
if fileNum[j] != "" {
//因为pdf是另外一个服务器所以需要下载但是有的又是本地服务器所以直接复制
if strings.Contains(fileNum[j], "/wxfile/") {
pathstr := g.Cfg().MustGet(gctx.New(), "cory").String() + fileNum[j]
pathParts := strings.Split(pathstr, "/")
filePath := str + "/" + pathParts[len(pathParts)-1]
err = DownloadFile(pathstr, filePath) //下载网络图片
if err != nil {
return "", err
}
} else {
source := FileToFunc(fileNum[j], 2)
pathParts := strings.Split(fileNum[j], "/")
target := str + "/" + pathParts[len(pathParts)-1]
err := Xcopy(source, target)
if err != nil {
return "", err
}
}
}
}
}
//3、压缩成压缩包zip
path = paht + FileName("aqks") + ".zip"
err = CreateZipFile(folder, path)
return
}

View File

@ -0,0 +1,515 @@
package excelUtil
import (
"fmt"
"github.com/tiger1103/gfast/v3/api/v1/common/coryCommon"
"github.com/tiger1103/gfast/v3/api/wxApplet/wxApplet"
"github.com/xuri/excelize/v2"
"os"
"strconv"
"time"
)
// ExcelOne 每条数据一个工作簿
func ExcelOne(oneDateOneList []*wxApplet.PunchingCardRecordOne) (file string, filePath string, fileName string) {
f := excelize.NewFile()
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
for x, one := range oneDateOneList {
itoa := strconv.Itoa(x + 1)
// -----创建一个工作表
_, err := f.NewSheet("Sheet" + itoa)
if err != nil {
fmt.Println(err)
return
}
// -----表数据
//设置1、2行高度
err = f.SetRowHeight("Sheet"+itoa, 1, 36)
err = f.SetRowHeight("Sheet"+itoa, 2, 14)
//1、左侧固定 ABC的1~2
f.SetCellValue("Sheet"+itoa, "A1", "序号")
f.MergeCell("Sheet"+itoa, "A1", "A2")
f.SetCellValue("Sheet"+itoa, "B1", "姓名")
f.MergeCell("Sheet"+itoa, "B1", "B2")
f.SetCellValue("Sheet"+itoa, "C1", "")
f.MergeCell("Sheet"+itoa, "C1", "C2")
//2、左侧固定 ABC的3~4
f.SetCellValue("Sheet"+itoa, "A3", "1")
f.MergeCell("Sheet"+itoa, "A3", "A4")
f.SetCellValue("Sheet"+itoa, "B3", one.Name)
f.MergeCell("Sheet"+itoa, "B3", "B4")
f.SetCellValue("Sheet"+itoa, "C3", "打卡记录")
f.SetCellValue("Sheet"+itoa, "C4", "工时")
//3、中间数据---------从4开始创建列
var num = "0" //循环生成列
var numInt = 1 //循环列对应的编号
var numi = 0 //循环过后需要的列
for i := 0; i < len(one.PunchCard); i++ {
num = getColumnName(4 + i)
f.SetCellValue("Sheet"+itoa, num+"2", numInt) //循环列对应的编号
f.SetCellValue("Sheet"+itoa, num+"3", one.PunchCard[i].Clock) //循环生成列 时间
f.SetCellValue("Sheet"+itoa, num+"4", one.PunchCard[i].Hour) //循环生成列 统计
numi = 4 + i
numInt = numInt + 1
//最后一个总计
if i == len(one.PunchCard)-1 {
f.SetCellValue("Sheet"+itoa, num+"5", "总计") //循环生成列
}
}
f.MergeCell("Sheet"+itoa, getColumnName(4)+"1", getColumnName(numi)+"1")
f.SetCellValue("Sheet"+itoa, num+"1", "8月")
numi = numi + 1
num = getColumnName(numi)
//4、右侧不清楚
f.SetCellValue("Sheet"+itoa, num+"1", "合计工时")
f.SetCellValue("Sheet"+itoa, num+"4", one.SumHour)
f.SetCellValue("Sheet"+itoa, num+"5", one.SumHour)
f.MergeCell("Sheet"+itoa, num+"1", num+"2")
numi = numi + 1
num = getColumnName(numi)
f.SetCellValue("Sheet"+itoa, num+"1", "合计工天")
f.SetCellValue("Sheet"+itoa, num+"4", one.SumDay)
f.SetCellValue("Sheet"+itoa, num+"5", one.SumDay)
f.MergeCell("Sheet"+itoa, num+"1", num+"2")
numi = numi + 1
num = getColumnName(numi)
f.SetCellValue("Sheet"+itoa, num+"1", "工人签名")
f.MergeCell("Sheet"+itoa, num+"1", num+"2")
numi = numi + 1
num = getColumnName(numi)
f.SetCellValue("Sheet"+itoa, num+"1", "备注")
f.MergeCell("Sheet"+itoa, num+"1", num+"2")
}
// 设置工作簿的默认工作表
f.SetActiveSheet(1)
// 根据指定路径保存文件
str := FileName()
filePath = coryCommon.Temporary + "/" + str
getwd, err := os.Getwd()
err = f.SaveAs(getwd + filePath)
if err != nil {
fmt.Println(err)
return "", "", ""
}
return getwd + filePath, filePath, str
}
// ExcelTwo 多条数据在一个工作簿
func ExcelTwo(oneDateOneList []*wxApplet.PunchingCardRecordOne) (file string, filePath string, fileName string) {
f := excelize.NewFile()
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
bottomStyleId, err := f.NewStyle(&excelize.Style{
Border: []excelize.Border{
{Type: "bottom", Color: "000000", Style: 1},
//{Type: "diagonalDown", Color: "000000", Style: 5},
//{Type: "diagonalUp", Color: "A020F0", Style: 6},
},
//背景
Fill: excelize.Fill{
Type: "pattern",
Color: []string{"#e6ecf0"},
Pattern: 1,
},
////字体
//Font: &excelize.Font{
// Color: "#ffffff", // 字体颜色,这里使用蓝色 (#0000FF)
//},
})
//全样式
styleId, err := f.NewStyle(&excelize.Style{
//边框
Border: []excelize.Border{
{Type: "left", Color: "000000", Style: 1},
{Type: "top", Color: "000000", Style: 1},
{Type: "bottom", Color: "000000", Style: 1},
{Type: "right", Color: "000000", Style: 1},
},
//居中
Alignment: &excelize.Alignment{
Vertical: "center", // 上下居中
Horizontal: "center", // 左右居中
},
//背景
Fill: excelize.Fill{
Type: "pattern",
Color: []string{"#e6ecf0"},
Pattern: 1,
},
////字体
//Font: &excelize.Font{
// Color: "#ffffff", // 字体颜色,这里使用蓝色 (#0000FF)
//},
})
// 每组数据隔N行另起
bookNum := 0
for x, one := range oneDateOneList {
if one.Name == "" {
continue
}
itoa := "1" //第一个工作簿
var num = "0" //循环生成列
var numInt = 1 //循环列对应的编号
var numi = 0 //循环过后需要的列
for i := 0; i < len(one.PunchCard); i++ {
//设置1、2行高度
err := f.SetRowHeight("Sheet"+itoa, bookNum+1, 36)
err = f.SetRowHeight("Sheet"+itoa, bookNum+2, 14)
if err != nil {
fmt.Println(err)
return
}
//1、左侧固定 ABC的1~2
f.SetCellValue("Sheet"+itoa, "A"+strconv.Itoa((bookNum+1)), "序号")
f.MergeCell("Sheet"+itoa, "A"+strconv.Itoa((bookNum+1)), "A"+strconv.Itoa((bookNum+2)))
f.SetCellStyle("Sheet"+itoa, "A"+strconv.Itoa((bookNum+1)), "A"+strconv.Itoa((bookNum+2)), styleId)
f.SetCellValue("Sheet"+itoa, "B"+strconv.Itoa((bookNum+1)), "姓名")
f.MergeCell("Sheet"+itoa, "B"+strconv.Itoa((bookNum+1)), "B"+strconv.Itoa((bookNum+2)))
f.SetCellStyle("Sheet"+itoa, "B"+strconv.Itoa((bookNum+1)), "B"+strconv.Itoa((bookNum+2)), styleId)
f.SetCellValue("Sheet"+itoa, "C"+strconv.Itoa((bookNum+1)), "")
f.MergeCell("Sheet"+itoa, "C"+strconv.Itoa((bookNum+1)), "C"+strconv.Itoa((bookNum+2)))
f.SetCellStyle("Sheet"+itoa, "C"+strconv.Itoa((bookNum+1)), "C"+strconv.Itoa((bookNum+2)), styleId)
//2、左侧固定 ABC的3~4
f.SetCellValue("Sheet"+itoa, "A"+strconv.Itoa((bookNum+3)), x+1)
f.MergeCell("Sheet"+itoa, "A"+strconv.Itoa((bookNum+3)), "A"+strconv.Itoa((bookNum+4)))
f.SetCellStyle("Sheet"+itoa, "A"+strconv.Itoa((bookNum+3)), "A"+strconv.Itoa((bookNum+4)), styleId)
f.SetCellValue("Sheet"+itoa, "B"+strconv.Itoa((bookNum+3)), one.Name)
f.MergeCell("Sheet"+itoa, "B"+strconv.Itoa((bookNum+3)), "B"+strconv.Itoa((bookNum+4)))
f.SetCellStyle("Sheet"+itoa, "B"+strconv.Itoa((bookNum+3)), "B"+strconv.Itoa((bookNum+4)), styleId)
//f.SetCellValue("Sheet"+itoa, "c"+strconv.Itoa((bookNum+3)), "打卡记录")
f.SetCellValue("Sheet"+itoa, "c"+strconv.Itoa((bookNum+3)), "工时")
f.MergeCell("Sheet"+itoa, "c"+strconv.Itoa((bookNum+3)), "c"+strconv.Itoa((bookNum+4)))
f.SetCellStyle("Sheet"+itoa, "c"+strconv.Itoa((bookNum+3)), "c"+strconv.Itoa((bookNum+4)), styleId)
//3、中间数据---------从4开始创建列
num = getColumnName(4 + i)
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+2)), numInt) //循环列对应的编号
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa((bookNum+2)), num+strconv.Itoa((bookNum+2)), styleId) //循环列对应的编号
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+3)), one.PunchCard[i].Clock) //循环生成列 时间
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa((bookNum+3)), num+strconv.Itoa((bookNum+3)), styleId) //循环生成列 时间
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), one.PunchCard[i].Hour) //循环生成列 统计
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+4)), styleId) //循环生成列 统计
numi = 4 + i
numInt = numInt + 1
//最后一个总计
if i == len(one.PunchCard)-1 {
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+5)), "总计") //循环生成列
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa((bookNum+5)), "总计", styleId) //循环生成列
}
}
f.SetCellValue("Sheet"+itoa, "D"+strconv.Itoa((bookNum+1)), one.Years)
f.MergeCell("Sheet"+itoa, getColumnName(4)+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+1))
f.SetCellStyle("Sheet"+itoa, getColumnName(4)+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+1), styleId)
numi = numi + 1
num = getColumnName(numi)
//4、右侧不清楚
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+1)), "合计工时")
f.MergeCell("Sheet"+itoa, num+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+3))
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+3), styleId)
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), one.SumHour)
f.MergeCell("Sheet"+itoa, num+strconv.Itoa(bookNum+4), getColumnName(numi)+strconv.Itoa(bookNum+4))
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa(bookNum+4), getColumnName(numi)+strconv.Itoa(bookNum+4), styleId)
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+5)), one.SumHour)
f.MergeCell("Sheet"+itoa, num+strconv.Itoa(bookNum+5), getColumnName(numi)+strconv.Itoa(bookNum+5))
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa(bookNum+5), getColumnName(numi)+strconv.Itoa(bookNum+5), styleId)
numi = numi + 1
num = getColumnName(numi)
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+1)), "合计工天")
f.MergeCell("Sheet"+itoa, num+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+3))
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+3), styleId)
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), one.SumDay)
f.MergeCell("Sheet"+itoa, num+strconv.Itoa(bookNum+4), getColumnName(numi)+strconv.Itoa(bookNum+4))
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa(bookNum+4), getColumnName(numi)+strconv.Itoa(bookNum+4), styleId)
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+5)), one.SumDay)
f.MergeCell("Sheet"+itoa, num+strconv.Itoa(bookNum+5), getColumnName(numi)+strconv.Itoa(bookNum+5))
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa(bookNum+5), getColumnName(numi)+strconv.Itoa(bookNum+5), styleId)
numi = numi + 1
num = getColumnName(numi)
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+1)), "工人签名")
f.MergeCell("Sheet"+itoa, num+strconv.Itoa((bookNum+1)), num+strconv.Itoa((bookNum+3)))
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa((bookNum+1)), num+strconv.Itoa((bookNum+3)), styleId)
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), "")
f.MergeCell("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+5)))
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+5)), styleId)
numi = numi + 1
num = getColumnName(numi)
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+1)), "备注")
f.MergeCell("Sheet"+itoa, num+strconv.Itoa((bookNum+1)), num+strconv.Itoa((bookNum+3)))
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa((bookNum+1)), num+strconv.Itoa((bookNum+3)), styleId)
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), "")
f.MergeCell("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+5)))
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+5)), styleId)
f.SetCellStyle("Sheet"+itoa, "A"+strconv.Itoa((bookNum+5)), getColumnName(numi-4)+strconv.Itoa((bookNum+5)), bottomStyleId)
bookNum = bookNum + 10
}
// 设置工作簿的默认工作表
f.SetActiveSheet(1)
// 根据指定路径保存文件
str := FileName()
filePath = coryCommon.Temporary + "/" + str
getwd, err := os.Getwd()
err = f.SaveAs(getwd + filePath)
if err != nil {
fmt.Println(err)
return "", "", ""
}
return getwd + filePath, filePath, str
}
// ExcelThree 多个工作簿,每个工作簿有多条数据
func ExcelThree(gey []*wxApplet.GroupEntity) (file string, filePath string, fileName string) {
f := excelize.NewFile()
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
bottomStyleId, err := f.NewStyle(&excelize.Style{
Border: []excelize.Border{
{Type: "bottom", Color: "000000", Style: 1},
//{Type: "diagonalDown", Color: "000000", Style: 5},
//{Type: "diagonalUp", Color: "A020F0", Style: 6},
},
//背景
Fill: excelize.Fill{
Type: "pattern",
Color: []string{"#e6ecf0"},
Pattern: 1,
},
////字体
//Font: &excelize.Font{
// Color: "#ffffff", // 字体颜色,这里使用蓝色 (#0000FF)
//},
})
//全样式
styleId, err := f.NewStyle(&excelize.Style{
//边框
Border: []excelize.Border{
{Type: "left", Color: "000000", Style: 1},
{Type: "top", Color: "000000", Style: 1},
{Type: "bottom", Color: "000000", Style: 1},
{Type: "right", Color: "000000", Style: 1},
},
//居中
Alignment: &excelize.Alignment{
Vertical: "center", // 上下居中
Horizontal: "center", // 左右居中
},
//背景
Fill: excelize.Fill{
Type: "pattern",
Color: []string{"#e6ecf0"},
Pattern: 1,
},
////字体
//Font: &excelize.Font{
// Color: "#ffffff", // 字体颜色,这里使用蓝色 (#0000FF)
//},
})
//工作簿
for _, data := range gey {
//itoa := strconv.Itoa(y + 1)
itoa := data.GroupName
_, err = f.NewSheet(itoa)
oneDateOneList := data.PunchingCardRecordOne
//工作簿里面的数据
bookNum := 0 // 每组数据隔N行另起
for x, one := range oneDateOneList {
//itoa := "1" //第一个工作簿
var num = "0" //循环生成列
var numInt = 1 //循环列对应的编号
var numi = 0 //循环过后需要的列
for i := 0; i < len(one.PunchCard); i++ {
//设置1、2行高度
err := f.SetRowHeight(itoa, bookNum+1, 36)
err = f.SetRowHeight(itoa, bookNum+2, 14)
if err != nil {
fmt.Println(err)
return
}
//1、左侧固定 ABC的1~2
f.SetCellValue(itoa, "A"+strconv.Itoa((bookNum+1)), "序号")
f.MergeCell(itoa, "A"+strconv.Itoa((bookNum+1)), "A"+strconv.Itoa((bookNum+2)))
f.SetCellStyle(itoa, "A"+strconv.Itoa((bookNum+1)), "A"+strconv.Itoa((bookNum+2)), styleId)
f.SetCellValue(itoa, "B"+strconv.Itoa((bookNum+1)), "姓名")
f.MergeCell(itoa, "B"+strconv.Itoa((bookNum+1)), "B"+strconv.Itoa((bookNum+2)))
f.SetCellStyle(itoa, "B"+strconv.Itoa((bookNum+1)), "B"+strconv.Itoa((bookNum+2)), styleId)
f.SetCellValue(itoa, "C"+strconv.Itoa((bookNum+1)), "")
f.MergeCell(itoa, "C"+strconv.Itoa((bookNum+1)), "C"+strconv.Itoa((bookNum+2)))
f.SetCellStyle(itoa, "C"+strconv.Itoa((bookNum+1)), "C"+strconv.Itoa((bookNum+2)), styleId)
//2、左侧固定 ABC的3~4
f.SetCellValue(itoa, "A"+strconv.Itoa((bookNum+3)), x+1)
f.MergeCell(itoa, "A"+strconv.Itoa((bookNum+3)), "A"+strconv.Itoa((bookNum+4)))
f.SetCellStyle(itoa, "A"+strconv.Itoa((bookNum+3)), "A"+strconv.Itoa((bookNum+4)), styleId)
f.SetCellValue(itoa, "B"+strconv.Itoa((bookNum+3)), one.Name)
f.MergeCell(itoa, "B"+strconv.Itoa((bookNum+3)), "B"+strconv.Itoa((bookNum+4)))
f.SetCellStyle(itoa, "B"+strconv.Itoa((bookNum+3)), "B"+strconv.Itoa((bookNum+4)), styleId)
//f.SetCellValue(itoa, "c"+strconv.Itoa((bookNum+3)), "打卡记录")
f.SetCellValue(itoa, "c"+strconv.Itoa((bookNum+3)), "工时")
f.MergeCell(itoa, "c"+strconv.Itoa((bookNum+3)), "c"+strconv.Itoa((bookNum+4)))
f.SetCellStyle(itoa, "c"+strconv.Itoa((bookNum+3)), "c"+strconv.Itoa((bookNum+4)), styleId)
//3、中间数据---------从4开始创建列
num = getColumnName(4 + i)
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+2)), numInt) //循环列对应的编号
f.SetCellStyle(itoa, num+strconv.Itoa((bookNum+2)), num+strconv.Itoa((bookNum+2)), styleId) //循环列对应的编号
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+3)), one.PunchCard[i].Clock) //循环生成列 时间
f.SetCellStyle(itoa, num+strconv.Itoa((bookNum+3)), num+strconv.Itoa((bookNum+3)), styleId) //循环生成列 时间
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+4)), one.PunchCard[i].Hour) //循环生成列 统计
f.SetCellStyle(itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+4)), styleId) //循环生成列 统计
numi = 4 + i
numInt = numInt + 1
//最后一个总计
if i == len(one.PunchCard)-1 {
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+5)), "总计") //循环生成列
f.SetCellStyle(itoa, num+strconv.Itoa((bookNum+5)), "总计", styleId) //循环生成列
}
}
f.SetCellValue(itoa, "D"+strconv.Itoa((bookNum+1)), one.Years)
f.MergeCell(itoa, getColumnName(4)+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+1))
f.SetCellStyle(itoa, getColumnName(4)+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+1), styleId)
numi = numi + 1
num = getColumnName(numi)
//4、右侧不清楚
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+1)), "合计工时")
f.MergeCell(itoa, num+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+3))
f.SetCellStyle(itoa, num+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+3), styleId)
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+4)), one.SumHour)
f.MergeCell(itoa, num+strconv.Itoa(bookNum+4), getColumnName(numi)+strconv.Itoa(bookNum+4))
f.SetCellStyle(itoa, num+strconv.Itoa(bookNum+4), getColumnName(numi)+strconv.Itoa(bookNum+4), styleId)
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+5)), one.SumHour)
f.MergeCell(itoa, num+strconv.Itoa(bookNum+5), getColumnName(numi)+strconv.Itoa(bookNum+5))
f.SetCellStyle(itoa, num+strconv.Itoa(bookNum+5), getColumnName(numi)+strconv.Itoa(bookNum+5), styleId)
numi = numi + 1
num = getColumnName(numi)
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+1)), "合计工天")
f.MergeCell(itoa, num+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+3))
f.SetCellStyle(itoa, num+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+3), styleId)
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+4)), one.SumDay)
f.MergeCell(itoa, num+strconv.Itoa(bookNum+4), getColumnName(numi)+strconv.Itoa(bookNum+4))
f.SetCellStyle(itoa, num+strconv.Itoa(bookNum+4), getColumnName(numi)+strconv.Itoa(bookNum+4), styleId)
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+5)), one.SumDay)
f.MergeCell(itoa, num+strconv.Itoa(bookNum+5), getColumnName(numi)+strconv.Itoa(bookNum+5))
f.SetCellStyle(itoa, num+strconv.Itoa(bookNum+5), getColumnName(numi)+strconv.Itoa(bookNum+5), styleId)
numi = numi + 1
num = getColumnName(numi)
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+1)), "工人签名")
f.MergeCell(itoa, num+strconv.Itoa((bookNum+1)), num+strconv.Itoa((bookNum+3)))
f.SetCellStyle(itoa, num+strconv.Itoa((bookNum+1)), num+strconv.Itoa((bookNum+3)), styleId)
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+4)), "")
f.MergeCell(itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+5)))
f.SetCellStyle(itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+5)), styleId)
numi = numi + 1
num = getColumnName(numi)
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+1)), "备注")
f.MergeCell(itoa, num+strconv.Itoa((bookNum+1)), num+strconv.Itoa((bookNum+3)))
f.SetCellStyle(itoa, num+strconv.Itoa((bookNum+1)), num+strconv.Itoa((bookNum+3)), styleId)
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+4)), "")
f.MergeCell(itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+5)))
f.SetCellStyle(itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+5)), styleId)
f.SetCellStyle(itoa, "A"+strconv.Itoa((bookNum+5)), getColumnName(numi-4)+strconv.Itoa((bookNum+5)), bottomStyleId)
//err = f.SetCellStyle("Sheet1", "A"+strconv.Itoa((bookNum+1)), "A"+strconv.Itoa((bookNum+5)), leftStyleId)
//err = f.SetCellStyle("Sheet1", "I"+strconv.Itoa((bookNum+1)), "I"+strconv.Itoa((bookNum+5)), rightStyleId)
//err = f.SetCellStyle("Sheet1", "A"+strconv.Itoa((bookNum+1)), "I"+strconv.Itoa((bookNum+1)), topStyleId)
//err = f.SetCellStyle("Sheet1", "A"+strconv.Itoa((bookNum+5)), "I"+strconv.Itoa((bookNum+5)), bottomStyleId)
bookNum = bookNum + 10
}
}
// 设置工作簿的默认工作表
f.SetActiveSheet(1)
// 根据指定路径保存文件
str := FileName()
filePath = coryCommon.Temporary + "/" + str
getwd, err := os.Getwd()
err = f.SaveAs(getwd + filePath)
if err != nil {
fmt.Println(err)
return "", "", ""
}
return getwd + filePath, filePath, str
}
// 根据列索引获取列的字母标识 比如1就是A 30就是AD
func getColumnName(index int) string {
var columnName string
for index > 0 {
mod := (index - 1) % 26
columnName = string('A'+mod) + columnName
index = (index - 1) / 26
}
return columnName
}
// FileName 生成时间戳文件名
func FileName() (str string) {
// 获取当前时间
currentTime := time.Now()
// 格式化时间戳为字符串
timestamp := currentTime.Format("20060102150405")
// 生成文件名
fileName := fmt.Sprintf("zm_%s.xlsx", timestamp)
return fileName
}

View File

@ -0,0 +1,55 @@
package excelUtil
type Style struct {
Border []Border
Fill Fill
Font *Font
Alignment *Alignment
Protection *Protection
NumFmt int
DecimalPlaces int
CustomNumFmt *string
NegRed bool
}
type Fill struct {
Type string
Pattern int
Color []string
Shading int
}
type Protection struct {
Hidden bool
Locked bool
}
type Font struct {
Bold bool
Italic bool
Underline string
Family string
Size float64
Strike bool
Color string
ColorIndexed int
ColorTheme *int
ColorTint float64
VertAlign string
}
type Border struct {
Type string
Color string
Style int
}
type Alignment struct {
Horizontal string
Indent int
JustifyLastLine bool
ReadingOrder uint64
RelativeIndent int
ShrinkToFit bool
TextRotation int
Vertical string
WrapText bool
}

View File

@ -0,0 +1,209 @@
package coryCommon
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"image"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
)
const (
MinShortEdge = 15
MaxLongEdge = 4096
MaxSize = 1024 * 1024 // 4MB
SupportedExts = ".jpg .jpeg .png .bmp"
)
func ImgDataBase64(filePath string) (err error, encodedURL string) {
file, err := os.Open(filePath)
if err != nil {
fmt.Println("打开图像文件失败:", err)
err = errors.New("打开图像文件失败")
return
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
fmt.Println("读取图像文件信息失败:", err)
err = errors.New("读取图像文件信息失败")
return
}
fileSize := fileInfo.Size()
if fileSize > MaxSize {
fmt.Println("图像文件大小超过限制:", fileSize)
err = errors.New("图像文件大小超过限制")
return
}
imgData, err := ioutil.ReadAll(file)
if err != nil {
fmt.Println("读取图像文件内容失败:", err)
err = errors.New("读取图像文件内容失败")
return
}
bounds, format, err := image.DecodeConfig(bytes.NewReader(imgData))
if err != nil {
fmt.Println("解码图像配置失败:", err)
err = errors.New("解码图像配置失败")
return
}
if format != "jpeg" && format != "jpg" && format != "png" && format != "bmp" {
fmt.Println("不支持的图像格式:", format)
//err = errors.New("不支持的图像格式")
err = errors.New("支持的图像格式为jpg、png、gif、bmp")
return
}
width := bounds.Width
height := bounds.Height
shortEdge := width
if height < width {
shortEdge = height
}
if shortEdge < MinShortEdge {
fmt.Println("图像尺寸的最短边小于要求:", shortEdge)
str := "图像尺寸的最短边小于要求:" + strconv.Itoa(shortEdge)
err = errors.New(str)
return
}
if width > MaxLongEdge || height > MaxLongEdge {
fmt.Println("图像尺寸的最长边超过限制:", width, height)
str := "图像尺寸的最长边超过限制:" + strconv.Itoa(width) + strconv.Itoa(height)
err = errors.New(str)
return
}
// Base64编码图像数据
encodedStr := base64.StdEncoding.EncodeToString(imgData)
// URL编码
//urlEncodedStr := url.QueryEscape(encodedStr)
//fmt.Println("Base64编码并URL编码后的图像数据:", urlEncodedStr)
return err, encodedStr
}
/*
EncodeAndUrlEncodeImage 图片文件的绝对路径
功能:
图像数据base64编码后进行urlencode需去掉编码头data:image/jpeg;base64, 要求base64编码和urlencode后大小不超过N兆最短边至少15px最长边最大4096px,支持jpg/jpeg/png/bmp格式
*/
func EncodeAndUrlEncodeImage(filePath string, size int64) (string, error) {
// 检查文件是否存在
_, err := os.Stat(filePath)
if os.IsNotExist(err) {
return "", fmt.Errorf("文件 %s 不存在", filePath)
}
// 检查文件大小
fileSize := getFileSize(filePath)
if fileSize > (size * MaxSize) {
return "", fmt.Errorf("文件大小超过限制")
}
// 检查图像尺寸
img, err := loadImage(filePath)
if err != nil {
return "", fmt.Errorf("无法加载图像: %s", err)
}
bounds := img.Bounds()
if !isValidSize(bounds) {
return "", fmt.Errorf("图像尺寸不符合要求")
}
// 读取文件
fileData, err := ioutil.ReadFile(filePath)
if err != nil {
return "", fmt.Errorf("无法读取文件: %s", err)
}
// 获取文件后缀名
ext := filepath.Ext(filePath)
// 检查是否支持的文件格式
if !isSupportedFormat(ext) {
return "", fmt.Errorf("不支持的文件格式")
}
// 将图像数据进行 base64 编码
encodedData := base64.StdEncoding.EncodeToString(fileData)
// 去掉编码头(如:"data:image/jpeg;base64,"
encodedData = removeEncodingHeader(encodedData, ext)
//// 对 base64 编码后的数据进行 URL 编码
//encodedData = urlEncode(encodedData)
return encodedData, nil
}
func getFileSize(filePath string) int64 {
fileInfo, err := os.Stat(filePath)
if err != nil {
return 0
}
return fileInfo.Size()
}
func loadImage(filePath string) (image.Image, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer file.Close()
img, _, err := image.Decode(file)
if err != nil {
return nil, err
}
return img, nil
}
func isValidSize(bounds image.Rectangle) bool {
width := bounds.Dx()
height := bounds.Dy()
if width < MinShortEdge || height < MinShortEdge {
return false
}
if width > MaxLongEdge || height > MaxLongEdge {
return false
}
return true
}
func isSupportedFormat(ext string) bool {
ext = strings.ToLower(ext)
return strings.Contains(SupportedExts, ext)
}
func removeEncodingHeader(encodedData string, ext string) string {
header := fmt.Sprintf("data:image/%s;base64,", ext[1:])
if strings.HasPrefix(encodedData, header) {
encodedData = strings.TrimPrefix(encodedData, header)
}
return encodedData
}
func urlEncode(data string) string {
return url.PathEscape(data)
}

View File

@ -0,0 +1,38 @@
package coryCommon
import (
"errors"
"math/big"
)
// PercentageFunc 百分比计算精度很高比如说总数100完成100最终得到结果为99.99999999%那么会直接操作成100
// precision设置精度
// total总数据量
// finish完成度
func PercentageFunc(precision uint, total, finish float64) (consequence float64, err error) {
if total == 0 {
err = errors.New("总数据不能为初始值")
return 0, err
}
if precision == 0 {
err = errors.New("精度不能为初始值")
return 0, err
}
consequence = 0
// 定义大浮点数
numerator := big.NewFloat(100)
denominator := big.NewFloat(total)
result := big.NewFloat(finish)
// 设置精度
//var precision uint = 100 // 设置为你需要的精度
numerator.SetPrec(precision)
denominator.SetPrec(precision)
result.SetPrec(precision)
// 计算结果
result.Quo(result, denominator)
result.Mul(result, numerator)
// 截取到两位小数
resultRounded, _ := result.Float64()
consequence = float64(int(resultRounded*100)) / 100 // 保留两位小数
return
}

View File

@ -0,0 +1,111 @@
package coryCommon
import (
"fmt"
"github.com/golang/geo/s2"
toolTurf "github.com/tiger1103/gfast/v3/api/v1/common/tool/turf"
"github.com/tomchavakis/geojson/geometry"
"github.com/tomchavakis/turf-go"
)
// DetailedMap shp文件数据
type DetailedMap struct {
Positions []struct {
Lng float64 `json:"lng"`
Lat float64 `json:"lat"`
Alt float64 `json:"alt"`
} `json:"positions"`
Width string `json:"width"`
Color string `json:"color"`
Alpha string `json:"alpha"`
Name string `json:"name"`
Property string `json:"property"`
TxtMemo string `json:"TxtMemo"`
ShapeLeng string `json:"Shape_Leng"`
ShapeArea string `json:"Shape_Area"`
Range struct {
MinX float64 `json:"min_x"`
MinY float64 `json:"min_y"`
MaxX float64 `json:"max_x"`
MaxY float64 `json:"max_y"`
} `json:"range"`
}
// RectangularFrameRange 是否在矩形框范围内,是否在打卡范围内
func RectangularFrameRange(dataInfo DetailedMap, locationLng float64, locationLat float64) (flag bool) {
//1、组装数据 84坐标
polygon := [][]float64{}
for _, data := range dataInfo.Positions {
polygon = append(polygon, []float64{data.Lng, data.Lat})
}
//3、判断位置
distance := toolTurf.BooleanPointInPolygon([]float64{locationLng, locationLat}, polygon)
if distance {
fmt.Println("点在矩形框内")
return true
} else {
fmt.Println("点在矩形框外")
return false
}
}
// FEIQI_RectangularFrameRange 是否在矩形框范围内,是否在打卡范围内 !!!!!!!!!!!!!!!!!有问题
func FEIQI_RectangularFrameRange(dataInfo DetailedMap, locationLng float64, locationLat float64) (flag bool) {
//1、组装数据
var pl geometry.Polygon
var ls []geometry.LineString
var pt []geometry.Point
for _, data := range dataInfo.Positions {
wgs84 := LatLng{Latitude: data.Lat, Longitude: data.Lng}
t1 := WGS84ToEPSG900913(wgs84)
var p geometry.Point
p.Lng = t1.Latitude
p.Lat = t1.Longitude
pt = append(pt, p)
}
var lsTwo geometry.LineString
lsTwo.Coordinates = pt
ls = append(ls, lsTwo)
pl.Coordinates = append(pl.Coordinates, lsTwo)
//2、当前人所在位置
locationLng, locationLat = GCJ02toWGS84(locationLng, locationLat)
wgs84 := LatLng{Latitude: locationLng, Longitude: locationLat}
t1 := WGS84ToEPSG900913(wgs84)
myPoint := geometry.Point{
Lng: t1.Longitude,
Lat: t1.Latitude,
}
//3、判断myPoint是否在pl框内
distance, _ := turf.PointInPolygon(myPoint, pl)
if distance {
fmt.Println("点在矩形框内")
return true
} else {
fmt.Println("点在矩形框外")
return false
}
}
type LatLng struct {
Latitude float64
Longitude float64
}
// WGS84ToEPSG900913 将WGS84坐标转换为EPSG-900913坐标
func WGS84ToEPSG900913(wgs84 LatLng) LatLng {
// 将WGS84坐标转换为s2.LatLng
ll := s2.LatLngFromDegrees(wgs84.Latitude, wgs84.Longitude)
// 创建s2.Point
p := s2.PointFromLatLng(ll)
// 计算EPSG-900913坐标
//epsgX, epsgY := p.Normalize()
normalize := p.Normalize()
// 将坐标范围映射到EPSG-900913的范围
epsgX := normalize.X * 20037508.34 / 180.0
epsgY := normalize.Y * 20037508.34 / 180.0
return LatLng{Latitude: epsgY, Longitude: epsgX}
}

View File

@ -0,0 +1,23 @@
package coryCommon
import (
"crypto/md5"
"encoding/hex"
"strings"
)
// MD5Hash 将字符串进行 MD5 加密,并返回 32 位长度的 MD5 散列值
func MD5Hash(input string) string {
// 将输入字符串转换为小写,以确保不区分大小写
input = strings.ToLower(input)
// 创建 MD5 散列对象
md5Hash := md5.New()
// 将输入字符串转换为字节数组,并传递给 MD5 散列对象
_, _ = md5Hash.Write([]byte(input))
// 计算 MD5 散列值
hashBytes := md5Hash.Sum(nil)
// 将散列值转换为 32 位的十六进制字符串
md5HashString := hex.EncodeToString(hashBytes)
return md5HashString
}

View File

@ -0,0 +1,69 @@
package coryCommon
import (
"github.com/gogf/gf/v2/net/gclient"
"github.com/gogf/gf/v2/os/gctx"
)
// 高德地图API https://lbs.amap.com/api/webservice/guide/api/georegeo
// InverseGeocoding 逆地理编码
func InverseGeocoding(location string) (we string) {
//请求路径
key := "3bbede95174c607a1ed4c479d3f637cc"
requestURL := "https://restapi.amap.com/v3/geocode/regeo?location=" + location + "&key=" + key
response, err := gclient.New().ContentJson().ContentType("application/x-www-form-urlencoded").Get(gctx.New(), requestURL)
if err != nil {
return
}
var dataInfo = ""
dataInfo = response.ReadAllString()
return dataInfo
}
type InverseGeocodingRep struct {
Status string `json:"status"`
Regeocode Regeocode `json:"regeocode"`
Info string `json:"info"`
Infocode string `json:"infocode"`
}
type Regeocode struct {
FormattedAddress string `json:"formatted_address"`
AddressComponent AddressComponent `json:"addressComponent"`
}
type AddressComponent struct {
//City string `json:"city"`
Province string `json:"province"`
Adcode string `json:"adcode"`
District string `json:"district"`
Towncode string `json:"towncode"`
//StreetNumber StreetNumber `json:"streetNumber"`
Country string `json:"country"`
Township string `json:"township"`
//BusinessAreas []One `json:"businessAreas"`
//Building Two `json:"building"`
//Neighborhood Two `json:"neighborhood"`
Citycode string `json:"citycode"`
}
type StreetNumber struct {
Number string `json:"number"`
Location string `json:"location"`
Direction string `json:"direction"`
Distance string `json:"distance"`
Street string `json:"street"`
}
type One struct {
Location string `json:"location"`
Name string `json:"name"`
Id string `json:"id"`
}
type Two struct {
Name []string `json:"name"`
Type []string `json:"type"`
}

View File

@ -0,0 +1,473 @@
package coryCommon
import (
"bufio"
"context"
"fmt"
"io"
"log"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/tiger1103/gfast/v3/library/liberr"
"golang.org/x/exp/rand"
)
func UploadFileS(ctx context.Context, fileHeader []*ghttp.UploadFile, fileUrl string, typeStr string) (pathStr string, err error) {
for i := range fileHeader {
var str string
if typeStr == "1" {
str, err = UploadFile(ctx, fileHeader[i], fileUrl)
}
if typeStr == "2" {
str, err = UploadFileTwo(ctx, fileHeader[i], fileUrl)
}
if err != nil {
liberr.ErrIsNil(ctx, err)
return
}
pathStr = pathStr + ResourcePublicToFunc("/"+str, 0) + ","
}
pathStr = pathStr[0 : len(pathStr)-1]
return
}
// UploadFile [文件名称为原来的文件名称] 上传文件(流) 也可以将http图片上传 返回:resource/public/upload_file/2023-11-28/tmp_c7858f0fd98d74cbe2c095125871aaf19c749c209ae33f5f.png
func UploadFile(ctx context.Context, fileHeader *ghttp.UploadFile, fileUrl string) (str string, err error) {
// 获取上传的文件流
if fileHeader == nil {
log.Println("Failed to get file")
liberr.ErrIsNil(ctx, err, "Failed to get file")
return "", err
}
ynr := Ynr(fileUrl)
// 在当前目录创建一个新文件用于保存上传的数据
lj := "/" + ynr + fileHeader.Filename
destFilePath := filepath.Join(".", lj)
destFile, err := os.Create(destFilePath)
if err != nil {
log.Println("Failed to create destination file:", err)
liberr.ErrIsNil(ctx, err)
return "", err
}
defer destFile.Close()
// 创建一个内存缓冲区作为文件数据的临时存储
buffer := make([]byte, 4096)
// 记录已接收的数据量,用于计算上传进度
var totalReceived int64
// 获取文件上传的数据流
file, err := fileHeader.Open()
if err != nil {
log.Println("Failed to open file:", err)
liberr.ErrIsNil(ctx, err)
return "", err
}
defer file.Close()
// 循环读取文件流,直到读取完整个文件
for {
// 从文件流中读取数据到缓冲区
n, err := file.Read(buffer)
if err != nil && err != io.EOF {
log.Println("Failed to read file:", err)
liberr.ErrIsNil(ctx, err)
return "", err
}
// 如果缓冲区没有数据,则表示已读取完整个文件
if n == 0 {
break
}
// 将缓冲区的数据写入目标文件
_, err = destFile.Write(buffer[:n])
if err != nil {
log.Println("Failed to write file:", err)
liberr.ErrIsNil(ctx, err)
return "", err
}
// 更新已接收的数据量
totalReceived += int64(n)
}
//// 返回上传文件的路径给客户端
//uploadPath, err := filepath.Abs(destFilePath)
//if err != nil {
// log.Println("Failed to get absolute file path:", err)
// liberr.ErrIsNil(ctx, err)
// return "", err
//}
//统一路径斜杠为/
str = filepath.ToSlash(lj)
return str, err
}
func UploadUniqueFile(ctx context.Context, fileHeader *ghttp.UploadFile, fileUrl string) (str string, err error) {
// 获取上传的文件流
if fileHeader == nil {
return "", err
}
ynr := Ynr(fileUrl)
// 在当前目录创建一个新文件用于保存上传的数据
// 在文件名中添加一个唯一的标识符,例如当前的时间戳
lj := fmt.Sprintf("%s_%d_%s", ynr, time.Now().Unix(), fileHeader.Filename)
destFilePath := filepath.Join(".", lj)
destFile, err := os.Create(destFilePath)
if err != nil {
log.Println("Failed to create destination file:", err)
liberr.ErrIsNil(ctx, err)
return "", err
}
defer destFile.Close()
// 创建一个内存缓冲区作为文件数据的临时存储
buffer := make([]byte, 4096)
// 记录已接收的数据量,用于计算上传进度
var totalReceived int64
// 获取文件上传的数据流
file, err := fileHeader.Open()
if err != nil {
log.Println("Failed to open file:", err)
liberr.ErrIsNil(ctx, err)
return "", err
}
defer file.Close()
// 循环读取文件流,直到读取完整个文件
for {
// 从文件流中读取数据到缓冲区
n, err := file.Read(buffer)
if err != nil && err != io.EOF {
return "", err
}
// 如果缓冲区没有数据,则表示已读取完整个文件
if n == 0 {
break
}
// 将缓冲区的数据写入目标文件
_, err = destFile.Write(buffer[:n])
if err != nil {
return "", err
}
// 更新已接收的数据量
totalReceived += int64(n)
}
// 统一路径斜杠为/
str = filepath.ToSlash(lj)
return str, err
}
// UploadFileTwo [文件名称为时间戳] 上传文件(流) 也可以将http图片上传 返回:resource/public/upload_file/2023-11-28/tmp_c7858f0fd98d74cbe2c095125871aaf19c749c209ae33f5f.png
func UploadFileTwo(ctx context.Context, fileHeader *ghttp.UploadFile, fileUrl string) (str string, err error) {
// 获取上传的文件流
if fileHeader == nil {
log.Println("Failed to get file")
liberr.ErrIsNil(ctx, err, "Failed to get file")
return "", err
}
ynr := Ynr(fileUrl)
// 在当前目录创建一个新文件用于保存上传的数据
lj := /* "/" +*/ ynr + FileName("login") + filepath.Ext(fileHeader.Filename)
destFilePath := filepath.Join(".", lj)
destFile, err := os.Create(destFilePath)
if err != nil {
log.Println("Failed to create destination file:", err)
liberr.ErrIsNil(ctx, err)
return "", err
}
defer destFile.Close()
// 创建一个内存缓冲区作为文件数据的临时存储
buffer := make([]byte, 4096)
// 记录已接收的数据量,用于计算上传进度
var totalReceived int64
// 获取文件上传的数据流
file, err := fileHeader.Open()
if err != nil {
log.Println("Failed to open file:", err)
liberr.ErrIsNil(ctx, err)
return "", err
}
defer file.Close()
// 循环读取文件流,直到读取完整个文件
for {
// 从文件流中读取数据到缓冲区
n, err := file.Read(buffer)
if err != nil && err != io.EOF {
log.Println("Failed to read file:", err)
liberr.ErrIsNil(ctx, err)
return "", err
}
// 如果缓冲区没有数据,则表示已读取完整个文件
if n == 0 {
break
}
// 将缓冲区的数据写入目标文件
_, err = destFile.Write(buffer[:n])
if err != nil {
log.Println("Failed to write file:", err)
liberr.ErrIsNil(ctx, err)
return "", err
}
// 更新已接收的数据量
totalReceived += int64(n)
}
//// 返回上传文件的路径给客户端
//uploadPath, err := filepath.Abs(destFilePath)
//if err != nil {
// log.Println("Failed to get absolute file path:", err)
// liberr.ErrIsNil(ctx, err)
// return "", err
//}
//统一路径斜杠为/
str = filepath.ToSlash(lj)
return str, err
}
// Ynr 创建时间文件夹 传递相对路径/resource/public/ 返回相对路径resource/public/2023-11-12/
func Ynr(baseDir string) (str string) {
// 获取当前时间
currentTime := time.Now()
// 格式化为年月日的字符串格式
dateString := currentTime.Format("2006-01-02")
dateString = baseDir + dateString
// 在相对路径上添加文件夹
destFilePath := filepath.Join(".", dateString)
dateString = destFilePath
// 检查文件夹是否已存在
_, err := os.Stat(dateString)
if os.IsNotExist(err) {
// 文件夹不存在,创建文件夹
err := os.MkdirAll(dateString, os.ModePerm)
if err != nil {
fmt.Println("创建文件夹失败:", err)
return
}
// fmt.Println("文件夹创建成功:", dateString)
} else {
// fmt.Println("文件夹已存在:", dateString)
}
return dateString + "/"
}
// FileName 【前缀】+获取当前时间+随机数得到文件名
func FileName(prefix string) (uniqueFileName string) {
currentTime := time.Now()
timestamp := currentTime.UnixNano() / int64(time.Millisecond)
randomNum := rand.Intn(1000)
uniqueFileName = fmt.Sprintf("%d_%d", timestamp, randomNum)
if prefix != "" {
uniqueFileName = prefix + uniqueFileName
}
return uniqueFileName
}
// FileInfo 传参:相对路径 获取文件信息(文件名称、文件后缀、文件大小(字节))
func FileInfo(filePath string) (name string, ext string, size int64) {
newPath := ""
if strings.Contains(filePath, "wxfile") {
newPath = strings.ReplaceAll(filePath, "/wxfile", GetCWD()+"/resource/public")
} else {
newPath = strings.ReplaceAll(filePath, "/file", GetCWD()+"/resource/public")
}
newPath = strings.ReplaceAll(newPath, "\\", "/")
filePath = newPath
// filePath = "D:\\Cory\\go\\中煤\\zmkg-back\\resource\\public\\upload_file\\2023-08-31\\cv6kxb89pd984rt5ze.png"
// 获取文件的基本名称(包括扩展名)
fileName := filepath.Base(filePath)
// 分割文件名和扩展名
nameParts := strings.Split(fileName, ".")
fileNameWithoutExt := nameParts[0]
fileExt := nameParts[1]
// 获取文件大小
fileInfo, err := os.Stat(filePath)
if err != nil {
fmt.Println("无法获取文件信息:", err)
return
}
fileSize := fileInfo.Size()
// 文件名称、文件后缀、文件大小(字节)
name = fileNameWithoutExt
ext = fileExt
size = fileSize
return
}
// OutJson 创建txt写入数据jsonData数据、absPath 绝对路径(带文件名和后缀)
func OutJson(jsonData []byte, absPath string) (flag bool, err error) {
absPath = strings.ReplaceAll(absPath, "\\", "/")
absPath = strings.ReplaceAll(absPath, "/file", GetCWD()+"/resource/public")
flag = false
// 创建要写入的文件
file, err := os.Create(absPath)
if err != nil {
fmt.Println("无法创建文件:", err)
return
}
defer file.Close()
// 将 JSON 数据写入文件
_, err = file.Write(jsonData)
if err != nil {
fmt.Println("无法写入文件:", err)
return
}
return true, err
}
// PutJson 读取文件
func PutJson(filePath string) (jsonData string, err error) {
filePath = strings.ReplaceAll(filePath, "/file", GetCWD()+"/resource/public")
filePath = strings.ReplaceAll(filePath, "\\", "/")
// filePath := "relative/path/to/file.txt"
file, err := os.Open(filePath)
if err != nil {
fmt.Println("打开文件错误:", err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
// 设置一个足够大的缓冲区
const maxScanTokenSize = 128 * 1024
buf := make([]byte, maxScanTokenSize)
scanner.Buffer(buf, maxScanTokenSize)
for scanner.Scan() {
line := scanner.Text()
jsonData = jsonData + line
}
if scanner.Err() != nil {
fmt.Println("读取文件错误:", scanner.Err())
err = scanner.Err()
return
}
return
}
/*
BatchFile 批量刪除文件
filePath 相对路径
*/
func BatchFile(filePath []string) {
for _, data := range filePath {
// if strings.Contains(data, "file") || strings.Contains(data, "wxfile"){
newPath := ""
if strings.Contains(data, "/file") {
newPath = strings.Replace(data, "/file", GetCWD()+"/resource/public", 1)
} else if strings.Contains(data, "/wxfile") {
newPath = strings.Replace(data, "/wxfile", GetCWD()+"/resource/public", 1)
}
os.Remove(strings.ReplaceAll(newPath, "\\", "/"))
}
}
// FlagImg 判斷是否是圖片
func FlagImg(filePath string) (flag bool) {
filePathLower := strings.ToLower(filePath)
isImage := strings.HasSuffix(filePathLower, ".png") || strings.HasSuffix(filePathLower, ".jpg") || strings.HasSuffix(filePathLower, ".jpeg") || strings.HasSuffix(filePathLower, ".gif") || strings.HasSuffix(filePathLower, ".bmp")
if isImage {
flag = true
} else {
flag = false
}
return flag
}
// CreateDirectory 判断文件夹是否存在,不存在则创建文件夹
func CreateDirectory(folderName string) error {
// 检查文件夹是否已经存在
_, err := os.Stat(folderName)
if err == nil {
return nil
}
// 创建文件夹
err = os.Mkdir(folderName, 0o755)
if err != nil {
return err
}
return nil
}
// FileToFunc file转resource/public
func FileToFunc(path string, num int) (rpath string) {
if num == 1 {
rpath = strings.Replace(path, GetCWD()+"/file/", "/resource/public/", 1)
} else if num == 2 {
rpath = strings.Replace(path, "/file/", GetCWD()+"/resource/public/", 1)
} else if num == 3 {
rpath = strings.Replace(path, "/file/", "/wxfile/", 1)
} else if num == 4 {
rpath = strings.Replace(path, "/wxfile/", GetCWD()+"/resource/public/", 1)
} else if num == 5 {
rpath = strings.Replace(path, "/wxfile/", "/file/", 1)
} else {
rpath = strings.Replace(path, "/file/", "/resource/public/", 1)
}
rpath = filepath.ToSlash(rpath)
return
}
// ResourcePublicToFunc resource/public转file
func ResourcePublicToFunc(path string, num int) (rpath string) {
if num == 1 {
rpath = strings.Replace(path, GetCWD()+"/resource/public/", "/file/", 1)
} else if num == 2 {
rpath = strings.Replace(path, "/resource/public/", GetCWD()+"/resource/public/", 1)
} else if num == 3 {
rpath = strings.Replace(path, "/resource/public/", "/wxfile/", 1)
} else {
rpath = strings.Replace(path, "/resource/public/", "/file/", 1)
}
rpath = filepath.ToSlash(rpath)
return
}
// FormatRestrictionFunc 判断文件后缀是否匹配
func FormatRestrictionFunc(path, suffix string) (flag bool) {
split := strings.Split(suffix, ",")
for _, data := range split {
extension := filepath.Ext(path)
if extension == data {
return true
}
}
return false
}
// URLCoding 对url的特俗符号进行编码不对/进行编码(定制编码,将"/file/networkDisk/completion/admin37/2.jpg"的"networkDisk/completion/admin37/2.jpg"进行编码)
func URLCoding(path string) (filenPathCoding string) {
s2 := path[6 : len(path)-1]
split := strings.Split(s2, "/")
p := ""
for i2 := range split {
p = p + url.PathEscape(split[i2]) + "/"
}
p = p[0 : len(p)-1]
filenPathCoding = strings.ReplaceAll(path, s2, p)
return
}

View File

@ -0,0 +1,199 @@
package coryCommon
import (
"fmt"
"github.com/fogleman/gg"
"github.com/nfnt/resize"
"image/color"
"log"
"os"
"path/filepath"
"strings"
)
func GetCWD() string {
cwd, _ := os.Getwd()
return filepath.ToSlash(cwd)
}
func MultiPicture(pictureList string, compere string, meetingDate string, site string, teamName string, labourserviceName string) {
//1、分割字符串
split := strings.Split(pictureList, ",")
//2、循环添加水印
for i := range split {
filePath := split[i]
newPath := strings.ReplaceAll(filePath, "/wxfile", GetCWD()+"/resource/public")
newPath = strings.ReplaceAll(newPath, "\\", "/")
WatermarkFunc(newPath, compere, meetingDate, site, teamName, labourserviceName)
}
}
// WatermarkFunc 给图片设置水印和logo
func WatermarkFunc(filePath string, compere string, meetingDate string, site string, teamName string, labourserviceName string) {
// 检查文件是否存在
_, err := os.Stat(filePath)
if err != nil {
if os.IsNotExist(err) {
fmt.Println("文件不存在")
} else {
fmt.Println("发生错误:", err)
}
return
}
//// 获取文件名和后缀
//fileName := filepath.Base(filePath)
//fileExt := filepath.Ext(filePath)
//1、加载图片
srcImage, err := gg.LoadImage(filePath)
if err != nil {
log.Fatalf("打开图片失败: %v", err)
}
//2、加载logo水印
dc := gg.NewContextForImage(srcImage)
dc.SetRGB(1, 1, 1)
logoImage, err := gg.LoadImage(GetCWD() + "/resource/cory/zmlogo.jpg")
if err != nil {
log.Fatalf("打开 logo 图片失败: %v", err)
}
logoWidth := 80.0
logoHeight := float64(logoImage.Bounds().Dy()) * (logoWidth / float64(logoImage.Bounds().Dx()))
logoImage = resize.Resize(uint(logoWidth), uint(logoHeight), logoImage, resize.Lanczos3)
x := float64(logoWidth) + 10.0
y := float64(dc.Height()/2) + 96.0
dc.DrawImageAnchored(logoImage, int(x), int(y), 1.0, 1.0)
//3、设置字体
fontPath := GetCWD() + "/resource/cory/msyh.ttc"
if err != nil {
log.Fatalf("加载字体失败: %v", err)
}
dc.SetRGB(0, 0, 0)
//4、创建矩形框 背景透明
boxText := teamName
dc.SetRGBA255(0, 99, 175, 100)
rectangleX := x
rectangleY := y - logoHeight + 1
rectangleWidth := len(boxText) * 8
rectangleHeight := logoHeight - 1
dc.DrawRectangle(rectangleX, rectangleY, float64(rectangleWidth), rectangleHeight)
dc.Fill()
textFunc(dc, boxText, fontPath, 18.0, rectangleX, rectangleY, float64(rectangleWidth), rectangleHeight, 1)
//5、添加文字水印
text := "开会宣讲人:" + compere + "\n \n" +
"开 会 时 间:" + meetingDate + "\n \n" +
//"天 气:多云转晴\n \n" +
"地 点:" + site + "\n \n"
textX := x - logoWidth
textY := y + 10
err = dc.LoadFontFace(fontPath, 12)
dc.DrawStringWrapped(text, textX, textY, 0.0, 0.0, float64(dc.Width())-textX, 1.2, gg.AlignLeft)
//6、创建矩形框 渐变透明
boxText = labourserviceName
width := len(boxText) * 8
height := 30
fromAlpha := 0.6
toAlpha := 0
for x := 0; x <= width; x++ {
alpha := fromAlpha - (fromAlpha-float64(toAlpha))*(float64(x)/float64(width))
rgba := color.RGBA{G: 99, B: 175, A: uint8(alpha * 255)}
dc.SetRGBA255(int(rgba.R), int(rgba.G), int(rgba.B), int(rgba.A))
dc.DrawLine(float64(x)+10, y+96, float64(x)+10, y+96+float64(height))
dc.StrokePreserve()
dc.Fill()
}
textFunc(dc, boxText, fontPath, 16.0, 10.0, y+96, float64(width), float64(height), 2)
//7、保存图片
err = dc.SavePNG(filePath)
if err != nil {
log.Fatalf("保存带水印的图片失败: %v", err)
}
}
func textFunc(dc *gg.Context, boxText string, fontPath string, boxTextSize, rectangleX, rectangleY, rectangleWidth, rectangleHeight float64, num int) {
err := dc.LoadFontFace(fontPath, boxTextSize)
if err != nil {
log.Fatalf("加载字体失败: %v", err)
}
dc.SetRGB(1, 1, 1)
boxTextWidth, boxTextHeight := dc.MeasureString(boxText)
boxTextX := 0.00
if num == 1 {
boxTextX = rectangleX + (rectangleWidth-boxTextWidth)/2
} else if num == 2 {
boxTextX = 10
} else {
log.Fatalf("对齐方式错误")
}
boxTextY := rectangleY + (rectangleHeight-boxTextHeight)/2 + boxTextHeight
dc.DrawStringAnchored(boxText, boxTextX, boxTextY, 0.0, 0.0)
}
// 绘制矩形框
func Test_draw_rect_text(im_path string, x, y, w, h float64) {
// Load image
//font_path := GetCWD() + "/resource/cory/msyh.ttc"
im, err := gg.LoadImage(im_path)
if err != nil {
log.Fatal(err)
}
// 2 method
dc := gg.NewContextForImage(im)
// Set color and line width
dc.SetHexColor("#FF0000")
dc.SetLineWidth(4)
// DrawRoundedRectangle 使用 DrawRoundedRectangle 方法在图像上绘制一个带有圆角的矩形。这里 x, y 是矩形左上角的坐标w, h 是矩形的宽度和高度,最后的 0 表示圆角的半径为0。
dc.DrawRoundedRectangle(x, y, w, h, 0)
// Store set
dc.Stroke()
dc.DrawRectangle(x, y, w, h)
dc.Clip()
// Save png image
dc.SavePNG(im_path)
}
type TestDrawRectTextEntity struct {
ImPath string `json:"im_path"`
Coordinates []*CoordinatesListEntity `json:"coordinates"`
}
type CoordinatesListEntity struct {
X float64 `json:"x"`
Y float64 `json:"y"`
W float64 `json:"w"`
H float64 `json:"h"`
}
// TestDrawRectTextFunc 同一文件多次绘制矩形框
func TestDrawRectTextFunc(entity *TestDrawRectTextEntity) {
if entity == nil {
return
}
if len(entity.Coordinates) == 0 {
return
}
// 加载图像
im, err := gg.LoadImage(entity.ImPath) // 加载图像一次
if err != nil {
log.Fatal(err)
}
// 创建上下文
dc := gg.NewContextForImage(im)
// 设置颜色和线宽
dc.SetHexColor("#FF0000")
dc.SetLineWidth(4)
//绘制和保存矩形
for _, zuobiao := range entity.Coordinates {
// 绘制矩形
dc.DrawRoundedRectangle(zuobiao.X, zuobiao.Y, zuobiao.W, zuobiao.H, 0)
}
dc.Stroke()
// 保存图像
dc.SavePNG(entity.ImPath)
}

View File

@ -0,0 +1,166 @@
package coryCommon
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
commonService "github.com/tiger1103/gfast/v3/internal/app/common/service"
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
"github.com/tiger1103/gfast/v3/internal/app/system/model"
wxModel "github.com/tiger1103/gfast/v3/internal/app/wxApplet/model"
tool "github.com/tiger1103/gfast/v3/utility/coryUtils"
"strconv"
"strings"
"time"
)
// 微信小程序的消息订阅 https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscribe-message/sendMessage.html
type TokenEntity struct {
AccessToken string `json:"accessToken"`
ExpiresIn string `json:"expiresIn"`
}
func GetAccessToken() (token string, err error) {
var te *TokenEntity
appId, _ := g.Cfg().Get(gctx.New(), "wx.appId")
appSecret, _ := g.Cfg().Get(gctx.New(), "wx.appSecret")
key := "weChatAccessToken"
//从缓存捞取key
ctx := gctx.New()
get := commonService.Cache().Get(ctx, key)
if get != nil && get.String() != "" {
token = get.String()
return "", err
} else {
uri := "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId.String() + "&secret=" + appSecret.String()
response, err := g.Client().Get(gctx.New(), uri)
if err != nil {
return "", err
} else {
allString := response.ReadAllString()
err := json.Unmarshal([]byte(allString), &te)
if err != nil {
return "", err
} else {
//将token存储到redis中tiken默认时间为秒实际计算为2小时,(这里少100秒,防止token过期还存在redis中)
num, err := strconv.Atoi(te.ExpiresIn)
if err != nil {
err = errors.New("过期时间转换失败!")
return "", err
}
commonService.Cache().Set(ctx, key, te.AccessToken, time.Duration(num-100)*time.Second)
token = te.AccessToken
return token, err
}
}
}
}
type AppletSubscription struct {
Touser string `json:"touser"`
TemplateId string `json:"template_id"`
MiniprogramState string `json:"MiniprogramState"`
Data DataEntity `json:"data"`
}
type DataEntity struct {
Date1 ValueEntity `json:"date1"`
Thing2 ValueEntity `json:"thing2"`
Thing3 ValueEntity `json:"thing3"`
}
type ValueEntity struct {
Value string `json:"value"`
}
type MsgEntity struct {
ErrCode int `json:"errcode"`
ErrMsg int64 `json:"errmsg"`
MsgId string `json:"msgid"`
}
// Subscription 微信服务通知(消息订阅)
func Subscription(openid string) (msgEntity *MsgEntity, err error) {
//1、获取token
token, err := GetAccessToken()
if err != nil {
fmt.Println("获取微信凭证错误!")
return
}
//2、组装数据
//now := time.Now()
var entity = new(AppletSubscription)
entity.Touser = openid
entity.TemplateId = "EyBO6gWizF5HwUThYSSm_HuQWgfMrwEkVHPXeEq1Me8"
entity.MiniprogramState = "trial"
var dataEntity = DataEntity{}
dataEntity.Date1 = ValueEntity{Value: time.Now().Format("2006-01-02 15:04:05")}
dataEntity.Thing2 = ValueEntity{Value: "您今日还有打卡未完成!"}
dataEntity.Thing3 = ValueEntity{Value: "请登录中煤小程序进行打卡操作!"}
entity.Data = dataEntity
marshal, _ := json.Marshal(entity)
//3、发起请求
msgEntity = new(MsgEntity)
uri := "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + token
post, err := g.Client().Post(gctx.New(), uri, marshal)
postBytes, err := json.Marshal(post)
if err != nil {
fmt.Println("JSON marshaling error:", err)
return
}
if err := json.Unmarshal(postBytes, &msgEntity); err != nil {
fmt.Println("解析JSON错误:", err)
return nil, err
}
return
}
func ServiceNoticeFunc(ctx context.Context) {
//1、获取所有项目的打卡范围
var projectEntity []*model.SysProjectListRes
err := dao.SysProject.Ctx(ctx).
Where("status", 0).
Where("show_hidden", 1).
Fields("id,project_name,punch_range").Scan(&projectEntity)
if err != nil {
fmt.Println("获取项目失败!")
}
//2、遍历项目获取打卡范围
for _, pData := range projectEntity {
// 3、每个项目都有两个临时触发器
punchRange := pData.PunchRange
split := strings.Split(punchRange, ",")
for _, cfq := range split {
dn, err := tool.TimeStr(cfq)
if err != nil {
fmt.Println(err)
}
//创建临时定时器
if dn > 0 {
tempTimer := time.NewTimer(dn)
// 在新的 goroutine 中等待临时定时器触发
go func() {
<-tempTimer.C
//4、就获取当前项目下面的所有成员条件为subscription为1的数据 下发数据,并存储下发数据的状态
var openidList []*wxModel.BusConstructionUserListRes
dao.BusConstructionUser.Ctx(ctx).
Fields("openid").
Where("subscription", "1").
Where("status = 0").
Where("entry_date is not null and entry_date!='' and (leave_date is null or leave_date = '')").
Where("project_id", pData.Id).
Scan(&openidList)
for _, oi := range openidList {
Subscription(oi.Openid)
}
}()
}
}
}
}

View File

@ -0,0 +1,110 @@
package coryCommon
import (
"encoding/json"
"fmt"
"github.com/gogf/gf/v2/net/gclient"
"github.com/gogf/gf/v2/os/gctx"
)
var key = "00b60ebda96849e694cb570e3d4f5c89"
// WeatherRep 返回参数 免费天气查询 https://dev.qweather.com/docs/api/weather/weather-daily-forecast/
type WeatherRep struct {
Code string `json:"code"`
UpdateTime string `json:"updateTime"`
FxLink string `json:"fxLink"`
Daily []struct {
FxDate string `json:"fxDate"`
Sunrise string `json:"sunrise"`
Sunset string `json:"sunset"`
Moonrise string `json:"moonrise"`
Moonset string `json:"moonset"`
MoonPhase string `json:"moonPhase"`
MoonPhaseIcon string `json:"moonPhaseIcon"`
TempMax string `json:"tempMax"`
TempMin string `json:"tempMin"`
IconDay string `json:"iconDay"`
TextDay string `json:"textDay"`
IconNight string `json:"iconNight"`
TextNight string `json:"textNight"`
Wind360Day string `json:"wind360Day"`
WindDirDay string `json:"windDirDay"`
WindScaleDay string `json:"windScaleDay"`
WindSpeedDay string `json:"windSpeedDay"`
Wind360Night string `json:"wind360Night"`
WindDirNight string `json:"windDirNight"`
WindScaleNight string `json:"windScaleNight"`
WindSpeedNight string `json:"windSpeedNight"`
Humidity string `json:"humidity"`
Precip string `json:"precip"`
Pressure string `json:"pressure"`
Vis string `json:"vis"`
Cloud string `json:"cloud"`
UvIndex string `json:"uvIndex"`
} `json:"daily"`
Refer struct {
Sources []string `json:"sources"`
License []string `json:"license"`
} `json:"refer"`
}
// Weather 传递经纬度 location := "116.41,39.92"
func Weather(location string) (we string) {
//请求路径
//key := ""
requestURL := "https://devapi.qweather.com/v7/weather/3d?location=" + location + "&key=" + key
response, err := gclient.New().ContentJson().ContentType("application/x-www-form-urlencoded").Get(gctx.New(), requestURL)
if err != nil {
return
}
var dataInfo = ""
dataInfo = response.ReadAllString()
return dataInfo
}
type GridPointRes struct {
Code string `json:"code"`
UpdateTime string `json:"updateTime"`
FxLink string `json:"fxLink"`
Now struct {
ObsTime string `json:"obsTime"`
Temp string `json:"temp"`
Icon string `json:"icon"`
Text string `json:"text"`
Wind360 string `json:"wind360"`
WindDir string `json:"windDir"`
WindScale string `json:"windScale"`
WindSpeed string `json:"windSpeed"`
Humidity string `json:"humidity"`
Precip string `json:"precip"`
Pressure string `json:"pressure"`
Cloud string `json:"cloud"`
Dew string `json:"dew"`
} `json:"now"`
Refer struct {
Sources []string `json:"sources"`
License []string `json:"license"`
} `json:"refer"`
}
// 格点天气 GridPoint
func GridPoint(location string) (gp *GridPointRes, err error) {
//请求路径
//key := "00b60ebda96849e694cb570e3d4f5c89"
requestURL := "https://devapi.qweather.com/v7/grid-weather/now?location=" + location + "&key=" + key
response, err := gclient.New().ContentJson().ContentType("application/x-www-form-urlencoded").Get(gctx.New(), requestURL)
if err != nil {
return
}
var dataInfo = ""
dataInfo = response.ReadAllString()
gp = new(GridPointRes)
err = json.Unmarshal([]byte(dataInfo), &gp)
fmt.Println(gp.Now.Icon)
return
}

View File

@ -0,0 +1,621 @@
package coryCommon
import (
"archive/zip"
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
//解压文件、文件夹复制
// FileZipFunc 对文件进行解压
func FileZipFunc(relativePath string, filenPath string, template string) (fileName string, err error) {
// 打开压缩文件
zipFile, err := zip.OpenReader(relativePath)
if err != nil {
fmt.Println("无法打开压缩文件只支持ZIP:", err)
return
}
var i = 0
var name = ""
//进入的压缩文件,判断压缩文件中的第一层是否包含文件,如果有就在当前随机产生一个文件夹,返回就返回到随机文件夹的位置
randomFolder := ""
// 遍历压缩文件中的文件头信息
for _, f := range zipFile.File {
//path, _ := gbkDecode(f.Name)
fmt.Println("头? ", f.Name)
path, _ := IsGB18030(f.Name)
// 判断是否为顶层文件夹
if !hasSlash(path) {
randomFolder = FileName("/randomFolder")
template = template + randomFolder
filenPath = filenPath + randomFolder
break
}
}
// 遍历压缩文件中的文件
for _, file := range zipFile.File {
fmt.Println("ti ", file.Name)
// 解决文件名编码问题
if i == 0 {
gb18030, _ := IsGB18030(file.Name)
if err != nil {
return "", err
}
name = gb18030
}
file.Name, err = IsGB18030(file.Name)
if err != nil {
return "", err
}
//对所有文件的名称进行空格替换
file.Name = strings.Replace(file.Name, " ", "", -1)
if err != nil {
return file.Name, err
}
// 获取文件的相对路径,根据原本路径来操作还是根据新路径来filenPath
extractedFilePath := ""
extractedFilePathTwo := ""
if filenPath != "" {
extractedFilePath = filepath.Join(FileToFunc(filenPath, 2), "/"+file.Name)
split := strings.Split(filenPath, "/")
extractedFilePathTwo = extractedFilePath + "/" + split[len(split)-1]
} else {
extractedFilePath = filepath.ToSlash(filepath.Join(GetCWD()+template+"/"+".", file.Name))
extractedFilePathTwo = extractedFilePath + "/"
}
//判断文件夹是否存在存在就退出i==0 是为了只判断最外层那一文件夹路径)
_, err := os.Stat(filepath.Dir(extractedFilePathTwo))
if err == nil && i == 0 {
zipFile.Close()
// 删除压缩文件
err = os.Remove(relativePath)
err = errors.New("当前文件夹已经存在,导入无效!")
return "", err
}
i = i + 1
// 检查是否为文件
if !file.FileInfo().IsDir() {
// 创建文件的目录结构
err = os.MkdirAll(filepath.Dir(extractedFilePath), os.ModePerm)
if err != nil {
fmt.Println("无法创建目录:", err)
return "", err
}
// 打开压缩文件中的文件
zippedFile, err := file.Open()
if err != nil {
fmt.Println("无法打开压缩文件中的文件:", err)
return "", err
}
defer zippedFile.Close()
// 创建目标文件
extractedFile, err := os.Create(extractedFilePath)
if err != nil {
fmt.Println("无法创建目标文件:", err)
return "", err
}
defer extractedFile.Close()
// 将压缩文件中的内容复制到目标文件
_, err = io.Copy(extractedFile, zippedFile)
if err != nil {
fmt.Println("无法解压缩文件:", err)
return "", err
}
}
}
zipFile.Close()
// 删除压缩文件
err = os.Remove(relativePath)
if err != nil {
fmt.Println("无法删除压缩文件:", err)
return
}
fileName = strings.Split(name, "/")[0]
if randomFolder != "" {
fileName = ""
} else {
fileName = "/" + fileName
}
if filenPath != "" {
return FileToFunc(filenPath, 2) + fileName, err
} else {
return GetCWD() + template + fileName, err
}
}
// IsGB18030 判断字符串是否是 GB18030 编码
func IsGB18030(name string) (string, error) {
// 创建 GB18030 解码器
decoder := simplifiedchinese.GB18030.NewDecoder()
// 使用 transform 解码数据
_, err := io.ReadAll(transform.NewReader(bytes.NewReader([]byte(name)), decoder))
if err == nil {
return name, nil
} else {
fileName, errName := simplifiedchinese.GB18030.NewDecoder().String(name)
return fileName, errName
}
}
// gbkDecode 解决文件名乱码
func gbkDecode(s string) (string, error) {
gbkDecoder := simplifiedchinese.GBK.NewDecoder()
decodedName, _, err := transform.String(gbkDecoder, s)
return decodedName, err
}
type DocumentListPublicRes struct {
Id int64 `json:"id"`
IdStr string `json:"idStr"`
Pid string `json:"pid"`
Name string `json:"name"`
FilenPath string `json:"filenPath"`
FilenPathCoding string `json:"filenPathCoding"`
Suffix string `json:"suffix"`
Type string `json:"type"`
CreateBy string `json:"createBy"`
CreatedAt *gtime.Time `json:"createdAt"`
IsDuplicate bool `json:"isDuplicate"`
ProjectId int64 `json:"projectId"`
}
// Traversal 遍历文件夹 ctx绝对路径上级文件夹可有可无表名存储位置,项目id,模板1or资料2
//
// one, err := coryCommon.Traversal(ctx, path, req.Pid, dao.DocumentCompletion.Table(), dataFolder, req.ProjectId, "2") //遍历解压后的文件,插入数据
// if err != nil {
// liberr.ErrIsNil(ctx, err)
// return
// }
// _, err = g.DB().Model(dao.DocumentCompletion.Table()).Ctx(ctx).Insert(one)
// liberr.ErrIsNil(ctx, err, "新增失败!")
func Traversal(ctx context.Context, root string, pidstr string, tableName string, templatePath string, projectId int64, num string) (dataOne []*DocumentListPublicRes, err error) {
template := strings.Replace(templatePath, "/resource/public", "/file", 1)
err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 获取相对路径
relativePath, err := filepath.Rel(root, path)
if err != nil {
return err
}
// 根目录下创建,还是指定文件夹下面创建?
p := ""
if pidstr != "" {
value, _ := g.DB().Model(tableName).Ctx(ctx).Where("id_str", pidstr).Fields("filen_path").Value()
split := strings.Split(root, "/")
p = value.String() + "/" + split[len(split)-1] + "/" + relativePath
} else {
p = template + "/" + relativePath
}
p = strings.ReplaceAll(p, "\\", "/")
// 获取当前项的深度
depth := strings.Count(relativePath, string(filepath.Separator))
// 判断父子关系并打印结果
if depth == 0 && info.IsDir() {
if relativePath == "." {
split := strings.Split(root, "/")
n := split[len(split)-1]
// 根目录下创建,还是指定文件夹下面创建?
p := ""
if pidstr != "" {
value, _ := g.DB().Model(tableName).Ctx(ctx).Where("id_str", pidstr).Fields("filen_path").Value()
p = value.String() + "/" + n
} else {
p = template + "/" + n
}
p = strings.ReplaceAll(p, "\\", "/")
template = template + "/" + n
var dataTwo = new(DocumentListPublicRes)
dataTwo.IdStr = SHA256(p)
if pidstr != "" {
dataTwo.Pid = pidstr
} else {
dataTwo.Pid = "0"
}
dataTwo.Name = n
dataTwo.FilenPath = p
////如果文件夹路径重复,就提示 解压文件夹的时候就已经判断了,这里就不需要了
//err := IsFolderExist(ctx, p)
//if err != nil {
// return err
//}
dataTwo.Type = "2"
dataOne = append(dataOne, dataTwo)
} else {
dir, n := filepath.Split(p)
dir = strings.TrimSuffix(dir, "/")
var dataTwo = new(DocumentListPublicRes)
dataTwo.IdStr = SHA256(p)
dataTwo.Pid = SHA256(dir)
dataTwo.Name = n
dataTwo.FilenPath = p
////如果文件夹路径重复,就提示
//err := IsFolderExist(ctx, p)
//if err != nil {
// return err
//}
dataTwo.Type = "2"
dataOne = append(dataOne, dataTwo)
}
} else if info.IsDir() {
// 子文件夹
dir, n := filepath.Split(p)
dir = strings.TrimSuffix(dir, "/")
var dataTwo = new(DocumentListPublicRes)
dataTwo.IdStr = SHA256(p)
dataTwo.Pid = SHA256(dir)
dataTwo.Name = n
dataTwo.FilenPath = p
dataTwo.Type = "2"
dataOne = append(dataOne, dataTwo)
} else {
dir, n := filepath.Split(p)
dir = strings.TrimSuffix(dir, "/")
var dataTwo = new(DocumentListPublicRes)
dataTwo.Pid = SHA256(dir)
lastDotIndex := strings.LastIndex(n, ".")
if lastDotIndex == -1 || lastDotIndex == 0 {
dataTwo.Name = strings.Split(n, ".")[0]
} else {
dataTwo.Name = n[:lastDotIndex]
}
dataTwo.Suffix = n[lastDotIndex:]
dataTwo.FilenPath = p
dataTwo.Type = "1"
//文件只能是这三种类型,其他类型进不来
s := n[lastDotIndex:]
if num == "1" { //资料有格式限制
if strings.EqualFold(s, ".xls") || strings.EqualFold(s, ".xlsx") || strings.EqualFold(s, ".docx") || strings.EqualFold(s, ".doc") || strings.EqualFold(s, ".pptx") || strings.EqualFold(s, ".ppt") {
dataOne = append(dataOne, dataTwo)
}
} else {
dataOne = append(dataOne, dataTwo)
}
}
return err
})
if err != nil {
//fmt.Println("遍历文件夹时发生错误:", err)
return nil, err
}
// 有项目id表示资料 无表模板
if projectId > 0 {
for i := range dataOne {
dataOne[i].ProjectId = projectId
}
}
return
}
func SHA256(str string) (hx string) {
// 创建 SHA-256 哈希对象
hash := sha256.New()
// 将字符串转换为字节数组并进行哈希计算
hash.Write([]byte(str))
// 计算 SHA-256 哈希值
hashedBytes := hash.Sum(nil)
// 将哈希值转换为十六进制字符串
hashStr := hex.EncodeToString(hashedBytes)
return hashStr
}
// CopyFile 文件复制
func CopyFile(src, dst string) error {
// 打开源文件
inputFile, err := os.Open(src)
if err != nil {
return err
}
defer inputFile.Close()
// 创建目标文件
outputFile, err := os.Create(dst)
if err != nil {
return err
}
defer outputFile.Close()
// 通过 Copy 函数实现拷贝功能
_, err = io.Copy(outputFile, inputFile)
if err != nil {
return err
}
// 确保文件内容被刷新到磁盘上
err = outputFile.Sync()
if err != nil {
return err
}
return nil
}
// CopyDirectory 文件夹复制
func CopyDirectory(src string, dest string) error {
// 检查源文件夹是否存在
_, err := os.Stat(src)
if err != nil {
return err
}
// 检查目标文件夹是否存在,不存在则创建
err = os.MkdirAll(dest, 0755)
if err != nil {
return err
}
// 遍历源文件夹
files, err := os.ReadDir(src)
if err != nil {
return err
}
for _, file := range files {
srcPath := src + "/" + file.Name()
destPath := dest + "/" + file.Name()
// 判断文件类型
if file.IsDir() {
// 如果是文件夹,则递归调用 copyDirectory 函数复制文件夹及其子文件
err = CopyDirectory(srcPath, destPath)
if err != nil {
return err
}
} else {
// 如果是文件,则复制文件到目标文件夹
inputFile, err := os.Open(srcPath)
if err != nil {
return err
}
defer inputFile.Close()
outputFile, err := os.Create(destPath)
if err != nil {
return err
}
defer outputFile.Close()
_, err = io.Copy(outputFile, inputFile)
if err != nil {
return err
}
}
}
return nil
}
// MoveFile 文件移动
func MoveFile(source, destination string) (err error) {
// 执行移动操作
err = os.Rename(source, destination)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("File moved successfully!")
}
return err
}
// MoveFolder 文件夹下面的文件及子文件全部移动到新文件夹下
func MoveFolder(srcPath, destPath string) error {
// 获取源文件夹下的所有文件和子文件夹
fileList := []string{}
err := filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error {
fileList = append(fileList, path)
return nil
})
if err != nil {
return err
}
// 移动每个文件和子文件夹
for _, file := range fileList {
// 获取相对路径
relPath, err := filepath.Rel(srcPath, file)
if err != nil {
return err
}
// 构建目标路径
destFile := filepath.Join(destPath, relPath)
// 判断是文件还是文件夹
if fileInfo, err := os.Stat(file); err == nil && fileInfo.IsDir() {
// 如果是文件夹,创建目标文件夹
err := os.MkdirAll(destFile, os.ModePerm)
if err != nil {
return err
}
} else {
// 如果是文件,复制文件
err := copyFile(file, destFile)
if err != nil {
return err
}
}
}
// 移动完成后删除源文件夹
return os.RemoveAll(srcPath)
}
func copyFile(src, dest string) error {
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
destFile, err := os.Create(dest)
if err != nil {
return err
}
defer destFile.Close()
_, err = io.Copy(destFile, srcFile)
return err
}
// FolderToZip 将给定文件夹压缩成压缩包存储到另外一个路径(参数:源数据、目标路径)
func FolderToZip(folderToZip, zipFile string) (err error) {
// 创建一个新的压缩包文件
newZipFile, err := os.Create(zipFile)
if err != nil {
return err
}
defer newZipFile.Close()
// 创建一个 zip.Writer 来向压缩包中写入内容
zipWriter := zip.NewWriter(newZipFile)
defer zipWriter.Close()
// 递归遍历文件夹并将其中的文件和目录添加到压缩包中
err = filepath.Walk(folderToZip, func(filePath string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 获取当前文件的相对路径
relativePath, err := filepath.Rel(folderToZip, filePath)
if err != nil {
return err
}
// 如果是目录,则创建一个目录项
if info.IsDir() {
_, err = zipWriter.Create(relativePath + "/")
if err != nil {
return err
}
return nil
}
// 如果是文件,则创建一个文件项并写入文件内容
fileData, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
file, err := zipWriter.Create(relativePath)
if err != nil {
return err
}
_, err = file.Write(fileData)
if err != nil {
return err
}
return nil
})
return
}
// zipDirFiles 压缩文件
func ZipDirFiles(src_dir string, zip_file_name string) error {
// 检查并创建目标目录
err := os.MkdirAll(filepath.Dir(zip_file_name), os.ModePerm)
if err != nil {
return err
}
// 删除空的zip而不是直接使用os.RemoveAll以提高安全性
err = os.Remove(zip_file_name)
if err != nil && !os.IsNotExist(err) {
return err
}
// 创建新的zip文件
zipfile, err := os.Create(zip_file_name)
if err != nil {
return err
}
defer zipfile.Close()
// 初始化zip写入器
archive := zip.NewWriter(zipfile)
defer archive.Close()
// 遍历源目录下的文件和子目录
return filepath.Walk(src_dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 计算相对于源目录的路径
relPath, _ := filepath.Rel(src_dir, path)
if path == src_dir {
// 如果是源目录本身,跳过
return nil
}
// 创建zip文件头
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = filepath.ToSlash(relPath)
// 标记目录
if info.IsDir() {
header.Name += "/"
return nil
}
// 处理文件
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
writer, err := archive.CreateHeader(header)
if err != nil {
return err
}
_, err = io.Copy(writer, file)
return err
})
}
// 压缩后删除源文件
func DeleteFolderToZip(srcpath, destpathzip string) (err error) {
//srcpath := filepath.ToSlash(`D:\GiteeProject\gsproject\zmkg-back\resource\public\temporary\del`)
//destpathzip := filepath.ToSlash(`D:\GiteeProject\gsproject\zmkg-back\resource\public\temporary\yy.zip`)
err = ZipDirFiles(srcpath, destpathzip) // 文件压缩 压缩文件目录 和压缩文件zip
if err != nil {
return err
}
// 删除原文件
if err := os.RemoveAll(srcpath); err != nil {
if !os.IsNotExist(err) {
fmt.Printf("Error removing existing file %s: %v\n", srcpath, err)
return err
}
}
return err
}
// 判断字符串中是否包含斜杠
func hasSlash(s string) bool {
for _, c := range s {
if c == '/' || c == '\\' {
return true
}
}
return false
}