初始
This commit is contained in:
491
api/v1/common/coryCommon/baiduOCR.go
Normal file
491
api/v1/common/coryCommon/baiduOCR.go
Normal 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"` // 二选一:图片完整URL,URL长度不超过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
|
||||
}
|
104
api/v1/common/coryCommon/base64ToImg.go
Normal file
104
api/v1/common/coryCommon/base64ToImg.go
Normal 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
|
||||
}
|
107
api/v1/common/coryCommon/basics.go
Normal file
107
api/v1/common/coryCommon/basics.go
Normal 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
|
||||
}
|
17
api/v1/common/coryCommon/camera/cameraEntity.go
Normal file
17
api/v1/common/coryCommon/camera/cameraEntity.go
Normal 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"`
|
||||
}
|
303
api/v1/common/coryCommon/camera/cameraUtil.go
Normal file
303
api/v1/common/coryCommon/camera/cameraUtil.go
Normal 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"`
|
||||
}
|
58
api/v1/common/coryCommon/constant.go
Normal file
58
api/v1/common/coryCommon/constant.go
Normal 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"
|
277
api/v1/common/coryCommon/coordinateTransform.go
Normal file
277
api/v1/common/coryCommon/coordinateTransform.go
Normal 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
|
||||
}
|
329
api/v1/common/coryCommon/downloadFile.go
Normal file
329
api/v1/common/coryCommon/downloadFile.go
Normal 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
|
||||
}
|
515
api/v1/common/coryCommon/excelUtil/excel.go
Normal file
515
api/v1/common/coryCommon/excelUtil/excel.go
Normal 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
|
||||
}
|
55
api/v1/common/coryCommon/excelUtil/excelEntity.go
Normal file
55
api/v1/common/coryCommon/excelUtil/excelEntity.go
Normal 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
|
||||
}
|
209
api/v1/common/coryCommon/imgData.go
Normal file
209
api/v1/common/coryCommon/imgData.go
Normal 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)
|
||||
}
|
38
api/v1/common/coryCommon/largeNumber.go
Normal file
38
api/v1/common/coryCommon/largeNumber.go
Normal 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
|
||||
}
|
111
api/v1/common/coryCommon/map.go
Normal file
111
api/v1/common/coryCommon/map.go
Normal 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}
|
||||
}
|
23
api/v1/common/coryCommon/md5.go
Normal file
23
api/v1/common/coryCommon/md5.go
Normal 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
|
||||
}
|
69
api/v1/common/coryCommon/restapi.go
Normal file
69
api/v1/common/coryCommon/restapi.go
Normal 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"`
|
||||
}
|
473
api/v1/common/coryCommon/uploadFile.go
Normal file
473
api/v1/common/coryCommon/uploadFile.go
Normal 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
|
||||
}
|
199
api/v1/common/coryCommon/watermark.go
Normal file
199
api/v1/common/coryCommon/watermark.go
Normal 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)
|
||||
}
|
166
api/v1/common/coryCommon/weChatNewsFeeds.go
Normal file
166
api/v1/common/coryCommon/weChatNewsFeeds.go
Normal 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)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
110
api/v1/common/coryCommon/weather.go
Normal file
110
api/v1/common/coryCommon/weather.go
Normal 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
|
||||
}
|
621
api/v1/common/coryCommon/zip.go
Normal file
621
api/v1/common/coryCommon/zip.go
Normal 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
|
||||
}
|
Reference in New Issue
Block a user