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

View File

@ -0,0 +1,4 @@
package process
type ProcessApi struct {
}

47
api/app/process/req.go Normal file
View File

@ -0,0 +1,47 @@
package process
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// 新增进度
type CreateProcessReq struct {
g.Meta `path:"/process/create" method:"post" tags:"APP(里程碑进度上报)" summary:"新增里程碑进度"`
Stage string `json:"stage" v:"required" dc:"上传阶段名称"`
ProjectId int64 `json:"projectId" v:"required#该用户暂未绑定项目" dc:"项目ID"`
Percentage int `json:"percentage" dc:"完成情况1已完成 0未完成"`
Notes string `json:"notes" dc:"备注"`
CompleteTime gtime.Time `json:"completeTime" dc:"完成时间"`
}
// 获取进度列表
type ProcessListReq struct {
g.Meta `path:"/process/list" method:"get" tags:"APP(里程碑进度上报)" summary:"获取里程碑进度列表"`
ProjectId int64 `json:"projectId" v:"required" dc:"项目ID"`
Page int `json:"page" v:"required" dc:"页码"`
PageSize int `json:"pageSize" v:"required" dc:"每页大小"`
}
// 更新进度信息
type UpdateProcessReq struct {
g.Meta `path:"/process/update" method:"put" tags:"APP(里程碑进度上报)" summary:"更新里程碑进度信息"`
ProcessID int64 `json:"processId" v:"required" dc:"进度ID"`
Stage string `json:"stage" v:"required" dc:"上传阶段名称"`
ProjectId int64 `json:"projectId" v:"required" dc:"项目ID"`
Percentage int `json:"percentage" dc:"完成情况1已完成 0未完成"`
Notes string `json:"notes" dc:"备注"`
CompleteTime gtime.Time `json:"completeTime" dc:"完成时间"`
}
// 删除进度
type DeleteProcessReq struct {
g.Meta `path:"/process/delete" method:"delete" tags:"APP(里程碑进度上报)" summary:"删除里程碑进度"`
ProcessID int64 `json:"processId" v:"required" dc:"进度ID"`
}
// 获取详情
type ProcessDetailReq struct {
g.Meta `path:"/process/detail" method:"delete" tags:"APP(里程碑进度上报)" summary:"获取里程碑进度详情"`
ProcessID int64 `json:"processId" v:"required" dc:"进度ID"`
}

56
api/app/process/res.go Normal file
View File

@ -0,0 +1,56 @@
package process
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"time"
)
// 新增进度
type CreateProcessRes struct {
}
// 获取列表
type ProcessListRes struct {
g.Meta `mime:"application/json"`
Processes []ProcessVo `json:"processes"`
}
// Process 表的结构体定义
type Process struct {
g.Meta `mime:"application/json"`
ProcessID int64 `json:"processId" dc:"主键ID"`
ProjectId int64 `json:"projectId" dc:"项目ID"`
Stage string `json:"stage" dc:"阶段"`
Percentage int `json:"percentage" dc:"进度"`
Notes string `json:"notes" dc:"备注"`
CreatedAt time.Time `json:"createdAt" dc:"创建时间"`
UpdatedAt time.Time `json:"updatedAt" dc:"更新时间"`
CompleteTime gtime.Time `json:"completeTime" dc:"完成时间"`
CreatedBy int `json:"createdBy" dc:"录入人"`
}
// Process 表的结构体定义
type ProcessVo struct {
g.Meta `mime:"application/json"`
ProcessID int64 `json:"processId" dc:"主键ID"`
ProjectId int64 `json:"projectId" dc:"项目ID"`
Stage string `json:"stage" dc:"阶段"`
Percentage int `json:"percentage" dc:"进度"`
Notes string `json:"notes" dc:"备注"`
CreatedAt time.Time `json:"createdAt" dc:"创建时间"`
UpdatedAt time.Time `json:"updatedAt" dc:"更新时间"`
CompleteTime gtime.Time `json:"completeTime" dc:"完成时间"`
CreatedBy int `json:"createdBy" dc:"录入人ID"`
CreatedByName string `json:"createdByName" dc:"录入人姓名"`
}
type UpdateProcessRes struct {
}
type DeleteProcessRes struct {
}
type ProcessDetailRes struct {
ProcessInfo ProcessVo `json:"processInfo"`
}

View File

@ -0,0 +1,74 @@
package process
import (
"context"
"github.com/gogf/gf/v2/frame/g"
ct "github.com/tiger1103/gfast/v3/internal/app/system/logic/context"
)
func (p ProcessApi) CreateProcess(ctx context.Context, req *CreateProcessReq) (res *CreateProcessRes, err error) {
res = new(CreateProcessRes)
// 获取当前登陆用户的ID
userID := ct.New().GetUserId(ctx)
param := g.Map{
"stage": req.Stage,
"project_id": req.ProjectId,
"percentage": req.Percentage,
"complete_time": req.CompleteTime,
"notes": req.Notes,
"created_by": userID,
}
// 插入数据
_, err = g.Model("process").Ctx(ctx).Insert(param)
if err != nil {
return nil, err
}
return res, nil
}
// 根据项目列表查询里程碑数据
func (p ProcessApi) GetProcessList(ctx context.Context, req *ProcessListReq) (res *ProcessListRes, err error) {
res = new(ProcessListRes)
err = g.Model("process").Ctx(ctx).Fields("process.*,sys_user.user_nickname AS createdByName").
LeftJoin("sys_user on sys_user.id = process.created_by").
Where("project_id = ?", req.ProjectId).Page(req.Page, req.PageSize).Scan(&res.Processes)
if err != nil {
return nil, err
}
return res, nil
}
func (p ProcessApi) UpdateProcess(ctx context.Context, req *UpdateProcessReq) (res *UpdateProcessRes, err error) {
res = new(UpdateProcessRes)
_, err = g.Model("process").Ctx(ctx).Data(g.Map{
"stage": req.Stage,
"project_id": req.ProjectId,
"percentage": req.Percentage,
"complete_time": req.CompleteTime,
"notes": req.Notes,
}).Where("process_id = ?", req.ProcessID).Update()
if err != nil {
return nil, err
}
return res, nil
}
func (p ProcessApi) DeleteProcess(ctx context.Context, req *DeleteProcessReq) (res *DeleteProcessRes, err error) {
res = new(DeleteProcessRes)
_, err = g.Model("process").Ctx(ctx).Where("process_id", req.ProcessID).Delete()
if err != nil {
return nil, err
}
return res, nil
}
func (p ProcessApi) ProcessDetail(ctx context.Context, req *ProcessDetailReq) (res *ProcessDetailRes, err error) {
res = new(ProcessDetailRes)
g.Model("process").Ctx(ctx).Fields("process.*,sys_user.user_nickname AS createdByName").
InnerJoin("sys_user on sys_user.id = process.created_by").
Scan(&res.ProcessInfo)
if err != nil {
return nil, err
}
return res, nil
}

View File

@ -0,0 +1,4 @@
package project
type Project struct {
}

44
api/app/project/req.go Normal file
View File

@ -0,0 +1,44 @@
package project
import "github.com/gogf/gf/v2/frame/g"
// 查询所有项目列表数据
type ProjectIndexModuleReq struct {
g.Meta `path:"index" method:"get" tags:"APP(项目相关)" summary:"项目列表数据【首页列表】"`
ProjectName string `json:"projectName" dc:"模糊搜索项目名"`
PageNum int `json:"pageNum" dc:"页码" v:"required"`
PageSize int `json:"pageSize" dc:"每页数量" v:"required"`
}
// 根据项目ID查询详情【基础数据】
type ProjectIndexModuleDetailReq struct {
g.Meta `path:"base" method:"get" tags:"APP(项目相关)" summary:"项目详情数据【基础数据】"`
ProjectId string `json:"projectId" v:"required" dc:"项目ID"`
}
// 根据项目ID查询详情【项目计划】
type ProjectPlanDetailReq struct {
g.Meta `path:"plan" method:"get" tags:"APP(项目相关)" summary:"项目详情数据【项目计划】"`
ProjectId string `json:"projectId" v:"required" dc:"项目ID"`
}
// 根据项目ID查询详情【视频监控】
type ProjectVideoDetailReq struct {
g.Meta `path:"video" method:"get" tags:"APP(项目相关)" summary:"项目详情数据【视频监控】"`
ProjectId string `json:"projectId" v:"required" dc:"项目ID"`
}
// 根据项目ID查询详情【施工日志】
type ProjectLogReq struct {
g.Meta `path:"log" method:"get" tags:"APP(项目相关)" summary:"项目详情数据【施工日志】"`
ProjectId int64 `json:"projectId" v:"required" dc:"项目ID"`
}
// 根据项目ID查询详情【计划详情】
type ProjectPlanDetailStatusReq struct {
g.Meta `path:"/planDetail" tags:"APP(项目相关)" method:"get" summary:"项目详情数据【计划详情】"`
ProjectID int `json:"projectId"`
Status int `json:"status" dc:"任务状态 0:待开始 1:进行中 2:已完成 3:滞后"`
PageNum int `json:"pageNum" dc:"页码" v:"required"`
PageSize int `json:"pageSize" dc:"每页数量" v:"required"`
}

126
api/app/project/res.go Normal file
View File

@ -0,0 +1,126 @@
package project
import (
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/tiger1103/gfast/v3/api/v1/common"
)
// 项目列表响应数据
type ProjectListRes struct {
ProjectTotal int `json:"projectTotal"` // 项目总数
BuildProject int `json:"buildProject"` // 在建项目数量
ProjectCapacity int `json:"projectCapacity"` // 项目容量
ProjectListModels []ProjectListModel `json:"projectListModels"` // 列表数据
}
type ProjectListModel struct {
ProjectID int `json:"projectId"` // 项目ID
ProjectName string `json:"projectName"` // 项目名称
ProductionDays int `json:"productionDays"` // 安全生产天数
Status string `json:"status"` // 项目状态
TotalQuantity int `json:"totalQuantity"` // 总数量
CompletedQuantity int `json:"completedQuantity"` // 已完成数量
ProjectLeader string `json:"projectLeader"` // 项目负责人
OnStreamTime string `json:"onStreamTime"` // 开工时间
}
// 项目详情响应数据【基础数据部分】
type ProjectDetailModelRes struct {
ProjectID int `json:"projectId"` // 项目ID
ProjectName string `json:"projectName"` // 项目名称
ProjectLeader string `json:"projectLeader"` // 项目负责人
OnStreamTime string `json:"onStreamTime"` // 开工时间(计划开工)
ProductionDays int `json:"productionDays"` // 安全生产天数
TotalQuantity int `json:"totalQuantity"` // 总数量
CompletedQuantity int `json:"completedQuantity"` // 已完成数量
}
// 项目详情响应数据【项目计划】
type ProjectPlanDetailRes struct {
ProjectPlan
}
type ProjectPlan struct {
PendingPlan int `json:"pendingTasks"` // 待开始计划
ProcessingPlan int `json:"processingTasks"` // 进行中计划
CompletedPlan int `json:"completedTasks"` // 已完成计划
DeferredPlan int `json:"deferredTasks"` // 滞后计划
}
type ProjectPlanDetailStatusRes struct {
g.Meta `mime:"application/json"`
common.ListRes
List []SchduleList `json:"list"`
}
// SchduleList 计划详情
type SchduleList struct {
// 工作名称
WorkName string `json:"work_name"`
// 计划数量
PlanNum int `json:"plan_num"`
// 实际完成数量
FinishedNum int `json:"finished_num"`
// 开始时间
StartTime string `json:"start_at"`
// 结束时间
EndTime string `json:"end_at"`
// 工日
WorkDay int `json:"work_day"`
// 工作量百分比
WorkPercent int `json:"work_percent"`
// 负责人
Leader string `json:"principal"`
}
// 项目详情响应数据【视频监控】
type ProjectVideoDetailRes struct {
YS7Devices []YS7Device
}
type YS7Device struct {
ID int `json:"id"`
CreatedAt time.Time `json:"createdAt"`
DeviceSerial string `json:"deviceSerial"`
DeviceName string `json:"deviceName"`
DeviceType string `json:"deviceType"`
Status int `json:"status"`
Defence int `json:"defence"`
DeviceVersion string `json:"deviceVersion"`
ProjectID string `json:"projectId"`
Detail string `json:"detail"`
Position string `json:"position"`
Remark string `json:"remark"`
VideoEncrypted int `json:"videoEncrypted"`
}
// 项目详情响应数据【施工日志】
type ProjectLogRes struct {
Logs []ConstructionLog `json:"logs"`
}
type ConstructionLog struct {
ID int64 `json:"id"`
DateOfOccurrence string `json:"dateOfOccurrence"`
Condition string `json:"condition"`
TechnologyQuality string `json:"technologyQuality"`
Remark string `json:"remark"`
Path string `json:"path"`
CreatedBy string `json:"createdBy"`
UpdatedBy string `json:"updatedBy"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt time.Time `json:"deletedAt"`
}
type VisualProgress struct {
Id int64 `json:"id" dc:"主键"`
ProjectID int64 `json:"projectId" dc:"项目ID"`
ProjectName string `json:"projectName" dc:"项目名称"`
ReporterID int64 `json:"reporterId" dc:"上报人ID"`
ReporterName string `json:"reporterName" dc:"上报人名字"`
ReportTime time.Time `json:"reportTime" dc:"上报时间"`
Title string `json:"title" dc:"形象标题"`
ProgressDesc string `json:"progressDesc" dc:"进度描述"`
AttachmentURL string `json:"attachmentUrl" dc:"附件URL"`
}

279
api/app/project/service.go Normal file
View File

@ -0,0 +1,279 @@
package project
import (
"context"
"fmt"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/jinzhu/copier"
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
"github.com/tiger1103/gfast/v3/internal/app/system/model"
)
// 项目首页信息列表
func (p Project) ProjectIndex(ctx context.Context, req *ProjectIndexModuleReq) (res *ProjectListRes, err error) {
res = new(ProjectListRes)
// 查询所有项目总数
count, err := g.Model("sys_project").Ctx(ctx).Count()
if err != nil {
return nil, err
}
res.ProjectTotal = count
// 查询所有项目的总容量之和
var capacity struct{ ProjectCapacity int }
err = g.Model("sys_project").Fields("SUM(actual) AS ProjectCapacity").Ctx(ctx).Scan(&capacity)
if err != nil {
return nil, err
}
res.ProjectCapacity = capacity.ProjectCapacity
// 分页查询大项目基础信息,然后遍历信息去补全其他的数据
var projects []struct {
ProjectID int `json:"projectId"`
ProjectName string `json:"projectName"`
ProjectLeader string `json:"projectLeader"`
OnStreamTime string `json:"onStreamTime"`
}
// 构建查询模型
query := g.Model("sys_project").Ctx(ctx).
Fields("id AS ProjectID, project_name AS ProjectName, on_stream_time AS OnStreamTime, principal AS ProjectLeader")
if req.ProjectName != "" {
query = query.Where("project_name LIKE ?", "%"+req.ProjectName+"%")
}
err = query.Scan(&projects)
if err != nil {
return nil, err
}
buildProjectCount := 0 // 初始化在建项目数量计数器
for _, project := range projects {
// 初始化列表模型
projectModel := ProjectListModel{
ProjectID: project.ProjectID,
ProjectName: project.ProjectName,
ProjectLeader: project.ProjectLeader,
OnStreamTime: project.OnStreamTime,
}
// 首先将字符串转换为 JSON
onStreamTime, err := time.Parse("2006-01-02", project.OnStreamTime)
if err != nil {
return nil, err
}
// 检查该项目的所有子项目是否均有完成时间
subProjectCount, err := g.Model("sub_project").Ctx(ctx).Where("project_id = ? AND done_time IS NOT NULL", project.ProjectID).Count()
totalSubProjects, err := g.Model("sub_project").Ctx(ctx).Where("project_id = ?", project.ProjectID).Count()
if err != nil {
return nil, err
}
// 如果所有子项目的完成时间都不为空则设置为竣工
if subProjectCount == totalSubProjects && totalSubProjects != 0 {
var latestDoneTimeString string
// 查询子项目最后完成的任务的时间
err = g.Model("sub_project").Ctx(ctx).
Fields("MAX(done_time)").
Where("project_id = ?", project.ProjectID).
Scan(&latestDoneTimeString)
if err != nil {
return nil, err
}
latestDoneTime, err := time.Parse("2006-01-02", latestDoneTimeString)
if err != nil {
return nil, err
}
// 安全生产天数等于最后任务的完成时间减去项目的开始时间
projectModel.ProductionDays = int(latestDoneTime.Unix()-onStreamTime.Unix()) / 86400
projectModel.Status = "竣工"
} else {
// 安全生产天数等于当前的时间减去项目的开始时间
projectModel.ProductionDays = int(time.Now().Unix()-onStreamTime.Unix()) / 86400
projectModel.Status = "在建"
buildProjectCount++ // 累加在建项目数量
}
// 查询相关工作计划的总量与完成量
var work struct {
TotalQuantity int `json:"totalQuantity"`
CompletedQuantity int `json:"completedQuantity"`
}
err = g.Model("work_schedule").Ctx(ctx).
Fields("SUM(plan_num) AS totalQuantity, SUM(finished_num) AS completedQuantity").
Where("project_id = ?", project.ProjectID).
Scan(&work)
if err != nil {
return nil, err
}
projectModel.TotalQuantity = work.TotalQuantity
projectModel.CompletedQuantity = work.CompletedQuantity
// 添加到结果列表中
res.ProjectListModels = append(res.ProjectListModels, projectModel)
}
// 设置在建项目数量
res.BuildProject = buildProjectCount
return res, nil
}
// 根据项目ID查询详情【基础数据】
func (p Project) ProjectDetail(ctx context.Context, req *ProjectIndexModuleDetailReq) (res *ProjectDetailModelRes, err error) {
res = new(ProjectDetailModelRes)
// 初始化查询模型,用于获取项目的基础信息
var project struct {
ProjectID int `db:"ProjectID"`
ProjectName string `db:"ProjectName"`
ProjectLeader string `db:"ProjectLeader"`
OnStreamTime string `db:"OnStreamTime"`
}
err = g.Model("sys_project").Ctx(ctx).
Fields("id AS ProjectID, project_name AS ProjectName, principal AS ProjectLeader, on_stream_time AS OnStreamTime").
Where("id = ?", req.ProjectId).
Scan(&project)
if err != nil {
return nil, err
}
// 将基本信息设置到响应结构体中
res.ProjectID = project.ProjectID
res.ProjectName = project.ProjectName
res.ProjectLeader = project.ProjectLeader
res.OnStreamTime = project.OnStreamTime
// 计算安全生产天数
onStreamDate, err := time.Parse("2006-01-02", project.OnStreamTime)
if err != nil {
return nil, err
}
res.ProductionDays = int(time.Since(onStreamDate).Hours() / 24) // 将时间差转换为天数
// 获取总数量和已完成数量
var quantities struct {
TotalQuantity int `db:"TotalQuantity"`
CompletedQuantity int `db:"CompletedQuantity"`
}
err = g.Model("work_schedule").Ctx(ctx).
Fields("SUM(plan_num) AS TotalQuantity, SUM(finished_num) AS CompletedQuantity").
Where("project_id = ?", project.ProjectID).
Scan(&quantities)
if err != nil {
return nil, err
}
res.TotalQuantity = quantities.TotalQuantity
res.CompletedQuantity = quantities.CompletedQuantity
return res, nil
}
// 根据项目ID查询详情【项目计划】
func (p Project) ProjectPlanDetail(ctx context.Context, req *ProjectPlanDetailReq) (res *ProjectPlanDetailRes, err error) {
res = new(ProjectPlanDetailRes)
// 使用结构体来接收查询结果
var planCounts struct {
PendingPlan int `db:"PendingPlan"` // 待开始
ProcessingPlan int `db:"ProcessingPlan"` // 进行中
CompletedPlan int `db:"CompletedPlan"` // 已完成
DeferredPlan int `db:"DeferredPlan"` // 滞后
}
// 构建查询语句以聚合不同状态的计划数量
err = g.Model("work_schedule").Ctx(ctx).
Fields(
"SUM(case when status = 0 then 1 else 0 end) AS PendingPlan",
"SUM(case when status = 1 then 1 else 0 end) AS ProcessingPlan",
"SUM(case when status = 2 then 1 else 0 end) AS CompletedPlan",
"SUM(case when status = 3 then 1 else 0 end) AS DeferredPlan",
).
Where("project_id = ?", req.ProjectId).
Scan(&planCounts)
if err != nil {
return nil, err
}
// 将查询结果映射到响应结构体中
res.ProjectPlan = ProjectPlan{
PendingPlan: planCounts.PendingPlan,
ProcessingPlan: planCounts.ProcessingPlan,
CompletedPlan: planCounts.CompletedPlan,
DeferredPlan: planCounts.DeferredPlan,
}
return res, nil
}
// 根据项目ID查询详情【项目计划详情】
func (p Project) ProjectPlanDetailStatus(ctx context.Context, req *ProjectPlanDetailStatusReq) (res *ProjectPlanDetailStatusRes, err error) {
res = new(ProjectPlanDetailStatusRes)
list := []model.SchduleDetail{}
m := dao.WorkSchedule.Ctx(ctx).As("wc").
LeftJoin("work_status as ws", "wc.work_id = ws.work_id").
LeftJoin("sys_project as sp", "wc.project_id = sp.id").
Where("wc.status = ? AND wc.project_id = ?", req.Status, req.ProjectID)
res.Total, err = m.Count()
if err != nil {
return nil, fmt.Errorf("failed to count: %w", err)
}
res.CurrentPage = req.PageNum
// 查询工作计划详情
m = m.Fields("ws.work_name,wc.plan_num,wc.finished_num,wc.start_at,wc.end_at,sp.principal")
err = m.Page(req.PageNum, req.PageSize).Scan(&list)
if err != nil {
return nil, fmt.Errorf("failed to fetch page: %w", err)
}
res.List = make([]SchduleList, 0, len(list))
copier.Copy(&res.List, &list)
return res, nil
}
// 根据项目ID查询详情【视频监控】
func (p Project) ProjectVideoDetail(ctx context.Context, req *ProjectVideoDetailReq) (res *ProjectVideoDetailRes, err error) {
res = new(ProjectVideoDetailRes)
err = g.Model("ys7devices").Ctx(ctx).
Fields(
"id AS ID, created_at AS CreatedAt, DeviceSerial, DeviceName, DeviceType, Status, Defence, DeviceVersion, ProjectId, Detail, Position, Remark, VideoEncrypted").
Where("ProjectId = ?", req.ProjectId).
Order("CreatedAt DESC").
Scan(&res.YS7Devices)
if err != nil {
return nil, err
}
return res, nil
}
// 根据项目ID查询详情【施工日志】
func (p Project) GetConstructionLogs(ctx context.Context, req *ProjectLogReq) (res *ProjectLogRes, err error) {
res = new(ProjectLogRes)
err = g.Model("bus_construction_log").Ctx(ctx).
Fields(
"id, date_of_occurrence, condition, technology_quality, remark, path, created_by, updated_by, created_at, updated_at, deleted_at").
Where("project_id = ? AND deleted_at IS NULL", req.ProjectId).
Order("date_of_occurrence DESC").
Scan(&res.Logs)
if err != nil {
return nil, err
}
return res, nil
}

31
api/app/stage/req.go Normal file
View File

@ -0,0 +1,31 @@
package stage
import (
"github.com/gogf/gf/v2/frame/g"
)
// 新增阶段
type CreateStageReq struct {
g.Meta `path:"/stage/create" method:"post" tags:"APP(上报阶段)" summary:"创建新阶段"`
StageName string `json:"stageName" dc:"阶段名称"`
}
// 获取阶段列表
type StageListReq struct {
g.Meta `path:"/stage/list" method:"get" tags:"APP(上报阶段)" summary:"获取阶段列表"`
Page int `json:"page" dc:"页码"`
PageSize int `json:"pageSize" dc:"大小"`
}
// 更新阶段信息
type UpdateStageReq struct {
g.Meta `path:"/stage/update" method:"put" tags:"APP(上报阶段)" summary:"更新阶段信息"`
StageID int64 `json:"stageId" dc:"阶段ID"`
StageName string `json:"stageName" dc:"阶段名称"`
}
// 删除阶段
type DeleteStageReq struct {
g.Meta `path:"/stage/delete" method:"delete" tags:"APP(上报阶段)" summary:"删除阶段"`
StageID int64 `json:"stageId" dc:"阶段ID"`
}

24
api/app/stage/res.go Normal file
View File

@ -0,0 +1,24 @@
package stage
import "time"
type CreateStageRes struct {
}
type StageListRes struct {
Stages []Stage `json:"stages"`
}
// Stage 表的结构体定义
type Stage struct {
StageID int64 `json:"stageId" gorm:"primary_key"`
StageName string `json:"stageName" dc:"阶段名称"`
CreatedAt time.Time `json:"createdAt" dc:"创建时间"`
UpdatedAt time.Time `json:"updatedAt" dc:"更新时间"`
}
type UpdateStageRes struct {
}
type DeleteStageRes struct {
}

52
api/app/stage/service.go Normal file
View File

@ -0,0 +1,52 @@
package stage
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"time"
)
// 创建阶段
func (s StageApi) CreateStage(ctx context.Context, req *CreateStageReq) (res *CreateStageRes, err error) {
res = new(CreateStageRes)
stage := Stage{
StageName: req.StageName,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
_, err = g.Model("stage").Ctx(ctx).Insert(&stage)
if err != nil {
return nil, err
}
return res, nil
}
func (s StageApi) GetStageList(ctx context.Context, req *StageListReq) (res *StageListRes, err error) {
res = new(StageListRes)
err = g.Model("stage").Ctx(ctx).Page(req.Page, req.PageSize).Scan(&res.Stages)
if err != nil {
return nil, err
}
return res, nil
}
func (s StageApi) UpdateStage(ctx context.Context, req *UpdateStageReq) (res *UpdateStageRes, err error) {
res = new(UpdateStageRes)
_, err = g.Model("stage").Ctx(ctx).Data(g.Map{
"stage_name": req.StageName,
"updated_at": time.Now(),
}).Where("stage_id", req.StageID).Update()
if err != nil {
return nil, err
}
return res, nil
}
func (s StageApi) DeleteStage(ctx context.Context, req *DeleteStageReq) (res *DeleteStageRes, err error) {
res = new(DeleteStageRes)
_, err = g.Model("stage").Ctx(ctx).Where("stage_id", req.StageID).Delete()
if err != nil {
return nil, err
}
return res, nil
}

4
api/app/stage/stage.go Normal file
View File

@ -0,0 +1,4 @@
package stage
type StageApi struct {
}

48
api/app/visual/req.go Normal file
View File

@ -0,0 +1,48 @@
package visual
import (
"github.com/gogf/gf/v2/frame/g"
)
// 新增形象进度
type CreateVisualProgressReq struct {
g.Meta `path:"/visual/create" method:"post" tags:"APP(形象进度相关)" summary:"新增形象进度"`
ProjectID int64 `json:"projectId" v:"required" dc:"项目ID"`
ReporterID int64 `json:"reporterId" v:"required" dc:"上报人ID"`
ReportTime string `json:"reportTime" v:"required" dc:"上报时间"`
Title string `json:"title" v:"required" dc:"形象标题"`
ProgressDesc string `json:"progressDesc" v:"required" dc:"进度描述"`
AttachmentURL string `json:"attachmentUrl" dc:"附件URL"`
}
// 根据项目ID查询形象进度列表
type ReadVisualProgressReq struct {
g.Meta `path:"/visual/list" method:"get" tags:"APP(形象进度相关)" summary:"查询指定项目的形象进度列表"`
ProjectID int64 `json:"projectId" v:"required" dc:"项目ID"`
Page int64 `json:"page" dc:"请求的页码" v:"required"`
PageSize int64 `json:"pageSize" dc:"每页显示的条目数" v:"required"`
Title string `json:"title" dc:"模糊搜索字段"`
}
// 更新形象进度
type UpdateVisualProgressReq struct {
g.Meta `path:"/visual/update" method:"post" tags:"APP(形象进度相关)" summary:"更新项目进度"`
ID int64 `json:"id" v:"required" dc:"记录ID"`
ProjectID int64 `json:"projectId" dc:"项目ID"`
ReporterID int64 `json:"reporterId" dc:"上报人ID"`
Title string `json:"title" dc:"形象标题"`
ProgressDesc string `json:"progressDesc" dc:"进度描述"`
AttachmentURL string `json:"attachmentUrl" dc:"附件URL"`
}
// 删除形象进度
type DeleteVisualProgressReq struct {
g.Meta `path:"/visual/delete" method:"delete" tags:"APP(形象进度相关)" summary:"删除项目进度"`
ID int64 `json:"id" v:"required" dc:"形象进度ID"`
}
// 根据ID查询形象进度详情
type GetVisualProgressDetailReq struct {
g.Meta `path:"/visual/detail" method:"get" tags:"APP(形象进度相关)" summary:"根据形象进度ID查询详情"`
ID int64 `json:"id" v:"required" dc:"形象进度ID"`
}

36
api/app/visual/res.go Normal file
View File

@ -0,0 +1,36 @@
package visual
// 新增形象进度
type CreateVisualProgressRes struct{}
// 形象进度列表
type ReadVisualProgressRes struct {
ProgressList []VisualProgress `json:"progressList"`
Total int64 `json:"total"`
}
type VisualProgress struct {
Id int64 `json:"id" dc:"主键"`
ProjectID int64 `json:"projectId" dc:"项目ID"`
ProjectName string `json:"projectName" dc:"项目名称"`
ReporterID int64 `json:"reporterId" dc:"上报人ID"`
ReporterName string `json:"reporterName" dc:"上报人名字"`
ReportTime string `json:"reportTime" dc:"上报时间"`
Title string `json:"title" dc:"形象标题"`
ProgressDesc string `json:"progressDesc" dc:"进度描述"`
AttachmentURL string `json:"attachmentUrl" dc:"附件URL"`
}
// 更新形象进度
type UpdateVisualProgressRes struct{}
// 删除形象进度
type DeleteVisualProgressRes struct{}
// 根据ID查询形象进度详情
type GetVisualProgressDetailRes struct {
Detail VisualProgressDetail `json:"visualProgressDetail"`
}
type VisualProgressDetail struct {
VisualProgress
}

13
api/app/visual/router.go Normal file
View File

@ -0,0 +1,13 @@
package visual
import "github.com/gogf/gf/v2/net/ghttp"
// 形象进度API
func InitAppProjectAPI(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareCORS)
group.Group("/visual", func(group *ghttp.RouterGroup) {
group.Group("/api/v1", func(group *ghttp.RouterGroup) {
group.Bind(new(Visual))
})
})
}

96
api/app/visual/service.go Normal file
View File

@ -0,0 +1,96 @@
package visual
import (
"context"
"github.com/gogf/gf/v2/frame/g"
)
// 新增形象进度
func (v Visual) CreateVisualProgress(ctx context.Context, req *CreateVisualProgressReq) (res *CreateVisualProgressRes, err error) {
res = &CreateVisualProgressRes{}
_, err = g.Model("visual_progress").Ctx(ctx).Data(g.Map{
"project_id": req.ProjectID,
"reporter_id": req.ReporterID,
"report_time": req.ReportTime,
"title": req.Title,
"progress_desc": req.ProgressDesc,
"attachment_url": req.AttachmentURL,
}).Insert()
if err != nil {
return nil, err
}
return res, nil
}
// 形象进度列表
func (v Visual) GetVisualProgress(ctx context.Context, req *ReadVisualProgressReq) (res *ReadVisualProgressRes, err error) {
res = &ReadVisualProgressRes{}
offset := (req.Page - 1) * req.PageSize
count, err := g.Model("visual_progress").Ctx(ctx).Count()
if err != nil {
return nil, err
}
err = g.Model("visual_progress").Ctx(ctx).
Fields("sys_project.id, sys_project.project_name, user.user_nickname AS reporterName, visual_progress.*").
// 连表查询项目ID、项目名称
LeftJoin("sys_project on visual_progress.project_id = sys_project.id").
LeftJoin("sys_user AS user on user.id = visual_progress.reporter_id").
Where("visual_progress.project_id = ?", req.ProjectID).
Where("visual_progress.title LIKE ?", "%"+req.Title+"%").
Offset(int(offset)).Limit(int(req.PageSize)).
Order("visual_progress.report_time DESC").
Scan(&res.ProgressList)
if err != nil {
return nil, err
}
res.Total = int64(count)
return res, nil
}
// 更新形象进度
func (v Visual) UpdateVisualProgress(ctx context.Context, req *UpdateVisualProgressReq) (res *UpdateVisualProgressRes, err error) {
res = &UpdateVisualProgressRes{}
_, err = g.Model("visual_progress").Ctx(ctx).Data(g.Map{
"project_id": req.ProjectID,
"reporter_id": req.ReporterID,
"title": req.Title,
"progress_desc": req.ProgressDesc,
"attachment_url": req.AttachmentURL,
}).Where("id", req.ID).Update()
if err != nil {
return nil, err
}
return res, nil
}
// 删除形象进度
func (v Visual) DeleteVisualProgress(ctx context.Context, req *DeleteVisualProgressReq) (res *DeleteVisualProgressRes, err error) {
res = &DeleteVisualProgressRes{}
// 删除之前调用一个方法获取当前登录用户的ID如果是自己才可以删除
_, err = g.Model("visual_progress").Ctx(ctx).Where("id", req.ID).Delete()
if err != nil {
return nil, err
}
return res, nil
}
// 根据ID查询形象进度详情
func (v Visual) GetVisualProgressDetail(ctx context.Context, req *GetVisualProgressDetailReq) (res *GetVisualProgressDetailRes, err error) {
res = &GetVisualProgressDetailRes{}
// 查询项目基本信息
err = g.Model("visual_progress").Ctx(ctx).
Fields("sys_project.id, sys_project.project_name, user.user_nickname AS reporterName, visual_progress.*").
// 连表查询项目ID、项目名称
InnerJoin("sys_project on visual_progress.project_id = sys_project.id").
// 连表查询上报人名字(暂未确定是哪张用户表,需要联查)
InnerJoin("sys_user AS user on user.id = visual_progress.reporter_id").
Where("visual_progress.id = ?", req.ID).
Scan(&res.Detail)
// 查询备注列表信息
if err != nil {
return nil, err
}
return res, nil
}

4
api/app/visual/visual.go Normal file
View File

@ -0,0 +1,4 @@
package visual
type Visual struct {
}

View File

@ -0,0 +1,38 @@
package visual_remark
import "github.com/gogf/gf/v2/frame/g"
// 新增形象进度评论
type CreateRemarkReq struct {
g.Meta `path:"/remark/create" method:"post" tags:"APP(形象进度评论相关)" summary:"新增形象进度评论"`
VisualProgressID int `json:"visualProgressId" v:"required" dc:"形象进度ID"`
Comment string `json:"comment" v:"required" dc:"评论的内容"`
UserID int `json:"userId" v:"required" dc:"评论的用户ID"`
}
// 查看某个形象进度的评论列表
type ListRemarksReq struct {
g.Meta `path:"/remark/list" method:"get" tags:"APP(形象进度评论相关)" summary:"查看某个形象进度的评论列表"`
VisualID int64 `json:"visualID" v:"required" dc:"形象进度ID"`
}
// 获取形象进度评论详细信息
type GetRemarkDetailReq struct {
g.Meta `path:"/remark/detail" method:"get" tags:"APP(形象进度评论相关)" summary:"获取形象进度评论详细信息"`
ID int64 `json:"id" v:"required" dc:"备注ID"`
}
// 更新形象进度评论
type UpdateRemarkReq struct {
g.Meta `path:"/remark/update" method:"post" tags:"APP(形象进度评论相关)" summary:"更新形象进度评论"`
ID int `json:"id" v:"required" dc:"主键ID"`
VisualProgressID int `json:"visualProgressId" dc:"形象进度ID"`
Comment string `json:"comment" dc:"评论的内容"`
UserID int `json:"userId" dc:"评论的用户ID"`
}
// 删除形象进度评论
type DeleteRemarkReq struct {
g.Meta `path:"/remark/delete" method:"delete" tags:"APP(形象进度评论相关)" summary:"删除形象进度评论"`
ID int `json:"id" v:"required" dc:"主键ID"`
}

View File

@ -0,0 +1,31 @@
package visual_remark
// 查看某个形象进度的评论列表
type ListRemarksRes struct {
VisualProgressRemarks []VisualProgressRemark `json:"visualProgressRemarks"`
}
type VisualProgressRemark struct {
ID int `json:"id"`
Username string `json:"username"`
Avatar string `json:"avatar"`
VisualProgressID int `json:"visualProgressId"`
VisualProgressTitle string `json:"visualProgressTitle"`
Comment string `json:"comment"`
UserID int `json:"userId"`
CreatedAt string `json:"createdAt"`
}
// 查看形象进度评论详细信息
type GetRemarkDetailRes struct {
VisualProgressRemark
}
// 新增形象进度评论
type CreateRemarkRes struct{}
// 更新形象进度评论
type UpdateRemarkRes struct{}
// 删除形象进度评论
type DeleteRemarkRes struct{}

View File

@ -0,0 +1,115 @@
package visual_remark
import (
"context"
"errors"
"github.com/gogf/gf/v2/frame/g"
"github.com/tiger1103/gfast/v3/api/v1/system"
ct "github.com/tiger1103/gfast/v3/internal/app/system/logic/context"
"github.com/tiger1103/gfast/v3/internal/app/system/service"
)
// 列表请求
func (v VisualRemark) ListRemarks(ctx context.Context, req *ListRemarksReq) (res *ListRemarksRes, err error) {
res = &ListRemarksRes{}
err = g.Model("visual_progress_remark AS remark").Ctx(ctx).
Fields("remark.*,visual.title AS visualProgressTitle, user.user_nickname AS Username, cuser.head_icon AS Avatar").
// 连表查询形象进度表得到形象进度标题
LeftJoin("visual_progress AS visual", "visual.id = remark.visual_progress_id").
// 连表查询评论人的名字(暂未确定是哪张用户表,需要联查)
LeftJoin("sys_user AS user on user.id = remark.user_id").
// 头像需要从另外一张表查询出来
LeftJoin("bus_construction_user AS cuser on cuser.phone = user.mobile").
Where("visual.id = ? AND cuser.deleted_at is null", req.VisualID).
Scan(&res.VisualProgressRemarks)
g.Dump(res.VisualProgressRemarks)
if err != nil {
return nil, err
}
return res, nil
}
// 获取详情
func (v VisualRemark) GetRemarkDetail(ctx context.Context, req *GetRemarkDetailReq) (res *GetRemarkDetailRes, err error) {
res = &GetRemarkDetailRes{}
err = g.Model("visual_progress_remark AS remark").Ctx(ctx).
Fields("remark.*,visual.title AS visualProgressTitle, user.user_nickname AS Username, user.avatar AS Avatar").
// 连表查询形象进度表得到形象进度标题
LeftJoin("visual_progress AS visual on visual.id = remark.visual_progress_id").
// 连表查询评论人的名字
LeftJoin("sys_user AS user on user.id = remark.user_id").
Where("remark.id = ? AND cuser.deleted_at is null", req.ID).
Scan(&res.VisualProgressRemark)
if err != nil {
return nil, err
}
return res, nil
}
// 新增形象进度
func (v VisualRemark) CreateRemark(ctx context.Context, req *CreateRemarkReq) (res *CreateRemarkRes, err error) {
res = &CreateRemarkRes{}
_, err = g.Model("visual_progress_remark").Ctx(ctx).Data(g.Map{
"visual_progress_id": req.VisualProgressID,
"comment": req.Comment,
"user_id": req.UserID,
}).Insert()
if err != nil {
return nil, err
}
var VisualProgressVo struct {
ProjectId int `json:"projectId"`
ReporterId int `json:"reporterId"`
}
// 根据形象进度ID查询项目ID、发布者ID
g.Model("visual_progress").Fields("project_id AS ProjectId", "reporter_id AS ReporterId").
Where("id", req.VisualProgressID).Scan(&VisualProgressVo)
// 新增之后通知对应的发起人
param := &system.CommentListAddReq{
ProjectId: VisualProgressVo.ProjectId,
Receiver: VisualProgressVo.ReporterId,
Content: req.Comment,
Sender: req.UserID,
}
service.CommentList().Add(ctx, param)
return res, nil
}
// 更新形象备注
func (v VisualRemark) UpdateRemark(ctx context.Context, req *UpdateRemarkReq) (res *UpdateRemarkRes, err error) {
res = &UpdateRemarkRes{}
_, err = g.Model("visual_progress_remark").Ctx(ctx).Data(g.Map{
"visual_progress_id": req.VisualProgressID,
"comment": req.Comment,
"user_id": req.UserID,
}).Where("id", req.ID).Update()
if err != nil {
return nil, err
}
return res, nil
}
// 删除形象备注
func (v VisualRemark) DeleteRemark(ctx context.Context, req *DeleteRemarkReq) (res *DeleteRemarkRes, err error) {
userID := ct.New().GetUserId(ctx)
var VisualProgressRemarkVo struct {
ID int `json:"id"`
UserID uint64 `json:"userId"`
}
g.Model("visual_progress_remark").Ctx(ctx).Where("id", req.ID).Scan(&VisualProgressRemarkVo)
// 当前用户等于评论的发起人才可以删除自己的评论
if userID == VisualProgressRemarkVo.UserID {
res = &DeleteRemarkRes{}
_, err = g.Model("visual_progress_remark").Ctx(ctx).Where("id", req.ID).Delete()
if err != nil {
return nil, err
}
} else {
return nil, errors.New("只能删除自己的评论信息")
}
return
}

View File

@ -0,0 +1,4 @@
package visual_remark
type VisualRemark struct {
}

View File

@ -0,0 +1,30 @@
package attendanceMachine
import (
"github.com/gogf/gf/v2/frame/g"
commonApi "github.com/tiger1103/gfast/v3/api/v1/common"
)
type EquipmentTimeClockReq struct {
g.Meta `path:"/face" tags:"考勤机" method:"post" summary:"设备考勤打卡"`
commonApi.Author
Sn string `p:"sn"`
Count int `json:"Count"`
Logs []Log `json:"logs"`
}
type Log struct {
UserID string `json:"user_id"`
RecogType string `json:"recog_type"`
RecogTime string `json:"recog_time"`
Photo string `json:"photo"`
Location struct {
Longitude string `json:"longitude"`
Latitude string `json:"latitude"`
} `json:"location"`
}
type EquipmentTimeClockRes struct {
Result string `json:"Result"`
Msg string `json:"Msg"`
}

View File

@ -0,0 +1,15 @@
package attendanceMachine
import "github.com/gogf/gf/v2/net/ghttp"
type AttendanceMachineApi struct {
}
func InitAttendanceMachineAPI(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareCORS)
group.Group("/", func(group *ghttp.RouterGroup) {
group.Group("/api/v1/record", func(group *ghttp.RouterGroup) {
group.Bind(new(AttendanceMachineApi))
})
})
}

View File

@ -0,0 +1,308 @@
// @Author cory 2025/3/5 10:27:00
package attendanceMachine
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/tiger1103/gfast/v3/api/v1/common/coryCommon"
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
wxDao "github.com/tiger1103/gfast/v3/internal/app/wxApplet/dao"
wxBusAttendance "github.com/tiger1103/gfast/v3/internal/app/wxApplet/logic/busAttendance"
wxModel "github.com/tiger1103/gfast/v3/internal/app/wxApplet/model"
wxDo "github.com/tiger1103/gfast/v3/internal/app/wxApplet/model/do"
"github.com/tiger1103/gfast/v3/library/liberr"
tool "github.com/tiger1103/gfast/v3/utility/coryUtils"
"golang.org/x/net/context"
"io/ioutil"
"math"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
func (w AttendanceMachineApi) TaskCreate(ctx context.Context, req *EquipmentTimeClockReq) (res *EquipmentTimeClockRes, err error) {
res = new(EquipmentTimeClockRes)
err = AttendanceMachine(ctx, req)
res.Result = "0"
res.Msg = ""
r := ghttp.RequestFromCtx(ctx)
r.Response.ClearBuffer()
r.Response.WriteJson(res)
return
}
func AttendanceMachine(ctx context.Context, req *EquipmentTimeClockReq) (err error) {
logs := req.Logs[0]
f1 := logs.Location.Longitude
f2 := logs.Location.Latitude
res, err := wxBusAttendance.ThisInverseGeocodingFunc(f1 + "," + f2)
liberr.ErrIsNil(ctx, err)
// 上次打卡距离此次打卡不超过3分支那么提示“你刚刚已打上/下班卡了哦!”
currentTime := time.Now()
date := tool.New().GetFormattedDate(currentTime)
var bair *wxModel.BusAttendanceInfoRes
err = wxDao.BusAttendance.Ctx(ctx).
Where("openid", logs.UserID).
Where("printing_date", date).
Fields("clock_on,commuter").OrderDesc("clock_on").Limit().Scan(&bair)
liberr.ErrIsNil(ctx, err, "打卡失败!")
if err == nil && bair != nil {
//判断t1和t2是否相差180秒如果小于180秒就直接return
flag, err := IsWithinThreeMinutes(currentTime, bair.ClockOn)
if err != nil {
return err
}
if flag {
txt := ""
if bair.Commuter == "1" {
txt = "您已上班打卡成功!杜绝重复打卡!"
} else {
txt = "您已下班打卡成功!杜绝重复打卡!"
}
err = errors.New(txt)
return err
}
}
lng, err := strconv.ParseFloat(f1, 64)
if err != nil {
err = errors.New("精准度转换错误!")
return
}
lat, err := strconv.ParseFloat(f2, 64)
if err != nil {
err = errors.New("纬度转换错误!")
return
}
lng, lat = coryCommon.GCJ02toWGS84(lng, lat)
// 0-0、判断是否开启打卡功能 (禁止打卡和离职都不允许打卡)
var userInfo *wxModel.BusConstructionUserInfoRes
err = dao.BusConstructionUser.Ctx(ctx).Where("openid", logs.UserID).Scan(&userInfo)
if userInfo.TeamId == 0 {
err = errors.New("当前用户暂无班组,无法打卡!")
return
}
// 查看当前用户的打卡时间是否请假,如果请假就提示不用打卡
count, err := wxDao.BusAskforleave.Ctx(ctx).
Where(wxDao.BusAskforleave.Columns().ProjectId, userInfo.ProjectId).
Where(wxDao.BusAskforleave.Columns().Openid, userInfo.Openid).
Where("(" +
"DATE_FORMAT(" + date + ",'%Y-%m-%d') BETWEEN DATE_FORMAT(start_time,'%Y-%m-%d') AND DATE_FORMAT(end_time,'%Y-%m-%d')" +
" or " +
"DATE_FORMAT(" + date + ",'%Y-%m-%d') BETWEEN DATE_FORMAT(start_time,'%Y-%m-%d') AND DATE_FORMAT(end_time,'%Y-%m-%d')" +
")").Count()
liberr.ErrIsNil(ctx, err)
if count > 0 {
liberr.ErrIsNil(ctx, errors.New("您已请假,无需打卡!"))
return
}
// 部分班组可以直接跳过在范围内打卡,获取当前用户班组,看看是否是可以在任何地点打卡
value, errvalue := wxDao.SysProjectTeam.Ctx(ctx).Where("id", userInfo.TeamId).Fields("is_clock_in").Value()
if errvalue != nil {
err = errors.New("获取班组打卡状态失败!")
return err
}
if value.String() == "2" {
goto breakHere
}
// 计算是否在打卡范围中
if true {
// 查询当前用户是否有加入班组,有的话看看是什么项目,再根据项目获取到方正,最后根据方正数据得出打卡范围
projectId, _ := wxDao.SysProjectTeamMember.Ctx(ctx).As("a").
LeftJoin("sys_project_team as b on a.team_id = b.id").
Fields("a.project_id").
Where("a.openid", logs.UserID).Value()
if projectId != nil {
var qf []*wxBusAttendance.ProjectPunchRangeRes
err = g.DB().Model("sys_project_punch_range").Where("project_id", projectId).Fields("punch_range").Scan(&qf)
if err != nil {
err = errors.New("系统错误,请联系管理员!")
return err
}
if len(qf) == 0 {
err = errors.New("未设置打卡范围!请联系管理员设置!")
return err
}
countI := len(qf)
countII := 0
for _, dakaStr := range qf {
countII = countII + 1
var dataInfo coryCommon.DetailedMap
err = json.Unmarshal([]byte(fmt.Sprint(dakaStr.PunchRange)), &dataInfo.Positions)
flag := coryCommon.RectangularFrameRange(dataInfo, lng, lat)
if flag {
goto breakHere
} else {
// 没有一次范围匹配上,那就直接返回
if countII == countI {
err = errors.New("不在范围内,打卡无效!")
return err
}
}
}
} else {
err = errors.New("未指定打卡范围,请联系管理员!")
return err
}
}
breakHere:
if userInfo.Status == "1" {
err = errors.New("已离职,请联系管理员!")
return
}
if userInfo.Clock == "2" {
err = errors.New("已禁止打卡,请联系管理员!")
return
}
if len(strings.Trim(userInfo.LeaveDate.String(), "")) != 0 {
err = errors.New("已退场,请联系管理员!")
return
}
clockStatus := ""
// 1、获取当日时间
// 2、查询今日打卡记录
gm := wxDao.BusAttendance.Ctx(ctx).
Where(wxDao.BusAttendance.Columns().Openid, logs.UserID).
Where(wxDao.BusAttendance.Columns().PrintingDate, date)
// 3、如果有判断是否打了上班卡如果没有就打上班卡
count1, _ := gm.Where("commuter", "1").Count()
if count1 == 0 {
// 打上班卡
clockStatus = "1"
}
// 4、如果有判断是否打了下班卡如果没有就打下班卡
count2, _ := gm.Where("commuter", "2").Count()
if clockStatus == "" && count2 == 0 {
// 打下班卡
clockStatus = "2"
}
// 5、上下班卡都打了那么就提示今日打卡完成
if count1 != 0 && count2 != 0 {
// 已完成当日打卡
err = errors.New("当日打卡已完成!")
return err
}
// 提交数据
name := userInfo.NickName
if userInfo.UserName != "" {
name = userInfo.UserName
}
// 将base64人脸换成图片存储到本地
imagePath, err := DecodeBase64Image(logs.Photo)
if err != nil {
return err
}
rpath := coryCommon.ResourcePublicToFunc(imagePath, 1)
attendance := wxDo.BusAttendance{
PacePhoto: rpath,
UserName: name,
ProjectId: userInfo.ProjectId,
Openid: userInfo.Openid,
Commuter: clockStatus,
Lng: lng,
Lat: lat,
PrintingDate: date,
}
// 判断此次打卡状态 默认就为缺勤
dateTime := tool.New().GetFormattedDateTime(time.Now())
if userInfo.ProjectId > 0 {
value, err := dao.SysProject.Ctx(ctx).WherePri(userInfo.ProjectId).Fields("punch_range as punchRange").Value()
if err != nil {
err = errors.New("获取项目打卡范围失败!")
}
split := strings.Split(value.String(), ",")
strType := tool.New().TimeWithin(dateTime, split[0], split[1], clockStatus)
attendance.IsPinch = strType
attendance.PunchRange = value.String() // 记录项目的打卡时间,保留字段,后期有大用
}
// 获取此次打卡的日薪
vl, err := dao.BusConstructionUser.Ctx(ctx).As("a").
LeftJoin("bus_type_of_wage as f on a.type_of_work = f.type_of_work").
Fields("if (a.salary>0, a.salary,f.standard) as salary").
Where("a.openid", logs.UserID).Value()
liberr.ErrIsNil(ctx, err, "获取薪资失败")
// 记录打卡时间上下班打卡时间统一用clock_on
attendance.ClockOn = dateTime
attendance.DailyWage = vl
attendance.Lng = lng
attendance.Lat = lat
attendance.Location = res.Regeocode.FormattedAddress
_, err = wxDao.BusAttendance.Ctx(ctx).Insert(attendance)
liberr.ErrIsNil(ctx, err, "添加失败")
return
}
// DecodeBase64Image 函数用于解码 Base64 编码的图片数据并保存为图片
func DecodeBase64Image(encodedStr string) (string, error) {
// 进行 URL 解码
decodedURLStr, err := url.QueryUnescape(encodedStr)
if err != nil {
return "", fmt.Errorf("URL 解码失败: %w", err)
}
// 提取真正的 Base64 数据
parts := strings.SplitN(decodedURLStr, ",", 2)
if len(parts) != 2 {
return "", fmt.Errorf("无效的 Base64 编码图片数据格式")
}
base64Data := parts[1]
// 进行 Base64 解码
decodedData, err := base64.StdEncoding.DecodeString(base64Data)
if err != nil {
return "", fmt.Errorf("Base64 解码失败: %w", err)
}
ht := coryCommon.Helmet
str := coryCommon.Ynr(ht)
fn := coryCommon.FileName("face")
dir, err := os.Getwd()
str = dir + "/" + str + fn + ".jpg"
str = filepath.ToSlash(str)
// 保存解码后的图片到文件
err = ioutil.WriteFile(str, decodedData, 0644)
if err != nil {
fmt.Println("保存图片文件出错:", err)
return "", fmt.Errorf("保存解码后的图片失败: %w", err)
}
return str, nil
}
// 判断两个时间是否相差超过 3 分钟
func isMinutesDifferenceGreaterThanThree(startTime, endTime time.Time) bool {
// 计算时间差(单位:秒)
diff := endTime.Sub(startTime).Seconds()
// 判断是否超过 180 秒3 分钟)
return diff > 180
}
// 判断两个时间的秒数差是否 <= 180 秒
func IsWithinThreeMinutes(currentTime time.Time, strTime string) (bool, error) {
// 解析字符串时间,使用本地时区
loc, _ := time.LoadLocation("Local") // 获取本地时区
parsedTime, err := time.ParseInLocation("2006-01-02 15:04:05", strTime, loc)
if err != nil {
fmt.Println("时间解析错误:", err)
return false, err
}
// 计算时间差(秒)
diff := math.Abs(currentTime.Sub(parsedTime).Seconds())
// 判断是否相差 180 秒
return diff <= 180, nil
}

15
api/constant/video_hat.go Normal file
View File

@ -0,0 +1,15 @@
package constant
import "github.com/gorilla/websocket"
var Url string = "wss://caps.runde.pro/wss"
const (
Username = "重庆远界大数据"
Password = "Cqyj123`"
AdminId = "12316"
BatteryLevel = 20 // 电池电量
)
var Token = ""
var Conn *websocket.Conn = nil

48
api/hwy/client/client.go Normal file
View File

@ -0,0 +1,48 @@
package client
import (
"bytes"
"encoding/json"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/tiger1103/gfast/v3/api/hwy/sign"
"io/ioutil"
"net/http"
"net/url"
)
// 封装 Get 请求
func Get(uri string, params map[string]string) (error, []byte) {
joinPath, _ := url.JoinPath(sign.HOST, uri)
url := joinPath + "?" + sign.CreateLinkString(params)
response, err := g.Client().ContentJson().Get(gctx.New(), url)
if err != nil {
return err, []byte{}
}
bt := response.ReadAll()
return nil, bt
}
// 封装 Post 请求
func Post(uri string, params map[string]string) (error, []byte) {
// 把 map 转为 json
jsonData, err := json.Marshal(params)
if err != nil {
return err, nil
}
// 使用http包发送POST请求
response, err := http.Post(sign.HOST+uri, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return err, nil
}
defer response.Body.Close()
// 读取响应体
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return err, nil
}
return nil, body
}

74
api/hwy/detail/detail.go Normal file
View File

@ -0,0 +1,74 @@
package detail
type Pub struct {
Code int `json:"code"`
Message string `json:"message"`
Obj struct {
} `json:"obj"`
Success bool `json:"success"`
Timestamp int `json:"timestamp"`
}
/*逆变器设备详情*/
type EquipmentDetail struct {
DivertorId string `json:"divertorId"`
DivertorName string `json:"divertorName"`
EquipmentName string `json:"equipmentName"`
EquipmentPn string `json:"equipmentPn"`
EquipmentSn string `json:"equipmentSn"`
Id string `json:"id"`
Kwp float64 `json:"kwp"`
MonKwh float64 `json:"monKwh"`
NowKw float64 `json:"nowKw"`
ParamDcacCode string `json:"paramDcacCode"`
ParamDcdcCode string `json:"paramDcdcCode"`
PowerPlantId string `json:"powerPlantId"`
PowerPlantName string `json:"powerPlantName"`
RatedPower float64 `json:"ratedPower"`
SoftDcacCode string `json:"softDcacCode"`
SoftDcdcCode string `json:"softDcdcCode"`
Status int `json:"status"`
SumKwh float64 `json:"sumKwh"`
TodayKwh float64 `json:"todayKwh"`
UpdateTime string `json:"updateTime"`
UserId string `json:"userId"`
UserName string `json:"userName"`
YearKwh float64 `json:"yearKwh"`
}
/*电站详情*/
type PlantDetail struct {
Address string `json:"address"`
City string `json:"city"`
CompanyId string `json:"companyId"`
CompanyName string `json:"companyName"`
DipAngle float64 `json:"dipAngle"`
District string `json:"district"`
Id string `json:"id"`
Kwp float64 `json:"kwp"`
Latitude string `json:"latitude"`
Longitude string `json:"longitude"`
MonKwh float64 `json:"monKwh"`
Name string `json:"name"`
NetworkTime string `json:"networkTime"`
NetworkType string `json:"networkType"`
NowKw float64 `json:"nowKw"`
OrientationAngle float64 `json:"orientationAngle"`
OwnerId string `json:"ownerId"`
OwnerName string `json:"ownerName"`
PaymentType string `json:"paymentType"`
PlantContact string `json:"plantContact"`
PlantContactPhone string `json:"plantContactPhone"`
PlantImg string `json:"plantImg"`
PowerPlantType string `json:"powerPlantType"`
Province string `json:"province"`
Remark string `json:"remark"`
ServiceProviderName string `json:"serviceProviderName"`
ServiceProviderPhone string `json:"serviceProviderPhone"`
Status int `json:"status"`
SubassemblyNumber int `json:"subassemblyNumber"`
SumKwh float64 `json:"sumKwh"`
TodayKwh float64 `json:"todayKwh"`
UpdateTime string `json:"updateTime"`
YearKwh float64 `json:"yearKwh"`
}

View File

@ -0,0 +1,335 @@
package equipment
import (
"context"
"encoding/json"
"strconv"
"github.com/gogf/gf/v2/frame/g"
"github.com/tiger1103/gfast/v3/api/hwy/client"
"github.com/tiger1103/gfast/v3/api/hwy/sign"
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
"github.com/tiger1103/gfast/v3/library/liberr"
)
type EquipmentApi struct{}
// 从数据库中获取逆变器列表
type EquipmentListQueryReq struct {
g.Meta `path:"/equipment/query" method:"get" tags:"禾望云相关" summary:"从数据库中获取逆变器列表【实时数据不一样准确】"`
PageNum int `json:"pageNum" dc:"分页数" v:"required"`
PageSize int `json:"pageSize" dc:"分页大小" v:"required"`
}
func (e EquipmentApi) GetEquipmentListFromDB(ctx context.Context, req *EquipmentListQueryReq) (res *EquipmentListQueryRes, err error) {
res = new(EquipmentListQueryRes)
err = g.Try(ctx, func(ctx context.Context) {
// 从数据库中查询逆变器列表
m := dao.Equipment.Ctx(ctx).WithAll()
res.Total, err = m.Count()
liberr.ErrIsNil(ctx, err, "获取总行数失败")
if req.PageNum == 0 {
req.PageNum = 1
}
res.CurrentPage = req.PageNum
if req.PageSize == 0 {
req.PageSize = 10
}
err = m.Page(req.PageNum, req.PageSize).Scan(&res.List)
liberr.ErrIsNil(ctx, err, "获取逆变器列表失败")
})
return res, err
}
// 禾望云逆变器列表请求
type EquipmentListReq struct {
g.Meta `path:"/equipment/list" method:"get" tags:"禾望云相关" summary:"从平台获取逆变器列表【实时数据准确】"`
PageNum int `json:"pageNum" dc:"分页数" v:"required"`
PageSize int `json:"pageSize" dc:"分页大小" v:"required"`
PlantId int `json:"plantId" dc:"电站ID" v:"required"`
}
func (e EquipmentApi) GetEquipmentListAndSave(ctx context.Context, req *EquipmentListReq) (res *EquipmentListRes, err error) {
res = new(EquipmentListRes)
data := make(map[string]string)
data["pageIndex"] = strconv.Itoa(req.PageNum)
data["pageSize"] = strconv.Itoa(req.PageSize)
data["plantId"] = strconv.Itoa(req.PlantId)
sign.SignByRSA(data)
err, bytes := client.Get("/openApi/equipment/getEquipmentListByPlantId", data)
if err != nil {
return res, err
}
err = json.Unmarshal(bytes, &res)
if err != nil {
return res, err
}
// 处理响应并存储数据
model := g.Model("equipment")
for _, record := range res.Result.Records {
_, err = model.Data(map[string]interface{}{
"plantId": req.PlantId,
"divertorId": record.DivertorId,
"divertorName": record.DivertorName,
"equipmentName": record.EquipmentName,
"equipmentPn": record.EquipmentPn,
"equipmentSn": record.EquipmentSn,
"id": record.Id,
"kwp": record.Kwp,
"monKwh": record.MonKwh,
"nowKw": record.NowKw,
"paramDcacCode": record.ParamDcacCode,
"paramDcdcCode": record.ParamDcdcCode,
"powerPlantId": record.PowerPlantId, // 确保这个字段已经是字符串,或者在数据库中正确处理类型转换
"powerPlantName": record.PowerPlantName,
"ratedPower": record.RatedPower,
"softDcacCode": record.SoftDcacCode,
"softDcdcCode": record.SoftDcdcCode,
"status": record.Status,
"sumKwh": record.SumKwh,
"todayKwh": record.TodayKwh,
"updateTime": record.UpdateTime, // 确保转换为适合数据库的日期时间格式
"userId": record.UserId,
"userName": record.UserName,
"yearKwh": record.YearKwh,
}).Save()
if err != nil {
return res, err
}
}
return res, nil
}
// 获取逆变器详情
type EquipmentDetailReq struct {
g.Meta `path:"/equipment/detail" method:"get" tags:"禾望云相关" summary:"获取逆变器详情"`
Id string `json:"id" dc:"逆变器ID" v:"required"`
Sn string `json:"sn" dc:"逆变器SN" v:"required"`
}
func (e EquipmentApi) EquipmentDetail(ctx context.Context, req *EquipmentDetailReq) (res *EquipmentDetailRes, err error) {
res = new(EquipmentDetailRes)
data := make(map[string]string)
data["id"] = req.Id
data["sn"] = req.Sn
sign.SignByRSA(data)
err, bytes := client.Get("/openApi/equipment/getEquipmentDetail", data)
if err != nil {
return res, err
}
err = json.Unmarshal(bytes, &res)
var Address struct {
PlantAddress string `json:"plantAddress"`
}
name := res.Result.PowerPlantName
g.Model("plant").Fields("address AS plantAddress").Where("name = ?", name).Scan(&Address)
res.PlantAddress = Address.PlantAddress
return res, nil
}
// 获取逆变器实时数据
type EquipmentRealDataReq struct {
g.Meta `path:"/equipment/real/data" method:"get" tags:"禾望云相关" summary:"获取逆变器实时数据"`
Id string `json:"id" dc:"逆变器ID" v:"required"`
Sn string `json:"sn" dc:"逆变器SN" v:"required"`
}
func (e EquipmentApi) EquipmentRealData(ctx context.Context, req *EquipmentRealDataReq) (res *EquipmentRealDataRes, err error) {
res = new(EquipmentRealDataRes)
data := make(map[string]string)
data["id"] = req.Id
data["sn"] = req.Sn
sign.SignByRSA(data)
err, bytes := client.Get("/openApi/equipment/getEquipmentData", data)
g.Dump(string(bytes))
if err != nil {
return res, err
}
err = json.Unmarshal(bytes, &res)
return res, nil
}
// 获取逆变器历史数据
type EquipmentHistoryReq struct {
g.Meta `path:"/equipment/history" method:"get" tags:"禾望云相关" summary:"获取逆变器历史数据"`
Time string `json:"time" dc:"日期字符 yyyy-MM-dd" v:"required"`
Id string `json:"id" dc:"逆变器ID" v:"required"`
Sn string `json:"sn" dc:"逆变器SN" v:"required"`
}
func (e EquipmentApi) EquipmentHistory(ctx context.Context, req *EquipmentHistoryReq) (*EquipmentHistoryRes, error) {
res := new(EquipmentHistoryRes)
data := map[string]string{
"id": req.Id,
"sn": req.Sn,
"time": req.Time,
}
sign.SignByRSA(data)
err, bytes := client.Get("/openApi/equipment/getEquipmentHistoryData", data)
if err != nil {
return res, err
}
err = json.Unmarshal(bytes, &res)
if err != nil {
return res, err
}
requiredKeys := map[string]bool{
"ACActivePower": true,
"ACReactivePower": true,
"DCInputPower": true,
"internalTemperature": true,
"gridFrequency": true,
"dayElectricity": true,
"inverterEfficiency": true,
}
for i := range res.Result {
var filteredParams []struct {
Key string `json:"key"`
Name string `json:"name"`
Unit string `json:"unit"`
Value interface{} `json:"value"`
}
for _, param := range res.Result[i].ParamList {
if _, ok := requiredKeys[param.Key]; ok {
filteredParams = append(filteredParams, param)
}
}
res.Result[i].ParamList = filteredParams
}
return res, nil
}
// 获取逆变器日统计数据
type EquipmentStaticDayReq struct {
g.Meta `path:"/equipment/static/day" method:"get" tags:"禾望云相关" summary:"获取逆变器日统计数据"`
StartTime string `json:"startTime" dc:"开始日期字符 yyyy-MM-dd" v:"required"`
EndTime string `json:"endTime" dc:"开始日期字符 yyyy-MM-dd" v:"required"`
Id string `json:"id" dc:"逆变器ID" v:"required"`
Sn string `json:"sn" dc:"逆变器SN" v:"required"`
}
func (e EquipmentApi) EquipmentStaticDay(ctx context.Context, req *EquipmentStaticDayReq) (res *EquipmentStaticDayRes, err error) {
res = new(EquipmentStaticDayRes)
data := make(map[string]string)
data["id"] = req.Id
data["sn"] = req.Sn
data["startTime"] = req.StartTime
data["endTime"] = req.EndTime
sign.SignByRSA(data)
err, bytes := client.Get("/openApi/equipment/getEquipmentDayStatisticsData", data)
if err != nil {
return res, err
}
err = json.Unmarshal(bytes, &res)
return res, nil
}
// 获取逆变器月统计数据
type EquipmentStaticMonthReq struct {
g.Meta `path:"/equipment/static/month" method:"get" tags:"禾望云相关" summary:"获取逆变器月统计数据"`
StartTime string `json:"startTime" dc:"开始日期字符 yyyy-MM" v:"required"`
EndTime string `json:"endTime" dc:"开始日期字符 yyyy-MM" v:"required"`
Id string `json:"id" dc:"逆变器ID" v:"required"`
Sn string `json:"sn" dc:"逆变器SN" v:"required"`
}
func (e EquipmentApi) EquipmentStaticMonth(ctx context.Context, req *EquipmentStaticMonthReq) (res *EquipmentStaticMonthRes, err error) {
res = new(EquipmentStaticMonthRes)
data := make(map[string]string)
data["id"] = req.Id
data["sn"] = req.Sn
data["startTime"] = req.StartTime
data["endTime"] = req.EndTime
sign.SignByRSA(data)
err, bytes := client.Get("/openApi/equipment/getEquipmentMonthStatisticsData", data)
if err != nil {
return res, err
}
err = json.Unmarshal(bytes, &res)
return res, nil
}
// 获取逆变器年统计数据
type EquipmentStaticYearReq struct {
g.Meta `path:"/equipment/static/year" method:"get" tags:"禾望云相关" summary:"获取逆变器年统计数据"`
StartTime string `json:"startTime" dc:"开始日期字符 yyyy" v:"required"`
EndTime string `json:"endTime" dc:"开始日期字符 yyyy" v:"required"`
Id string `json:"id" dc:"逆变器ID" v:"required"`
Sn string `json:"sn" dc:"逆变器SN" v:"required"`
}
func (e EquipmentApi) EquipmentStaticYear(ctx context.Context, req *EquipmentStaticYearReq) (res *EquipmentStaticYearRes, err error) {
res = new(EquipmentStaticYearRes)
data := make(map[string]string)
data["id"] = req.Id
data["sn"] = req.Sn
data["startTime"] = req.StartTime
data["endTime"] = req.EndTime
sign.SignByRSA(data)
err, bytes := client.Get("/openApi/equipment/getEquipmentYearStatisticsData", data)
if err != nil {
return res, err
}
err = json.Unmarshal(bytes, &res)
return res, nil
}
// 获取逆变器报警列表数据
type EquipmentAlarmListReq struct {
g.Meta `path:"/equipment/alarm/list" method:"get" tags:"禾望云相关" summary:"获取逆变器报警数据列表"`
StartTime string `json:"startTime" dc:"开始日期字符 yyyy-MM-dd" v:"required"`
EndTime string `json:"endTime" dc:"开始日期字符 yyyy-MM-dd" v:"required"`
Pn string `json:"pn" dc:"逆变器PN" v:"required"`
Sn string `json:"sn" dc:"逆变器SN" v:"required"`
}
func (e EquipmentApi) EquipmentAlarmList(ctx context.Context, req *EquipmentAlarmListReq) (res *EquipmentAlarmListRes, err error) {
res = new(EquipmentAlarmListRes)
data := make(map[string]string)
data["pn"] = req.Pn
data["sn"] = req.Sn
data["startTime"] = req.StartTime
data["endTime"] = req.EndTime
sign.SignByRSA(data)
err, bytes := client.Get("/openApi/equipment/getEquipmentFaultData", data)
if err != nil {
return res, err
}
err = json.Unmarshal(bytes, &res)
return res, nil
}
// 获取逆变器报警相关数据
type EquipmentAlarmDetailDataReq struct {
g.Meta `path:"/equipment/alarm/detail" method:"get" tags:"禾望云相关" summary:"获取逆变器报警数据详情"`
AlarmId string `json:"alarmId" dc:"报警ID" v:"required"`
}
// 获取设备报警详情数据
func (e EquipmentApi) EquipmentAlarmDetailData(ctx context.Context, req *EquipmentAlarmDetailDataReq) (res *EquipmentAlarmDetailDataRes, err error) {
res = new(EquipmentAlarmDetailDataRes)
data := make(map[string]string)
data["alarmId"] = req.AlarmId
sign.SignByRSA(data)
err, bytes := client.Post("openApi/equipment/getAlarmDetailDataById", data)
if err != nil {
return res, err
}
err = json.Unmarshal(bytes, &res)
return res, nil
}

296
api/hwy/equipment/res.go Normal file
View File

@ -0,0 +1,296 @@
package equipment
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/tiger1103/gfast/v3/api/v1/common"
"github.com/tiger1103/gfast/v3/internal/app/system/model/entity"
)
// 逆变器列表响应
type EquipmentListRes struct {
Code int `json:"code"`
Message string `json:"message"`
Obj struct{} `json:"obj"`
Result struct {
Page struct {
PageIndex int `json:"pageIndex"`
PageSize int `json:"pageSize"`
Total int `json:"total"`
} `json:"page"`
Records []struct {
DivertorId string `json:"divertorId"`
DivertorName string `json:"divertorName"`
EquipmentName string `json:"equipmentName"`
EquipmentPn string `json:"equipmentPn"`
EquipmentSn string `json:"equipmentSn"`
Id string `json:"id"`
Kwp float64 `json:"kwp"`
MonKwh float64 `json:"monKwh"`
NowKw float64 `json:"nowKw"`
ParamDcacCode string `json:"paramDcacCode"`
ParamDcdcCode string `json:"paramDcdcCode"`
PowerPlantId string `json:"powerPlantId"`
PowerPlantName string `json:"powerPlantName"`
RatedPower float64 `json:"ratedPower"`
SoftDcacCode string `json:"softDcacCode"`
SoftDcdcCode string `json:"softDcdcCode"`
Status int `json:"status"`
SumKwh float64 `json:"sumKwh"`
TodayKwh float64 `json:"todayKwh"`
UpdateTime string `json:"updateTime"`
UserId string `json:"userId"`
UserName string `json:"userName"`
YearKwh float64 `json:"yearKwh"`
} `json:"records"`
Statistics struct {
AlarmNumber int `json:"alarmNumber"`
OfflineNumber int `json:"offlineNumber"`
OnlineNumber int `json:"onlineNumber"`
SumNumber int `json:"sumNumber"`
} `json:"statistics"`
} `json:"result"`
Success bool `json:"success"`
Timestamp int `json:"timestamp"`
}
// 逆变器详情响应
type EquipmentDetailRes struct {
Code int `json:"code"`
Message string `json:"message"`
Obj struct{} `json:"obj"`
PlantAddress string `json:"plantAddress"`
Result struct {
DivertorId string `json:"divertorId"`
DivertorName string `json:"divertorName"`
EquipmentName string `json:"equipmentName"`
EquipmentPn string `json:"equipmentPn"`
EquipmentSn string `json:"equipmentSn"`
Id string `json:"id"`
Kwp float64 `json:"kwp"`
MonKwh float64 `json:"monKwh"`
NowKw float64 `json:"nowKw"`
ParamDcacCode string `json:"paramDcacCode"`
ParamDcdcCode string `json:"paramDcdcCode"`
PowerPlantId string `json:"powerPlantId"`
PowerPlantName string `json:"powerPlantName"`
RatedPower float64 `json:"ratedPower"`
SoftDcacCode string `json:"softDcacCode"`
SoftDcdcCode string `json:"softDcdcCode"`
Status int `json:"status"`
SumKwh float64 `json:"sumKwh"`
TodayKwh float64 `json:"todayKwh"`
UpdateTime string `json:"updateTime"`
UserId string `json:"userId"`
UserName string `json:"userName"`
YearKwh float64 `json:"yearKwh"`
} `json:"result"`
Success bool `json:"success"`
Timestamp int `json:"timestamp"`
}
// 逆变器实时数据
type EquipmentRealDataRes struct {
Code int `json:"code"`
Message string `json:"message"`
Obj struct{} `json:"obj"`
Result struct {
EquipmentModel string `json:"equipmentModel"`
FullHours float64 `json:"fullHours"`
InternalTemperature float64 `json:"internalTemperature"`
MonKwh float64 `json:"monKwh"`
NowKw float64 `json:"nowKw"`
ParamData struct {
ParamList []struct {
Key string `json:"key"`
Name string `json:"name"`
Unit string `json:"unit"`
Value interface{} `json:"value"`
} `json:"paramList"`
Time string `json:"time"`
} `json:"paramData"`
PowerFactor float64 `json:"powerFactor"`
PowerPercentage float64 `json:"powerPercentage"`
PowerUnit string `json:"powerUnit"`
RatedPower float64 `json:"ratedPower"`
RatedVoltage float64 `json:"ratedVoltage"`
SumKwh float64 `json:"sumKwh"`
TodayKwh float64 `json:"todayKwh"`
YearKwh float64 `json:"yearKwh"`
} `json:"result"`
Success bool `json:"success"`
Timestamp int `json:"timestamp"`
}
// 逆变器历史数据
type EquipmentHistoryRes struct {
Code int `json:"code"`
Message string `json:"message"`
Obj struct{} `json:"obj"`
Result []struct {
ParamList []struct {
Key string `json:"key"`
Name string `json:"name"`
Unit string `json:"unit"`
Value interface{} `json:"value"`
} `json:"paramList"`
Time string `json:"time"`
} `json:"result"`
Success bool `json:"success"`
Timestamp int `json:"timestamp"`
}
// 逆变器日统计数据
type EquipmentStaticDayRes struct {
Code int `json:"code"`
Message string `json:"message"`
Obj struct{} `json:"obj"`
Success bool `json:"success"`
Timestamp int64 `json:"timestamp"`
Result []struct {
Time string `json:"time"`
Kwh float64 `json:"kwh"`
KwhUnit string `json:"kwhUnit"`
Earnings float64 `json:"earnings"`
EarningsUnit string `json:"earningsUnit"`
} `json:"result"`
}
// 逆变器月统计数据
type EquipmentStaticMonthRes struct {
Code int `json:"code"`
Message string `json:"message"`
Obj struct{} `json:"obj"`
Success bool `json:"success"`
Timestamp int64 `json:"timestamp"`
Result []struct {
Time string `json:"time"`
Kwh float64 `json:"kwh"`
KwhUnit string `json:"kwhUnit"`
Earnings float64 `json:"earnings"`
EarningsUnit string `json:"earningsUnit"`
} `json:"result"`
}
// 逆变器年统计数据
type EquipmentStaticYearRes struct {
Code int `json:"code"`
Message string `json:"message"`
Obj struct{} `json:"obj"`
Success bool `json:"success"`
Timestamp int64 `json:"timestamp"`
Result []struct {
Time string `json:"time"`
Kwh float64 `json:"kwh"`
KwhUnit string `json:"kwhUnit"`
Earnings float64 `json:"earnings"`
EarningsUnit string `json:"earningsUnit"`
} `json:"result"`
}
// 逆变器报警列表数据
type EquipmentAlarmListRes struct {
Code int `json:"code"`
Message string `json:"message"`
Obj struct{} `json:"obj"`
Result []struct {
AlarmCode string `json:"alarmCode"`
AlarmContent string `json:"alarmContent"`
AlarmGrade string `json:"alarmGrade"`
AlarmType string `json:"alarmType"`
CausesAnalysis string `json:"causesAnalysis"`
DiagnosticAdvice string `json:"diagnosticAdvice"`
EquipmentPn string `json:"equipmentPn"`
EquipmentSn string `json:"equipmentSn"`
Id string `json:"id"`
PlantContactPhone string `json:"plantContactPhone"`
PowerPlantId string `json:"powerPlantId"`
PowerPlantName string `json:"powerPlantName"`
ReportedTime string `json:"reportedTime"`
UserId string `json:"userId"`
UserName string `json:"userName"`
} `json:"result"`
Success bool `json:"success"`
Timestamp int `json:"timestamp"`
}
// 逆变器报警数据
type EquipmentAlarmDetailRes struct {
Code int `json:"code"`
Message string `json:"message"`
Obj struct{} `json:"obj"`
Result struct {
Page struct {
PageIndex int `json:"pageIndex"`
PageSize int `json:"pageSize"`
Total int `json:"total"`
} `json:"page"`
Records []struct {
AlarmCode string `json:"alarmCode"`
AlarmContent string `json:"alarmContent"`
AlarmGrade string `json:"alarmGrade"`
AlarmType string `json:"alarmType"`
CausesAnalysis string `json:"causesAnalysis"`
DiagnosticAdvice string `json:"diagnosticAdvice"`
EquipmentPn string `json:"equipmentPn"`
EquipmentSn string `json:"equipmentSn"`
Id string `json:"id"`
Phone string `json:"phone"`
PowerPlantAddress string `json:"powerPlantAddress"`
PowerPlantId string `json:"powerPlantId"`
PowerPlantName string `json:"powerPlantName"`
ReportedTime string `json:"reportedTime"`
RestoreTime string `json:"restoreTime"`
Status string `json:"status"`
UserId string `json:"userId"`
UserName string `json:"userName"`
} `json:"records"`
} `json:"result"`
Success bool `json:"success"`
Timestamp int `json:"timestamp"`
}
// 逆变器报警详情
type EquipmentAlarmDetailDataRes struct {
Code int `json:"code"`
Message string `json:"message"`
Result struct {
Address string `json:"address"`
AlarmCode string `json:"alarmCode"`
AlarmContent string `json:"alarmContent"`
AlarmGrade int `json:"alarmGrade"`
AlarmGradeName string `json:"alarmGradeName"`
AlarmSource int `json:"alarmSource"`
AlarmSourceName string `json:"alarmSourceName"`
CausesAnalysis string `json:"causesAnalysis"`
ContactName string `json:"contactName"`
DeviceModel string `json:"deviceModel"`
DiagnosticAdvice string `json:"diagnosticAdvice"`
Email string `json:"email"`
Id string `json:"id"`
IgnoreStatus int `json:"ignoreStatus"`
IgnoreStatusName string `json:"ignoreStatusName"`
OrganizationId string `json:"organizationId"`
OrganizationName string `json:"organizationName"`
Phone string `json:"phone"`
Pn string `json:"pn"`
PowerPlantId string `json:"powerPlantId"`
PowerPlantName string `json:"powerPlantName"`
ProductTypeName string `json:"productTypeName"`
ReportedTime string `json:"reportedTime"`
RestoreTime string `json:"restoreTime"`
Sn string `json:"sn"`
Status int `json:"status"`
StatusName string `json:"statusName"`
UserId string `json:"userId"`
UserName string `json:"userName"`
} `json:"result"`
Success bool `json:"success"`
Timestamp string `json:"timestamp"`
}
// EquipmentListQueryRes 数据库查询返回
type EquipmentListQueryRes struct {
g.Meta `mime:"application/json"`
common.ListRes
List []entity.Equipment `json:"list"`
}

217
api/hwy/plant/plant.go Normal file
View File

@ -0,0 +1,217 @@
package plant
import (
"context"
"encoding/json"
"github.com/gogf/gf/v2/frame/g"
"github.com/tiger1103/gfast/v3/api/hwy/client"
"github.com/tiger1103/gfast/v3/api/hwy/sign"
"strconv"
)
type PlantApi struct {
}
// 禾望云电站列表请求
type PlantListReq struct {
g.Meta `path:"/plant/list" method:"get" tags:"禾望云相关" summary:"获取电站列表"`
PageNum int `json:"pageNum" dc:"分页数" v:"required"`
PageSize int `json:"pageSize" dc:"分页大小" v:"required"`
UserId string `json:"userId" dc:"用户ID"`
}
func (p PlantApi) GetPlantList(ctx context.Context, req *PlantListReq) (res *PlantListRes, err error) {
res = new(PlantListRes)
data := make(map[string]string)
data["pageIndex"] = strconv.Itoa(req.PageNum)
data["pageSize"] = strconv.Itoa(req.PageSize)
sign.SignByRSA(data)
err, bytes := client.Get("/openApi/powerPlant/getPowerPlantList", data)
if err != nil {
return res, err
}
err = json.Unmarshal(bytes, &res)
if err != nil {
return res, err
}
model := g.Model("plant")
for _, record := range res.Result.Records {
err = g.Try(ctx, func(ctx context.Context) {
// 尝试查找记录
exists, _ := model.Where("id", record.Id).One()
// 准备数据
data := map[string]interface{}{
"id": record.Id,
"name": record.Name,
"address": record.Address,
"city": record.City,
"district": record.District,
"province": record.Province,
"latitude": record.Latitude,
"longitude": record.Longitude,
"kwp": record.Kwp,
"monKwh": record.MonKwh,
"nowKw": record.NowKw,
"sumKwh": record.SumKwh,
"todayKwh": record.TodayKwh,
"yearKwh": record.YearKwh,
"networkTime": record.NetworkTime,
"updateTime": record.UpdateTime,
"companyId": record.CompanyId,
"companyName": record.CompanyName,
"ownerId": record.OwnerId,
"ownerName": record.OwnerName,
"serviceProviderName": record.ServiceProviderName,
"serviceProviderPhone": record.ServiceProviderPhone,
"networkType": record.NetworkType,
"powerPlantType": record.PowerPlantType,
"status": record.Status,
"paymentType": record.PaymentType,
"plantContact": record.PlantContact,
"plantContactPhone": record.PlantContactPhone,
"plantImg": record.PlantImg,
"remark": record.Remark,
"dipAngle": record.DipAngle,
"orientationAngle": record.OrientationAngle,
"subassemblyNumber": record.SubassemblyNumber,
}
// 根据是否存在来决定是更新还是插入
if exists.IsEmpty() {
model.Insert(data)
} else {
model.Where("id", record.Id).Data(data).Update()
}
})
}
return res, err
}
// 获取电站详情
type PlantAddReq struct {
g.Meta `path:"/plant/deatil" method:"get" tags:"禾望云相关" summary:"获取电站详情"`
PlantId string `json:"plantId" dc:"电站ID" v:"required"`
}
func (p PlantApi) PlanAdd(ctx context.Context, req *PlantAddReq) (res *PlantAddRes, err error) {
res = new(PlantAddRes)
data := make(map[string]string)
data["plantId"] = req.PlantId
sign.SignByRSA(data)
err, bytes := client.Get("/openApi/powerPlant/getPowerPlantDetailById", data)
if err != nil {
return res, err
}
err = json.Unmarshal(bytes, &res)
g.Dump(res)
return res, nil
}
// 禾望云电站实时数据
type RealDataReq struct {
g.Meta `path:"/plant/real/data" method:"get" tags:"禾望云相关" summary:"获取电站实时数据"`
PlantId string `json:"plantId" dc:"电站ID" v:"required"`
}
func (p PlantApi) RealData(ctx context.Context, req *RealDataReq) (res *RealDataRes, err error) {
res = new(RealDataRes)
data := make(map[string]string)
data["plantId"] = req.PlantId
sign.SignByRSA(data)
err, bytes := client.Get("/openApi/powerPlant/getPowerPlantRealData", data)
if err != nil {
return res, err
}
err = json.Unmarshal(bytes, &res)
return res, nil
}
// 禾望云电站日统计信息
type DayStaticReq struct {
g.Meta `path:"/plant/static/day" method:"get" tags:"禾望云相关" summary:"获取电站日统计信息"`
StartTime string `json:"startTime" dc:"开始日期 yyyy-MM-dd" v:"required"`
PlantId string `json:"plantId" dc:"电站ID" v:"required"`
EndTime string `json:"endTime" dc:"结束日期 yyyy-MM-dd" v:"required"`
}
func (p PlantApi) DayStatic(ctx context.Context, req *DayStaticReq) (res *DayStaticRes, err error) {
res = new(DayStaticRes)
data := make(map[string]string)
data["startTime"] = req.StartTime
data["plantId"] = req.PlantId
data["endTime"] = req.EndTime
sign.SignByRSA(data)
err, bytes := client.Get("/openApi/powerPlant/getPowerPlantDayStatisticsData", data)
if err != nil {
return res, err
}
err = json.Unmarshal(bytes, &res)
return res, nil
}
// 禾望云电站月统计信息
type MonthStaticReq struct {
g.Meta `path:"/plant/static/month" method:"get" tags:"禾望云相关" summary:"获取电站月统计信息"`
StartTime string `json:"startTime" dc:"开始日期 yyyy-MM" v:"required"`
PlantId string `json:"plantId" dc:"电站ID" v:"required"`
EndTime string `json:"endTime" dc:"结束日期 yyyy-MM" v:"required"`
}
func (p PlantApi) MonthStatic(ctx context.Context, req *MonthStaticReq) (res *MonthStaticRes, err error) {
res = new(MonthStaticRes)
data := make(map[string]string)
data["startTime"] = req.StartTime
data["plantId"] = req.PlantId
data["endTime"] = req.EndTime
sign.SignByRSA(data)
err, bytes := client.Get("/openApi/powerPlant/getPowerPlantMonthStatisticsData", data)
if err != nil {
return res, err
}
err = json.Unmarshal(bytes, &res)
return res, nil
}
// 禾望云电站年统计信息
type YearStaticReq struct {
g.Meta `path:"/plant/static/year" method:"get" tags:"禾望云相关" summary:"获取电站年统计信息"`
StartTime string `json:"startTime" dc:"开始日期 yyyy" v:"required"`
PlantId string `json:"plantId" dc:"电站ID" v:"required"`
EndTime string `json:"endTime" dc:"结束日期 yyyy" v:"required"`
}
func (p PlantApi) YearStatic(ctx context.Context, req *YearStaticReq) (res *YearStaticRes, err error) {
res = new(YearStaticRes)
data := make(map[string]string)
data["startTime"] = req.StartTime
data["plantId"] = req.PlantId
data["endTime"] = req.EndTime
sign.SignByRSA(data)
err, bytes := client.Get("/openApi/powerPlant/getPowerPlantYearStatisticsData", data)
if err != nil {
return res, err
}
err = json.Unmarshal(bytes, &res)
return res, nil
}
// 禾望云电站历史记录
type HistoryStaticReq struct {
g.Meta `path:"/plant/static/history" method:"get" tags:"禾望云相关" summary:"获取电站历史数据"`
PlantId string `json:"plantId" dc:"电站ID" v:"required"`
Time string `json:"time" dc:"日期字符 yyyy-MM-dd" v:"required"`
}
func (p PlantApi) HistoryStatic(ctx context.Context, req *HistoryStaticReq) (res *HistoryStaticRes, err error) {
res = new(HistoryStaticRes)
data := make(map[string]string)
data["plantId"] = req.PlantId
data["time"] = req.Time
sign.SignByRSA(data)
err, bytes := client.Get("/openApi/powerPlant/getPowerPlantHistoryPower", data)
if err != nil {
return res, err
}
err = json.Unmarshal(bytes, &res)
return res, nil
}

189
api/hwy/plant/res.go Normal file
View File

@ -0,0 +1,189 @@
package plant
// 电站详情
type PlantAddRes struct {
Code int `json:"code"`
Message string `json:"message"`
Obj struct {
} `json:"obj"`
Result struct {
Address string `json:"address"`
City string `json:"city"`
CompanyId string `json:"companyId"`
CompanyName string `json:"companyName"`
DipAngle int `json:"dipAngle"`
District string `json:"district"`
Earnings int `json:"earnings"`
Id string `json:"id"`
Kwp int `json:"kwp"`
Latitude string `json:"latitude"`
Longitude string `json:"longitude"`
MonEarnings int `json:"monEarnings"`
MonKwh float64 `json:"monKwh"`
Name string `json:"name"`
NetworkType string `json:"networkType"`
NowKw int `json:"nowKw"`
OrientationAngle int `json:"orientationAngle"`
OwnerId string `json:"ownerId"`
PaymentType string `json:"paymentType"`
PlantContact string `json:"plantContact"`
PlantContactPhone string `json:"plantContactPhone"`
PlantImg string `json:"plantImg"`
PowerPlantType string `json:"powerPlantType"`
Province string `json:"province"`
Remark string `json:"remark"`
ServiceProviderEmail string `json:"serviceProviderEmail"`
ServiceProviderPhone string `json:"serviceProviderPhone"`
Status int `json:"status"`
SubassemblyNumber int `json:"subassemblyNumber"`
SumEarnings int `json:"sumEarnings"`
SumKwh float64 `json:"sumKwh"`
TodayEarnings int `json:"todayEarnings"`
TodayKwh float64 `json:"todayKwh"`
UpdateTime string `json:"updateTime"`
UserName string `json:"userName"`
YearEarnings int `json:"yearEarnings"`
YearKwh int `json:"yearKwh"`
} `json:"result"`
Success bool `json:"success"`
Timestamp int `json:"timestamp"`
}
// 电站列表
type PlantListRes struct {
Code int `json:"code"`
Message string `json:"message"`
Obj struct {
} `json:"obj"`
Result struct {
Page struct {
PageIndex int `json:"pageIndex"`
PageSize int `json:"pageSize"`
Total int `json:"total"`
} `json:"page"`
Records []struct {
Address string `json:"address"`
City string `json:"city"`
CompanyId string `json:"companyId"`
CompanyName string `json:"companyName"`
DipAngle int `json:"dipAngle"`
District string `json:"district"`
Id string `json:"id"`
Kwp float64 `json:"kwp"`
Latitude string `json:"latitude"`
Longitude string `json:"longitude"`
MonKwh float64 `json:"monKwh"`
Name string `json:"name"`
NetworkTime string `json:"networkTime"`
NetworkType string `json:"networkType"`
NowKw float64 `json:"nowKw"`
OrientationAngle int `json:"orientationAngle"`
OwnerId string `json:"ownerId"`
OwnerName string `json:"ownerName"`
PaymentType string `json:"paymentType"`
PlantContact string `json:"plantContact"`
PlantContactPhone string `json:"plantContactPhone"`
PlantImg string `json:"plantImg"`
PowerPlantType string `json:"powerPlantType"`
Province string `json:"province"`
Remark string `json:"remark"`
ServiceProviderName string `json:"serviceProviderName"`
ServiceProviderPhone string `json:"serviceProviderPhone"`
Status int `json:"status"`
SubassemblyNumber int `json:"subassemblyNumber"`
SumKwh float64 `json:"sumKwh"`
TodayKwh float64 `json:"todayKwh"`
UpdateTime string `json:"updateTime"`
YearKwh float64 `json:"yearKwh"`
} `json:"records"`
Statistics struct {
AlarmNumber int `json:"alarmNumber"`
OfflineNumber int `json:"offlineNumber"`
OnlineNumber int `json:"onlineNumber"`
SumNumber int `json:"sumNumber"`
} `json:"statistics"`
} `json:"result"`
Success bool `json:"success"`
Timestamp int `json:"timestamp"`
}
// 电站实时数据
type RealDataRes struct {
Code int `json:"code"`
Message string `json:"message"`
Obj struct {
} `json:"obj"`
Result struct {
NowKw int `json:"nowKw"`
PowerUnit string `json:"powerUnit"`
Time string `json:"time"`
} `json:"result"`
Success bool `json:"success"`
Timestamp int `json:"timestamp"`
}
// 电站日统计信息
type DayStaticRes struct {
Code int `json:"code"`
Message string `json:"message"`
Obj struct {
} `json:"obj"`
Result []struct {
Earnings int `json:"earnings"`
EarningsUnit string `json:"earningsUnit"`
Kwh int `json:"kwh"`
KwhUnit string `json:"kwhUnit"`
Time string `json:"time"`
} `json:"result"`
Success bool `json:"success"`
Timestamp int `json:"timestamp"`
}
// 电站月统计信息
type MonthStaticRes struct {
Code int `json:"code"`
Message string `json:"message"`
Obj struct {
} `json:"obj"`
Result []struct {
Earnings int `json:"earnings"`
EarningsUnit string `json:"earningsUnit"`
Kwh int `json:"kwh"`
KwhUnit string `json:"kwhUnit"`
Time string `json:"time"`
} `json:"result"`
Success bool `json:"success"`
Timestamp int `json:"timestamp"`
}
// 电站年统计信息
type YearStaticRes struct {
Code int `json:"code"`
Message string `json:"message"`
Obj struct {
} `json:"obj"`
Result []struct {
Earnings int `json:"earnings"`
EarningsUnit string `json:"earningsUnit"`
Kwh int `json:"kwh"`
KwhUnit string `json:"kwhUnit"`
Time string `json:"time"`
} `json:"result"`
Success bool `json:"success"`
Timestamp int `json:"timestamp"`
}
// 电站历史记录
type HistoryStaticRes struct {
Code int `json:"code"`
Message string `json:"message"`
Obj struct {
} `json:"obj"`
Result []struct {
NowKw int `json:"nowKw"`
PowerUnit string `json:"powerUnit"`
Time string `json:"time"`
} `json:"result"`
Success bool `json:"success"`
Timestamp int `json:"timestamp"`
}

17
api/hwy/router/router.go Normal file
View File

@ -0,0 +1,17 @@
package hwy
import (
"github.com/gogf/gf/v2/net/ghttp"
"github.com/tiger1103/gfast/v3/api/hwy/equipment"
"github.com/tiger1103/gfast/v3/api/hwy/plant"
)
func InitHwyAPI(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareCORS)
group.Group("/manage", func(group *ghttp.RouterGroup) {
group.Group("/api/v1", func(group *ghttp.RouterGroup) {
group.Bind(new(plant.PlantApi))
group.Bind(new(equipment.EquipmentApi))
})
})
}

92
api/hwy/sign/sign.go Normal file
View File

@ -0,0 +1,92 @@
package sign
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"fmt"
"log"
"net/url"
"sort"
"strings"
)
const APPID = "a23b2d45b0214f7ebbe167cd2e490435"
const PRI_KEY = "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAOAoyk5ot2KHo3hFga+ozeSO6XORk6wd4MkyETSGCDrha8klXfTukAJfYz3eS/PpkdLJTGrd34D21BkKzJ7Pc+ExMwTEc4xTaWU9vVJWlshVgHUsfbRhVps3IivtK9e268j+7xHf0I6j0zJ7zOLO6aADf76e/GqVfA+rz0DPPR3jAgMBAAECgYEA3blbSy2nX42dM6VE/zTw28hEwOzZbiFrrHearyJ4140MKb2Nb23eAorJxnOxG0YI2MAGl5p5rr331AFrnxbCnb4JZ5fnT1uBNtmH4uEnHhIhQm3CsSukFWvnO5KRD+9Zl3BKUSor2ynCs5/EO0hcKlKCYtHiXKzkkUi65rLuH4kCQQD2tlELA8YDMGgKNOF3ZpvRrTqKxGb4wwg+jUX4ns4oXutc57EfG4R96MWG5gu4x/3ZVrHPMHsmPClnK7hAyXP/AkEA6JkfBiLkhlCmkCn5F3qDwuQNzTHqdm/Ql71Hogk6+2itVgJ9ua+Eegw5cVynPqrEWW2e7VbNjuiVlkMLJE0GHQJBAKFCOCTf+YzaFhcdy4X1DsJ13T8Y80mEiZ4BT4wbmRswN92JH+/6V5bJEFuFgIHuTxHBpgWMZeJvDoz+Obg3NVcCQE+tdFUzyrjAE+66khua2lv+p0OtX7Xmo7v3GPzG0K+isg4OmGbtWyI74cmVha0P7mb8CD8hRxU3U1a/7Kcow3kCQQCky3My+Qr+offSBqBE1cuFbkJ09G6nlotlPlO0pVnXOHMsUBCkfocv6QJ25KooppU1C1Cktgf9BYJ9b/r+r1Lu"
const HOST = "http://openapi.hopewindcloud.com/"
// 对参数进行签名,并为 Map 添加 appid 和 sign 参数
func SignByRSA(params map[string]string) string {
params["appid"] = APPID
content := CreateLinkString(params)
privateKeyBytes, err := base64.StdEncoding.DecodeString(PRI_KEY)
if err != nil {
return ""
}
pkcs8PrivateKey, err := x509.ParsePKCS8PrivateKey(privateKeyBytes)
if err != nil {
log.Fatalf("Failed to parse PKCS#8 private key: %v", err)
}
privateKey, ok := pkcs8PrivateKey.(*rsa.PrivateKey)
if !ok {
log.Fatal("Private key is not of type RSA")
}
h := sha1.New()
_, err = h.Write([]byte(content))
if err != nil {
fmt.Println(err)
return ""
}
hashed := h.Sum(nil)
signed, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA1, hashed)
if err != nil {
fmt.Println(err)
return ""
}
sign := url.QueryEscape(base64.StdEncoding.EncodeToString(signed))
params["sign"] = sign
return sign
}
// paramsFilter 过滤参数
func paramsFilter(params map[string]interface{}) map[string]string {
result := make(map[string]string)
if params == nil || len(params) == 0 {
return result
}
for key, value := range params {
if value == nil || key == "sign" {
continue
}
result[key] = value.(string)
}
return result
}
// createLinkString 创建链接字符串
func CreateLinkString(params map[string]string) string {
if params == nil {
return ""
}
// 排序键
keys := make([]string, 0, len(params))
for key := range params {
keys = append(keys, key)
}
sort.Strings(keys)
sb := strings.Builder{}
keyLastNum := len(keys) - 1
for i, key := range keys {
value := params[key]
sb.WriteString(key)
sb.WriteString("=")
sb.WriteString(value)
if i != keyLastNum {
sb.WriteString("&")
}
}
return sb.String()
}

39
api/pilot/manage/login.go Normal file
View File

@ -0,0 +1,39 @@
package manage
import (
"context"
"github.com/gogf/gf/v2/frame/g"
)
type LOGIN struct {
}
type LoginReq struct {
g.Meta `path:"login" summary:"pilot登录" method:"post" tags:"无人机遥控器"`
Username string `json:"username" v:"required"`
Password string `json:"password" v:"required"`
//Flag int `json:"flag" v:"required"`
}
type LoginRes struct {
Username string `json:"username"`
UserId string `json:"user_id"`
WorkspaceId string `json:"workspace_id"`
UserType int `json:"user_type"`
MqttUsername string `json:"mqtt_username"`
MqttPassword string `json:"mqtt_password"`
AccessToken string `json:"access_token"`
MqttAddr string `json:"mqtt_addr"`
}
func (receiver *LOGIN) Login(ctx context.Context, req *LoginReq) (res *LoginRes, err error) {
res = &LoginRes{}
res.Username = "pilot"
res.UserId = "be7c6c3d-afe9-4be4-b9eb-c55066c0914e"
res.WorkspaceId = "e3dea0f5-37f2-4d79-ae58-490af3228069"
res.UserType = 2
res.MqttUsername = "pilot"
res.MqttPassword = "pilot123"
res.AccessToken = "abc"
res.MqttAddr = "tcp://jl.yj-3d.com:1883"
return
}

122
api/pilot/manage/manage.go Normal file
View File

@ -0,0 +1,122 @@
package manage
import (
"context"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
type MANAGE struct {
}
type UsualRes struct {
}
type BindReq struct {
g.Meta `path:"devices/{device_sn}/binding" summary:"绑定设备" method:"post" tags:"无人机遥控器"`
DeviceSN string `json:"device_sn"`
UserID string `json:"user_id"`
WorkspaceId string `json:"workspace_id"`
}
func (receiver *MANAGE) Bind(ctx context.Context, req *BindReq) (res *UsualRes, err error) {
fmt.Println(req.DeviceSN)
fmt.Println(req.UserID)
fmt.Println(req.WorkspaceId)
return
}
type UserCurrentReq struct {
g.Meta `path:"users/current" summary:"获取当前用户信息信息" method:"get" tags:"无人机遥控器"`
}
type UserCurrentRes struct {
Username string `json:"username"`
UserId string `json:"user_id"`
WorkspaceId string `json:"workspace_id"`
UserType int `json:"user_type"`
MqttUsername string `json:"mqtt_username"`
MqttPassword string `json:"mqtt_password"`
MqttAddr string `json:"mqtt_addr"`
}
func (receiver *MANAGE) UserCurrent(ctx context.Context, req *UserCurrentReq) (res *UserCurrentRes, err error) {
res = &UserCurrentRes{}
res.Username = "pilot"
res.UserId = "be7c6c3d-afe9-4be4-b9eb-c55066c0914e"
res.WorkspaceId = "e3dea0f5-37f2-4d79-ae58-490af3228069"
res.UserType = 2
res.MqttUsername = "pilot"
res.MqttPassword = "pilot123"
res.MqttAddr = "tcp://jl.yj-3d.com:1883"
return
}
type WorkSpaceCurrentReq struct {
g.Meta `path:"workspaces/current" summary:"获取当前工作空间信息" method:"get" tags:"无人机遥控器"`
}
type WorkSpaceCurrentRes struct {
WorkspaceId string `json:"workspace_id"`
WorkspaceName string `json:"workspace_name"`
WorkspaceDesc string `json:"workspace_desc"`
PlatformName string `json:"platform_name"`
BindCode string `json:"bind_code"`
}
func (receiver *MANAGE) WorkSpaceCurrent(ctx context.Context, req *WorkSpaceCurrentReq) (res *WorkSpaceCurrentRes, err error) {
res = &WorkSpaceCurrentRes{}
res.WorkspaceName = "测试组"
res.WorkspaceId = "e3dea0f5-37f2-4d79-ae58-490af3228069"
res.WorkspaceDesc = "中煤科工测试云平台"
res.PlatformName = "中煤科工光伏平台"
res.BindCode = "qwe"
return
}
type TopologiesReq struct {
g.Meta `path:"workspaces/{workspace_id}/devices/topologies" summary:"获取设备拓扑列表)" method:"get" tags:"无人机遥控器"`
}
type TopologiesRes struct {
List []struct {
Hosts []struct {
Sn string `json:"sn"`
DeviceModel struct {
Key string `json:"key"`
Domain string `json:"domain"`
Type string `json:"type"`
SubType string `json:"sub_type"`
} `json:"device_model"`
OnlineStatus bool `json:"online_status"`
DeviceCallsign string `json:"device_callsign"`
UserId string `json:"user_id"`
UserCallsign string `json:"user_callsign"`
IconUrls struct {
NormalIconUrl string `json:"normal_icon_url"`
SelectedIconUrl string `json:"selected_icon_url"`
} `json:"icon_urls"`
} `json:"hosts"`
Parents []struct {
Sn string `json:"sn"`
OnlineStatus bool `json:"online_status"`
DeviceModel struct {
Key string `json:"key"`
Domain string `json:"domain"`
Type string `json:"type"`
SubType string `json:"sub_type"`
} `json:"device_model"`
DeviceCallsign string `json:"device_callsign"`
UserId string `json:"user_id"`
UserCallsign string `json:"user_callsign"`
IconUrls struct {
NormalIconUrl string `json:"normal_icon_url"`
SelectedIconUrl string `json:"selected_icon_url"`
} `json:"icon_urls"`
} `json:"parents"`
} `json:"list"`
}
func (receiver *MANAGE) Topologies(ctx context.Context, req *TopologiesReq) (res *TopologiesRes, err error) {
workspace_id := ghttp.RequestFromCtx(ctx).Get("workspace_id")
fmt.Println("workspace_id", workspace_id)
res = &TopologiesRes{}
return
}

51
api/pilot/map/map.go Normal file
View File

@ -0,0 +1,51 @@
package _map
import (
"context"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
type MAP struct {
}
type Element_groupsReq struct {
g.Meta `path:"workspaces/{workspace_id}/element-groups" summary:"获取元素列表" method:"get" tags:"无人机遥控器"`
GroupId string `json:"group_id"`
IsDistributed bool `json:"is_distributed"`
}
type Element_groupsRes struct {
Id string `json:"id"`
Type int `json:"type"`
Name string `json:"name"`
IsLock bool `json:"is_lock"`
CreateTime int64 `json:"create_time"`
Elements []struct {
Id string `json:"id"`
Name string `json:"name"`
CreateTime int64 `json:"create_time"`
UpdateTime int64 `json:"update_time"`
Resource struct {
Type int `json:"type"`
UserName string `json:"user_name"`
Content struct {
Type string `json:"type"`
Properties struct {
Color string `json:"color"`
ClampToGround bool `json:"clampToGround"`
} `json:"properties"`
Geometry struct {
Type string `json:"type"`
Coordinates []interface{} `json:"coordinates"`
} `json:"geometry"`
} `json:"content"`
} `json:"resource"`
} `json:"elements"`
}
func (receiver MAP) Element_groups(ctx context.Context, req *Element_groupsReq) (res *Element_groupsRes, err error) {
workspace_id := ghttp.RequestFromCtx(ctx).Get("workspace_id")
fmt.Println("获取元素列表workspace_id", workspace_id)
return
}

32
api/pilot/pilot.go Normal file
View File

@ -0,0 +1,32 @@
package pilot
import (
"github.com/gogf/gf/v2/net/ghttp"
"github.com/tiger1103/gfast/v3/api/pilot/manage"
_map "github.com/tiger1103/gfast/v3/api/pilot/map"
"github.com/tiger1103/gfast/v3/api/pilot/wayline"
"github.com/tiger1103/gfast/v3/api/pilot/ws"
)
func InitPlilotAPI(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareCORS)
group.Group("/manage", func(group *ghttp.RouterGroup) {
group.Group("/api/v1", func(group *ghttp.RouterGroup) {
group.Bind(new(manage.LOGIN))
//service.GfToken().Middleware(group)
group.Bind(new(manage.MANAGE))
group.Bind(new(ws.WS))
})
})
group.Group("/wayline", func(group *ghttp.RouterGroup) {
group.Group("/api/v1", func(group *ghttp.RouterGroup) {
group.Bind(new(wayline.WAYLINE))
})
})
group.Group("/map", func(group *ghttp.RouterGroup) {
group.Group("/api/v1", func(group *ghttp.RouterGroup) {
group.Bind(new(_map.MAP))
})
})
}

View File

@ -0,0 +1,48 @@
package wayline
import (
"context"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
type WAYLINE struct {
}
type WaylinesReq struct {
g.Meta `path:"workspaces/{workspace_id}/waylines" summary:"获取航线列表" method:"get" tags:"无人机遥控器"`
Favorited bool `json:"favorited"`
Order_by string `json:"order_by"`
Page int `json:"page"`
PageSize int `json:"page_size"`
TemplateType []int `json:"template_type"`
}
type Wayline struct {
Id string `json:"id"`
DroneModelKey string `json:"drone_model_key"`
Favorited bool `json:"favorited"`
Name string `json:"name"`
PayloadModelKeys []string `json:"payload_model_keys"`
TemplateTypes []int `json:"template_types"`
UpdateTime int64 `json:"update_time"`
UserName string `json:"user_name"`
}
type WaylinesRes struct {
List []Wayline `json:"list"`
Pagination struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Total int `json:"total"`
} `json:"pagination"`
}
func (receiver WAYLINE) Waylines(ctx context.Context, req *WaylinesReq) (res *WaylinesRes, err error) {
workspace_id := ghttp.RequestFromCtx(ctx).Get("workspace_id")
fmt.Println("获取航线列表workspace_id", workspace_id)
fmt.Println(req.Order_by, req.TemplateType, req.Page, req.PageSize, req.Favorited)
res = &WaylinesRes{}
res.List = []Wayline{}
return
}

32
api/pilot/ws/ws.go Normal file
View File

@ -0,0 +1,32 @@
package ws
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
type WS struct {
}
type WsReq struct {
g.Meta `path:"ws2" summary:"ws2" method:"get" tags:"无人机"`
}
type UsualRes struct {
}
func (receiver *WS) WSConnect(ctx context.Context, req *WsReq) (res *UsualRes, err error) {
r := ghttp.RequestFromCtx(ctx)
ws, err := r.WebSocket()
if err != nil {
return nil, err
}
for {
_, _, err := ws.ReadMessage()
if err != nil {
return nil, err
}
//fmt.Println(msgType, string(msg))
}
}

162
api/project/index_count.go Normal file
View File

@ -0,0 +1,162 @@
package project
import (
"context"
"encoding/json"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
)
// PvModuleStatsReq 用于查询统计信息的请求结构体
type PvModuleStatsReq struct {
g.Meta `path:"moduleStats" method:"get" tags:"项目首页相关" summary:"获取模块的统计信息"`
SubProjectId string `json:"subProjectId"`
FangzhenId string `json:"fangzhenId"`
}
// PvModuleStatsRes 用于封装统计结果的响应结构体,调整为使用字符串描述类型
type PvModuleStatsRes struct {
Data []struct {
TypeDesc string `json:"typeDesc"`
Total int `json:"total"`
Complete int `json:"complete"`
} `json:"data"`
}
func (p Project) QueryPvModuleStats(ctx context.Context, req *PvModuleStatsReq) (res *PvModuleStatsRes, err error) {
model := g.Model("pv_module").Fields("type, COUNT(*) as total, SUM(CASE WHEN status='2' THEN 1 ELSE 0 END) as complete").Group("type")
if req.SubProjectId != "" {
model = model.Where("sub_projectid", req.SubProjectId)
}
if req.FangzhenId != "" {
model = model.Where("fangzhen_id", req.FangzhenId)
}
var stats []struct {
Type int `json:"type"`
Total int `json:"total"`
Complete int `json:"complete"`
}
err = model.Scan(&stats)
if err != nil {
glog.Error(ctx, "查询 pv_module 统计信息失败:", err)
return nil, err
}
var resultStats []struct {
TypeDesc string `json:"typeDesc"`
Total int `json:"total"`
Complete int `json:"complete"`
}
for _, stat := range stats {
typeDesc, exists := ConstMap[stat.Type]
if !exists {
glog.Error(ctx, "未知的 type", stat.Type)
continue // 或者处理未知类型
}
resultStats = append(resultStats, struct {
TypeDesc string `json:"typeDesc"`
Total int `json:"total"`
Complete int `json:"complete"`
}{TypeDesc: typeDesc, Total: stat.Total, Complete: stat.Complete})
}
return &PvModuleStatsRes{Data: resultStats}, nil
}
// 修改请求结构体以接收类型切片
type PvModuleQueryReq struct {
g.Meta `path:"indexCount" method:"get" tags:"项目首页相关" summary:"根据子项目ID、方针ID和类型来获取数据"`
SubProjectId string `json:"subProjectId"`
FangzhenId string `json:"fangzhenId"`
Types []int `json:"types"` // 新增字段,用于指定需要查询的类型
}
// 修改返回结果结构体,去除 total 和 complete
type PvModuleQueryRes struct {
Data map[int][]PVModule `json:"data"`
View string `json:"view" orm:"view"`
}
func (p Project) QueryPVModule(ctx context.Context, req *PvModuleQueryReq) (res *PvModuleQueryRes, err error) {
res = new(PvModuleQueryRes)
// 查询对应方针的 View 值
g.Model("qianqi_fangzhen").Where("id", req.FangzhenId).Scan(&res)
// 准备存储每种 type 对应的数据
data := make(map[int][]PVModule)
// 仅针对请求中指定的 type 进行查询
for _, t := range req.Types {
var modules []PVModule
queryModel := g.Model("pv_module").Where("type", t)
if req.SubProjectId != "" {
queryModel = queryModel.Where("sub_projectid", req.SubProjectId)
}
if req.FangzhenId != "" {
queryModel = queryModel.Where("fangzhen_id", req.FangzhenId)
}
err = queryModel.Scan(&modules)
if err != nil {
continue // 或处理错误
}
// 对 modules 进行处理,比如反序列化 Detail 字段、添加倾角和方位角等
var processedModules []PVModule
for _, m := range modules {
// 反序列化 Detail 字段
var detailMap map[string]interface{}
err = json.Unmarshal([]byte(m.Detail), &detailMap)
if err != nil {
continue
}
// 为其添加倾角和方位角
//detailMap["roll"] = m.Tilt
//detailMap["heading"] = m.Azimuth
// 重新序列化
serializedDetail, _ := json.Marshal(detailMap)
processedModules = append(processedModules, PVModule{
ID: m.ID,
FangzhenID: m.FangzhenID,
SubProjectID: m.SubProjectID,
WorkID: m.WorkID,
Name: m.Name,
Status: m.Status,
DoneTime: m.DoneTime,
Detail: string(serializedDetail),
Type: t,
DeviceID: m.DeviceID,
EquipmentSn: "",
})
}
// 遍历processedModules中那些DeviceID不为空的值取出数据然后查询equipment表根据设备id查询equipment表得到设备sn赋值给EquipmentSn
for i, mod := range processedModules {
if mod.DeviceID != "" {
var equipment struct {
EquipmentSn string
}
// 使用g.DB模型查询equipment表中的SN字段假定设备的序列号字段为SN
err := g.Model("equipment").Fields("equipmentSn").Where("id", mod.DeviceID).Scan(&equipment)
if err != nil {
// 处理查询错误,例如记录日志或跳过
continue
}
// 更新模块的EquipmentSn字段
processedModules[i].EquipmentSn = equipment.EquipmentSn
}
}
// 更新数据存储结构
data[t] = processedModules
}
res.Data = data
return res, nil
}

71
api/project/index_pv.go Normal file
View File

@ -0,0 +1,71 @@
package project
import (
"context"
"encoding/json"
"unsafe"
"github.com/gogf/gf/v2/frame/g"
lop "github.com/samber/lo/parallel"
)
// 根据大项目ID去查询所有的数据
type ProjectIndexModuleReq struct {
g.Meta `path:"index" method:"get" tags:"项目首页相关" summary:"根据项目ID获取数据"`
ID int64 `json:"id"` // 项目ID
}
type ProjectIndexModuleRes struct {
Data []PVModule `json:"data"` // PVModule 切片
}
// 根据大项目ID去查询所有的 pv_module 数据
func (p Project) ProjectIndexModule(ctx context.Context, req *ProjectIndexModuleReq) (res *ProjectIndexModuleRes, err error) {
// 准备返回结果
res = &ProjectIndexModuleRes{}
// 构建查询
var modules []PVModule
err = g.Model("pv_module").As("pm").
Fields("pm.id, pm.fangzhen_id, pm.sub_projectid, pm.work_id, pm.name, pm.status, pm.done_time, pm.detail, pm.type, pm.tilt,pm.azimuth").
InnerJoin("sub_project sp", "pm.sub_projectid = sp.id").
InnerJoin("sys_project sysp", "sp.project_id = sysp.id").
Where("sysp.id", req.ID).
Where("sysp.deleted_at IS NULL").
Scan(&modules)
// 错误处理
if err != nil {
return nil, err
}
// 并行处理每个元素
lop.ForEach[PVModule](modules, func(x PVModule, i int) {
// 如果不是光伏板则不处理
if x.Type != 15 {
return
}
// 反序列化 detail 字段
var detail map[string]interface{}
if err := json.Unmarshal([]byte(x.Detail), &detail); err != nil {
return
}
// 为其添加倾角和方位角
//detail["roll"] = x.Tilt
//detail["heading"] = x.Azimuth
// 重新序列化
detailBytes, err := json.Marshal(detail)
if err != nil {
return
}
// 重新赋值给 modules[i].Detail
modules[i].Detail = *(*string)(unsafe.Pointer(&detailBytes))
})
// 将查询结果赋值给返回结构
res.Data = modules
return res, nil
}

61
api/project/model.go Normal file
View File

@ -0,0 +1,61 @@
package project
// 大项目表
type SysProject struct {
ID int64 `json:"id"` // 项目ID
ProjectName string `json:"projectName"` // 项目名称
ShortName string `json:"shortName"` // 项目简称
PID int64 `json:"pId"` // 父ID
Status string `json:"status"` // 状态0正常 1停用
PicURL string `json:"picUrl"` // 项目图片URL
Lng string `json:"lng"` // 经度
Lat string `json:"lat"` // 纬度
Remark string `json:"remark"` // 备注
Type string `json:"type"` // 项目类型
ColourRGB string `json:"colourRgb"` // 展示颜色RGB值
CreateBy string `json:"createBy"` // 创建者
UpdateBy string `json:"updateBy"` // 更新者
CreateTime string `json:"createTime"` // 创建时间
UpdateTime string `json:"updateTime"` // 更新时间
DeletedAt string `json:"deletedAt"` // 删除时间
ProjectID string `json:"projectId"` // 废弃字段项目ID
View string `json:"view"` // 项目所在地视角参数
ProjectSite string `json:"projectSite"` // 项目地址
Principal string `json:"principal"` // 负责人
PrincipalPhone string `json:"principalPhone"` // 负责人电话
PrincipalXZ string `json:"principalXz"` // 小程序薪资负责人
Actual string `json:"actual"` // 实际容量
Plan string `json:"plan"` // 计划容量
OnStreamTime string `json:"onStreamTime"` // 开工时间
PunchRange string `json:"punchRange"` // 打卡范围(默认值:"09:00,18:00"
DesignTotal int `json:"designTotal"` // 设计总量
SecurityAgreement string `json:"securityAgreement"` // 安全协议书
IsType string `json:"isType"` // 项目类型1光伏 2风电
}
// 子项目表
type SubProject struct {
ID uint `json:"id"` // 主键ID
ProjectID uint `json:"projectId"` // 项目ID
ProjectName string `json:"projectName"` // 子项目名
CreatedAt string `json:"createdAt"` // 创建时间
}
// PVModule 表示光伏模块的数据模型
type PVModule struct {
ID uint `json:"id" dc:"主键ID"` // 主键ID用于唯一标识光伏模块
FangzhenID string `json:"fangzhenId" dc:"方阵ID"` // 方阵ID指示光伏模块所属方阵的唯一标识符
SubProjectID string `json:"subProjectId" dc:"子项目ID"` // 子项目ID标识光伏模块所属子项目的唯一标识符
WorkID string `json:"workId" dc:"工作ID"` // 工作ID表示光伏模块所关联的工作的唯一标识符
Name string `json:"name" dc:"名字"` // 名字,光伏模块的名称或标识
Status string `json:"status" dc:"状态"` // 状态表示光伏模块的当前状态。可能的取值有0表示未开始1表示进行中2表示已完成
DoneTime string `json:"doneTime" dc:"完成时间"` // 完成时间,指示光伏模块完成的时间
Detail string `json:"detail" dc:"坐标详细信息"` // 坐标详细信息,包含关于光伏模块的详细描述
Type int `json:"type" dc:"类型"` // 类型,表示光伏模块的类型
Tilt float64 `json:"-" orm:"tilt" dc:"倾斜角"` // 倾斜角
Azimuth float64 `json:"-" orm:"azimuth" dc:"方位角"` // 方位角
DeviceID string `json:"deviceID" orm:"device_id" dc:"设备ID"` // 设备ID
EquipmentSn string `json:"equipmentSn" dc:"设备序列号"` // 设备序列号
//Heading float64 `json:"heading" orm:"heading" dc:"方位角"` // 方位角
//Roll float64 `json:"roll" orm:"roll" dc:"倾斜角"` // 倾斜角
}

4
api/project/project.go Normal file
View File

@ -0,0 +1,4 @@
package project
type Project struct {
}

46
api/project/pv_update.go Normal file
View File

@ -0,0 +1,46 @@
package project
import (
"context"
"fmt"
"github.com/gogf/gf/v2/frame/g"
)
// 根据 ID 更新 pv_module 数据库的信息
type PvModuleUpdateBatchReq struct {
g.Meta `path:"updateBatch" method:"post" tags:"项目首页相关" summary:"批量更新数据"`
Modules []PVModule
}
type PvModuleUpdateRes struct {
}
// PvModuleUpdateBatch 根据ID批量更新对应的pv_module数据
func (p Project) PvModuleUpdateBatch(ctx context.Context, req *PvModuleUpdateBatchReq) (res *PvModuleUpdateRes, err error) {
res = &PvModuleUpdateRes{}
batchSize := 300 // 每批处理的数据量
for i := 0; i < len(req.Modules); i += batchSize {
end := i + batchSize
if end > len(req.Modules) {
end = len(req.Modules)
}
batch := req.Modules[i:end]
for _, module := range batch {
dataMap := g.Map{
"detail": module.Detail,
}
// 更新操作
if _, err := g.Model("pv_module").Data(dataMap).Where("id", module.ID).Update(); err != nil {
fmt.Printf("Error updating module ID %d: %v\n", module.ID, err)
return nil, err // 如果更新过程中出现错误,提前返回
}
}
// 可以在这里加入日志,记录这一批次的处理情况
fmt.Printf("Updated a batch of %d modules starting from ID %d\n", len(batch), batch[0].ID)
}
// 返回更新结果
return res, nil
}

12
api/project/router.go Normal file
View File

@ -0,0 +1,12 @@
package project
import "github.com/gogf/gf/v2/net/ghttp"
func InitProjectAPI(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareCORS)
group.Group("/manage", func(group *ghttp.RouterGroup) {
group.Group("/api/v1", func(group *ghttp.RouterGroup) {
group.Bind(new(Project))
})
})
}

38
api/project/type_map.go Normal file
View File

@ -0,0 +1,38 @@
package project
var ConstMap = map[int]string{
1: "防雷接地网",
2: "接地沟",
3: "接地敷设",
4: "接地检测",
5: "围栏",
6: "围栏基础",
7: "围栏安装",
8: "道路",
9: "道路路基",
10: "道路排水沟",
11: "低压部分",
12: "低压钻孔",
13: "低压桩基",
14: "低压支架",
15: "低压光伏板",
16: "低压直流电缆",
17: "低压接地线",
18: "低压逆变器安装",
19: "低压电缆沟开挖",
20: "低压敷设",
21: "低压调试",
22: "高压部分",
23: "高压箱变基础",
24: "高压箱变安装",
25: "高压电缆线开挖",
26: "高压电缆敷设",
27: "高压电缆试验",
28: "高压电缆调式试验",
29: "环网柜",
30: "环网柜基础",
31: "环网柜安装",
32: "环网柜敷设",
33: "环网柜试验",
34: "环网柜调试试验",
}

98
api/saft_hat/device.go Normal file
View File

@ -0,0 +1,98 @@
package saft_hat
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"time"
)
// 新增安全帽设备
type CreateDeviceReq struct {
g.Meta `path:"/device/create" method:"post" tags:"设备管理" summary:"创建新设备"`
DevNum string `json:"devNum" dc:"设备编号"`
DevName string `json:"devName" dc:"设备名称"`
ProjectID int64 `json:"projectId" dc:"项目ID"`
}
type CreateDeviceRes struct {
}
func (h Hat) CreateDevice(ctx context.Context, req *CreateDeviceReq) (res *CreateDeviceRes, err error) {
res = new(CreateDeviceRes)
device := g.Map{
"dev_num": req.DevNum,
"dev_name": req.DevName,
"project_id": req.ProjectID,
"create_time": time.Now(),
}
_, err = g.Model("device").Ctx(ctx).Insert(device)
if err != nil {
return res, err
}
return res, nil
}
// 获取安全帽设备列表
type DeviceListReq struct {
g.Meta `path:"/device/list" method:"get" tags:"设备管理" summary:"获取设备信息"`
ProjectId int64 `json:"projectId" dc:"项目ID"`
Page int64 `json:"page" dc:"请求的页码" v:"required"`
PageSize int64 `json:"pageSize" dc:"每页显示的条目数" v:"required"`
}
type DeviceListRes struct {
Devices []Device `json:"devices"` // 设备信息
}
func (h Hat) GetDevice(ctx context.Context, req *DeviceListReq) (res *DeviceListRes, err error) {
res = new(DeviceListRes)
offset := (req.Page - 1) * req.PageSize
var devices []Device
err = g.Model("device").Ctx(ctx).Where("project_id = ?", req.ProjectId).Offset(int(offset)).Limit(int(req.PageSize)).Scan(&devices)
if err != nil {
return nil, err
}
res.Devices = devices
return res, nil
}
// 删除设备信息
type DeleteDeviceReq struct {
g.Meta `path:"/device/delete" method:"delete" tags:"设备管理" summary:"删除设备"`
DevNum string `json:"devNum" dc:"设备编号"` // 设备编号
}
type DeleteDeviceRes struct {
}
func (h Hat) DeleteDevice(ctx context.Context, req *DeleteDeviceReq) (res *DeleteDeviceRes, err error) {
res = new(DeleteDeviceRes)
// 执行删除操作
_, err = g.Model("device").Ctx(ctx).Where("dev_num", req.DevNum).Delete()
if err != nil {
return res, err
}
return res, nil
}
// 修改安全帽的名称
type UpdateDeviceNameReq struct {
g.Meta `path:"/device/update/name" method:"post" tags:"设备管理" summary:"更新安全帽设备名称"`
DevNum string `json:"devNum" dc:"设备编号"`
DevName string `json:"devName" dc:"设备名称"`
}
type UpdateDeviceNameRes struct {
}
func (h Hat) UpdateDeviceName(ctx context.Context, req *UpdateDeviceNameReq) (res *UpdateDeviceNameRes, err error) {
res = new(UpdateDeviceNameRes)
_, err = g.Model("device").Data(g.Map{
"dev_name": req.DevName,
}).Where("dev_num", req.DevNum).Update()
if err != nil {
return res, err
}
return res, nil
}

39
api/saft_hat/entity.go Normal file
View File

@ -0,0 +1,39 @@
package saft_hat
import "time"
type Device struct {
DevNum string `json:"devNum" dc:"设备编号"`
DevName string `json:"devName" dc:"设备名称"`
Status int `json:"status" dc:"状态"`
CreateTime time.Time `json:"createTime" dc:"添加时间"`
UpdateTime time.Time `json:"updateTime" dc:"修改时间"`
ProjectID int64 `json:"projectId" dc:"项目id"`
Temperature float64 `json:"temperature" dc:"设备采集温度"`
Humidity float64 `json:"humidity" dc:"设备采集湿度"`
Posture int `json:"posture" dc:"姿势1表示正常-1表示脱帽-2表示倒地"`
BatteryTemp float64 `json:"batteryTemp" dc:"电池温度"`
FixedBy string `json:"fixedBy" dc:"定位方"`
BatteryLevel float64 `json:"batteryLevel" dc:"电量"`
IsLowBattery int `json:"isLowBattery" dc:"是否低电量 1为低电量0为正常"`
}
type Location struct {
DevNum string `json:"devNum" dc:"设备编号"`
Time string `json:"time" dc:"时间"`
Alarm *int `json:"alarm" dc:"告警信息"`
Status *int `json:"status" dc:"状态"`
Latitude float64 `json:"latitude" dc:"纬度"`
Longitude float64 `json:"longitude" dc:"经度"`
Elevation *int16 `json:"elevation" dc:"海拔"`
Speed *int16 `json:"speed" dc:"速度"`
Direction *int16 `json:"direction" dc:"方向"`
Mileage *int64 `json:"mileage" dc:"里程数"`
AccEnable *bool `json:"accEnable" dc:"ACC开关"`
LocateEnable *bool `json:"locateEnable" dc:"定位开关"`
LatitudeType *int8 `json:"latitudeType" dc:"纬度类型"` // 0:北纬, 1:南纬
LongitudeType *int8 `json:"longitudeType" dc:"经度类型"` // 0:东经, 1:西经
SpeedingWarn *bool `json:"speedingWarn" dc:"超速报警"`
PowerVoltageWarn *bool `json:"powerVoltageWarn" dc:"电压告警"`
PowerFailure *bool `json:"powerFailure" dc:"电源掉电"`
}

8
api/saft_hat/hat.go Normal file
View File

@ -0,0 +1,8 @@
package saft_hat
// 定义签名的 APPID 和密钥常量
const secret = "5366474e589c4dcfadeef223a466ca0b"
const appid = "0c9ab925c6684ab4a33350e15ee35062"
type Hat struct {
}

View File

@ -0,0 +1,54 @@
package saft_hat
import (
"context"
"errors"
"fmt"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
"io/ioutil"
"log"
)
// 危险源数据上传结构体
type Danger struct {
BT_MAC string `json:"BT_MAC"` // 危险源MAC
IMEI string `json:"IMEI"` // 设备IMEI编号
}
// 危险源数据上传请求
type DangerReq struct {
g.Meta `path:"/device/danger" method:"post" tags:"安全帽相关" summary:"接收危险源数据(不需要前端调用)"`
}
type DangerRes struct {
}
func (h Hat) Danger(ctx context.Context, req *DangerReq) (res *DangerRes, err error) {
res = new(DangerRes)
r := g.RequestFromCtx(ctx)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Failed to read request body: %v", err)
return nil, err
}
defer r.Body.Close()
timestamp := r.GetHeader("timestamp")
signature := r.GetHeader("signature")
if !VerifySignature(string(body)+timestamp, signature, secret) {
glog.Errorf(ctx, "Signature verification failed")
return nil, errors.New("signature verification failed")
}
var danger Danger
if err := gjson.DecodeTo(body, &danger); err != nil {
return nil, err
}
fmt.Println("危险源数据", danger)
return res, nil
}

259
api/saft_hat/hat_data.go Normal file
View File

@ -0,0 +1,259 @@
package saft_hat
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
"github.com/gorilla/websocket"
"io/ioutil"
"log"
"net/http"
"strconv"
"time"
)
func StartWs() {
// 设置 WebSocket 路由
http.HandleFunc("/ws", HandleConnections)
// 开启一个新的协程,处理消息广播
go HandleMessages()
// 启动 WebSocket 服务器
log.Println("http server started on :8222")
err := http.ListenAndServe(":8222", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
// WebSocket 部分
var clients = make(map[*websocket.Conn]bool) // 连接的客户端
var broadcast = make(chan []byte) // 广播通道
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// 处理连接
func HandleConnections(w http.ResponseWriter, r *http.Request) {
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatal(err)
}
defer ws.Close()
// 注册新客户端
clients[ws] = true
// 无限循环,保持连接活跃,但不处理任何消息
for {
if _, _, err := ws.ReadMessage(); err != nil {
log.Printf("error: %v", err)
delete(clients, ws)
break
}
}
}
// 处理消息
func HandleMessages() {
for {
// 从广播通道中获取消息
msg := <-broadcast
// 发送消息到所有连接的客户端
for client := range clients {
err := client.WriteMessage(websocket.TextMessage, msg)
if err != nil {
log.Printf("websocket write error: %v", err)
client.Close()
delete(clients, client)
}
}
}
}
// WS 推送的数据
type BroadcastLocation struct {
DevNum string `json:"devNum"`
Time string `json:"time"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
}
// Webhook 定位数据上传【远程发送给我们平台的数据】
type WebhookRequest struct {
Temperature float64 `json:"temperature"` // 设备采集温度数据
Humidity float64 `json:"humidity"` // 设备采集湿度数据
Posture int `json:"posture"` // 姿态1正常 -1脱帽 -2倒地
BatteryTemp float64 `json:"batteryTemp"` // 电池温度
FixedBy string `json:"fixedBy"` // 定位方式:GPS/BD、WIFI、BT
UtcDateTime int64 `json:"utcDateTime"` // 设备内部上传时间戳
IMEI string `json:"IMEI"` // 设备IMEI
BatteryLevel float64 `json:"batteryLevel"` // 电池电量
Charging int `json:"charging"` // 是否正在充电 1充电 0没充电
BluetoothMac string `json:"bluetoothMac"` // 蓝牙Mac地址
Type string `json:"type"` // 设备上传类型和时间
Latitude float64 `json:"latitude"` // WGS-84是国际标准GPS坐标Google Earth使用、或者GPS模块纬度坐标
Longitude float64 `json:"longitude"` // WGS-84是国际标准GPS坐标Google Earth使用、或者GPS模块经度坐标
Altitude int `json:"altitude"` // 海拔高度
BT string `json:"bt"` // 蓝牙定位相关:{个数}:{地址,信号}
LBS []string `json:"LBS"` // 多基站定位信息
MAC []string `json:"MAC"` // WiFi - MAC地址信号
}
// 定位数据上传请求
type DataReq struct {
g.Meta `path:"/device/data" method:"post" tags:"安全帽相关" summary:"接收安全帽数据(不需要前端调用)"`
}
type DataRes struct {
}
func (h Hat) Data(ctx context.Context, req *DataReq) (res *DataRes, err error) {
res = new(DataRes)
r := g.RequestFromCtx(ctx)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("读取请求头失败: %v", err)
return res, err
}
defer r.Body.Close() // 延迟关闭请求体
// 从请求头中获取时间戳和签名
timestamp := r.GetHeader("timestamp")
signature := r.GetHeader("signature")
// 验证签名的有效性
if !VerifySignature(string(body)+timestamp, signature, secret) {
glog.Errorf(ctx, "签名验证失败")
return res, errors.New("验证签名失败")
}
// 解析请求体中的JSON数据到预定义的结构体
var webhookData WebhookRequest
if err := gjson.DecodeTo(body, &webhookData); err != nil {
return res, err
}
// 拿到数据之后,先取出 IMEI 设备号,根据 IMEI 查询,不存在则先插入数据到 device 表
device := Device{
DevNum: webhookData.IMEI,
Temperature: webhookData.Temperature,
Humidity: webhookData.Humidity,
Posture: webhookData.Posture,
FixedBy: webhookData.FixedBy,
BatteryLevel: webhookData.BatteryLevel,
}
// 查询设备是否存在,存在则更新安全帽的状态信息
count, err := g.Model(&device).Where("dev_num", device.DevNum).Count()
if err != nil {
glog.Errorf(ctx, "查询设备是否存在出错:%v", err)
}
if count > 0 {
// 判断电池是否处于低电量状态
var isLowBattery int
// 电量小于 0.2 则设置为低电量
if device.BatteryLevel < 0.2 {
isLowBattery = 1
} else {
isLowBattery = 0
}
// 更新设备的数据
_, err = g.Model("device").Data(g.Map{
"temperature": device.Temperature,
"humidity": device.Humidity,
"posture": device.Posture,
"battery_temp": device.BatteryTemp,
"fixed_by": device.FixedBy,
"battery_level": device.BatteryLevel * 100,
"is_low_battery": isLowBattery,
"update_time": time.Now(),
}).Where("dev_num", device.DevNum).Update()
if err != nil {
glog.Errorf(ctx, "更新设备数据出错:%v", err)
}
}
// 将接收到的数据转换为 Location 结构体
location := Location{
DevNum: webhookData.IMEI, // 设备编号为 IMEI
Time: time.Now().Format("2006-01-02 15:04:05"), // 当前时间
Latitude: webhookData.Latitude, // 将纬度转换为指针类型
Longitude: webhookData.Longitude, // 将经度转换为指针类型
}
// 构建插入数据的参数
data := g.Map{
"dev_num": location.DevNum,
"time": location.Time,
"latitude": location.Latitude,
"longitude": location.Longitude,
}
// 检查经纬度是否同时为 0如果是则不执行插入操作也不需要调用 Redis
if location.Latitude != 0 || location.Longitude != 0 {
// 执行插入操作
_, err = g.Model("location").Data(data).Insert()
// 获取 Redis 客户端
redis := g.Redis("helmetRedis")
// 构造 Redis 的 key 和 value
key := "safety_helmet:" + location.DevNum
value := strconv.FormatFloat(location.Latitude, 'f', -1, 64) + "," + strconv.FormatFloat(location.Longitude, 'f', -1, 64)
// 插入数据之后,写入 Redis 的发布订阅
_, err = redis.Publish(ctx, key, value)
if err != nil {
glog.Info(ctx, "发布订阅出错")
return res, nil
}
// 插入数据之后,写入 Redis 的键值对
_, err = redis.Set(ctx, key, value)
if err != nil {
glog.Info(ctx, "设置到Redis出错")
return res, nil
}
if err != nil {
// 处理可能的错误
fmt.Println("Insert error:", err)
}
} else {
fmt.Println("Latitude and Longitude are both zero, insertion skipped.")
}
// 返回响应对象
return &DataRes{}, nil
}
// 发送数据到 WS 客户端
func sendToWs(location Location, err error) {
// 创建 WS 需要发送的数据对象
broadcastLocation := BroadcastLocation{
DevNum: location.DevNum,
Time: location.Time,
Latitude: location.Latitude,
Longitude: location.Longitude,
}
// 转换为 JSON 字符串
locationJSON, err := json.Marshal(broadcastLocation)
if err != nil {
log.Printf("location json marshal error: %v", err)
} else {
// 发送转换后的JSON字符串到广播通道
broadcast <- locationJSON
}
}

View File

@ -0,0 +1,78 @@
package saft_hat
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strconv"
"time"
"github.com/gogf/gf/v2/frame/g"
)
// 更新设备定位数据
type LocationReq struct {
g.Meta `path:"/device/location" method:"post" tags:"安全帽相关" summary:"刷新设备定位数据"`
Data string `json:"data" dc:"设备号"` // 设备数据
}
type LocationRes struct {
Code int `json:"code"` // 响应代码
Data bool `json:"data"` // 是否成功
Msg string `json:"msg"` // 响应消息
}
// 处理设备开关机时间的更新
func (h Hat) Location(ctx context.Context, req *LocationReq) (res *LocationRes, err error) {
res = new(LocationRes)
data := req.Data
timestamp := time.Now().Unix()
// 准备生成签名的参数
params := map[string]string{
"appid": appid,
"data": data,
"secret": secret,
"timestamp": strconv.FormatInt(timestamp, 10),
}
// 生成签名
sign := GenerateSignature(params)
// 构造请求体
payload := map[string]interface{}{
"appid": appid,
"data": data,
"secret": secret,
"timestamp": timestamp,
"sign": sign,
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return res, err
}
// 发送POST请求
resp, err := http.Post("https://www.loctp.com/api/crm/v1/refresh/location", "application/json", bytes.NewBuffer(payloadBytes))
if err != nil {
return res, err
}
defer resp.Body.Close()
// 检查响应
respBody, err := ioutil.ReadAll(resp.Body)
fmt.Println(string(respBody))
if err != nil {
return res, err
}
err = json.Unmarshal(respBody, res)
if err != nil {
return res, err
}
return res, nil
}

81
api/saft_hat/hat_rate.go Normal file
View File

@ -0,0 +1,81 @@
package saft_hat
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"io/ioutil"
"net/http"
"strconv"
"time"
)
// 定义设备上传速度频率请求【我们请求平台】
type UploadRateReq struct {
g.Meta `path:"/device/rate" method:"post" tags:"安全帽相关" summary:"更新设备数据发送频率"`
Data string `json:"data" dc:"设备号"` // 设备数据
RateTime int `json:"rateTime" dc:"上传频率单位是S"` // 上传频率
}
type UploadRateRes struct {
Code int `json:"code"` // 响应代码
Data string `json:"data"` // 设备数据
Msg string `json:"msg"` // 响应消息
}
func (h Hat) UpdateRate(ctx context.Context, req *UploadRateReq) (res *UploadRateRes, err error) {
res = new(UploadRateRes)
// 获取请求参数
rateTime := req.RateTime
data := req.Data
timestamp := time.Now().Unix()
// 准备生成签名的参数
params := map[string]string{
"appid": appid,
"data": data,
"rate_time": strconv.Itoa(rateTime),
"secret": secret,
"timestamp": strconv.FormatInt(timestamp, 10),
}
// 生成签名
sign := GenerateSignature(params)
// 构造请求体
payload := map[string]interface{}{
"appid": appid,
"data": data,
"rate_time": rateTime,
"timestamp": timestamp,
"secret": secret,
"sign": sign,
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return res, err
}
// 发送POST请求
resp, err := http.Post("https://www.loctp.com/api/crm/v1/uploadRate", "application/json", bytes.NewBuffer(payloadBytes))
if err != nil {
return res, err
}
defer resp.Body.Close()
// 检查响应
respBody, err := ioutil.ReadAll(resp.Body)
err = json.Unmarshal(respBody, &res)
if err != nil {
return res, err
}
// 把 respBody 转化为 UploadRateRes
err = json.Unmarshal(respBody, &res)
if err != nil {
// 处理错误,例如打印或返回错误
fmt.Println("Error unmarshalling res:", err)
}
return res, err
}

62
api/saft_hat/hat_sos.go Normal file
View File

@ -0,0 +1,62 @@
package saft_hat
import (
"context"
"errors"
"fmt"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
"io/ioutil"
"log"
)
// SOS报警数据结构体
type SOS struct {
Flag int `json:"flag"` // 两次数据上传标志0:第二次 1:第一次
UTCDateTime int64 `json:"utcDateTime"` // 设备内部上传时间戳
Latitude float64 `json:"latitude"` // WGS-84 纬度坐标
IMEI string `json:"IMEI"` // 设备IMEI编号
Type string `json:"type"` // 设备上传类型和时间
Longitude float64 `json:"longitude"` // WGS-84 经度坐标
}
// SOS报警数据上传请求【其它平台发给我们】
type SOSReq struct {
g.Meta `path:"/device/alarm" method:"post" tags:"安全帽相关" summary:"接收SOS报警数据(不需要前端调用)"`
}
type SOSRes struct {
}
func (h Hat) SOS(ctx context.Context, req *SOSReq) (res *SOSRes, err error) {
r := g.RequestFromCtx(ctx) // 从上下文中获取请求对象
body, err := ioutil.ReadAll(r.Body) // 读取请求体
if err != nil {
log.Printf("读取请求头失败: %v", err)
return nil, err
}
defer r.Body.Close() // 延迟关闭请求体
// 从请求头中获取时间戳和签名
timestamp := r.GetHeader("timestamp")
signature := r.GetHeader("signature")
// 验证签名的有效性
if !VerifySignature(string(body)+timestamp, signature, secret) {
glog.Errorf(ctx, "签名验证失败")
return nil, errors.New("验证签名失败")
}
// 解析请求体中的 JSON 数据到预定义的结构体
var sos SOS
if err := gjson.DecodeTo(body, &sos); err != nil {
return nil, err
}
// 输出 SOS 的报警信息
fmt.Println("报警信息:", sos)
// 返回响应对象
return &SOSRes{}, nil
}

133
api/saft_hat/hat_status.go Normal file
View File

@ -0,0 +1,133 @@
package saft_hat
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"io/ioutil"
"net/http"
"strconv"
"strings"
"time"
)
// 获取设备状态的请求结构
type DeviceStatusReq struct {
g.Meta `path:"/device/status" method:"post" tags:"安全帽相关" summary:"获取安全帽状态"`
Data []string `json:"data" dc:"设备号"`
}
// 获取设备状态
func (h Hat) DeviceStatus(ctx context.Context, req *DeviceStatusReq) (res *DeviceStatusRes, err error) {
return StatusCheck(ctx, req.Data)
}
type DeviceStatusRes struct {
Code int `json:"code"` // 响应代码
Data []DeviceStatusInfo `json:"data"` // 设备数据
Msg string `json:"msg"` // 响应消息
}
// 定义数组中每个对象的结构
type DeviceStatusInfo struct {
IMEI string `json:"imei"`
Status int `json:"status"`
}
// 检查设备的状态情况
func StatusCheck(ctx context.Context, devNums []string) (res *DeviceStatusRes, err error) {
res = new(DeviceStatusRes)
data := strings.Join(devNums, ",")
timestamp := time.Now().Unix()
// 准备生成签名的参数
params := map[string]string{
"appid": appid,
"data": data,
"secret": secret,
"timestamp": strconv.FormatInt(timestamp, 10),
}
// 生成签名
sign := GenerateSignature(params)
// 构造请求体
payload := map[string]interface{}{
"appid": appid,
"data": data,
"secret": secret,
"sign": sign,
"timestamp": timestamp,
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return res, err
}
// 发送POST请求
resp, err := http.Post("https://www.loctp.com/api/crm/v1/getStatus", "application/json", bytes.NewBuffer(payloadBytes))
if err != nil {
return res, err
}
defer resp.Body.Close()
// 检查响应
respBody, err := ioutil.ReadAll(resp.Body)
fmt.Println(string(respBody))
if err != nil {
return res, err
}
err = json.Unmarshal(respBody, res)
if err != nil {
return res, err
}
return res, nil
}
// 心跳检测
func HeartCheck() {
ticker := time.NewTicker(60 * time.Second)
defer ticker.Stop() // 确保ticker被适当释放
for {
select {
case <-ticker.C:
var devices []Device
err := g.Model("device").Fields("dev_num").Scan(&devices)
if err != nil {
fmt.Println("获取设备列表出错:", err)
continue // 如果获取失败,则跳过本次循环
}
if len(devices) == 0 {
fmt.Println("没有找到任何设备")
continue // 如果没有设备,则跳过本次循环
}
// 构建devNums切片
var devNums []string
for _, device := range devices {
devNums = append(devNums, device.DevNum)
}
res, err := StatusCheck(context.Background(), devNums)
if err != nil {
fmt.Println("检查设备状态出错:", err)
continue // 如果检查失败,则跳过本次循环
}
for _, deviceStatus := range res.Data {
// 更新 device 表中的 status 字段
_, err := g.Model("device").Data("status", deviceStatus.Status).Where("dev_num", deviceStatus.IMEI).Update()
if err != nil {
fmt.Printf("更新设备 %s 状态出错: %v\n", deviceStatus.IMEI, err)
continue // 如果更新失败,则跳过本次设备的更新
}
}
}
}
}

View File

@ -0,0 +1,78 @@
package saft_hat
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"io/ioutil"
"net/http"
"strconv"
"time"
)
// TextToAudioReq 定义发送自定义语音的请求结构体
type TextToAudioReq struct {
g.Meta `path:"/device/text" method:"post" tags:"安全帽相关" summary:"发送自定义语言数据"`
Data string `json:"data" dc:"设备号"`
Text string `json:"text" dc:"发送语音的文字内容"`
Time int `json:"time" dc:"播放次数 最少一次 最多三次"`
}
type TextToAudioRes struct {
Code int `json:"code"` // 响应代码
Data string `json:"data"` // 设备数据
Msg string `json:"msg"` // 响应消息
}
// 发送自定义语音
func (h Hat) SendTextToAudio(ctx context.Context, req *TextToAudioReq) (res *TextToAudioRes, err error) {
res = new(TextToAudioRes)
timestamp := time.Now().Unix()
// 准备生成签名的参数
params := map[string]string{
"appid": appid,
"data": req.Data,
"secret": secret,
"text": req.Text,
"time": strconv.Itoa(req.Time),
"timestamp": strconv.FormatInt(timestamp, 10),
}
// 生成签名
sign := GenerateSignature(params)
// 构造请求体
reqBody := map[string]interface{}{
"data": req.Data,
"appid": appid,
"sign": sign,
"secret": secret,
"time": req.Time,
"text": req.Text,
"timestamp": strconv.FormatInt(timestamp, 10),
}
payloadBytes, err := json.Marshal(reqBody)
if err != nil {
return res, err
}
// 发送POST请求
resp, err := http.Post("https://www.loctp.com/api/crm/v1/textToAudio", "application/json", bytes.NewBuffer(payloadBytes))
if err != nil {
return res, err
}
defer resp.Body.Close()
// 读取响应
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return res, err
}
fmt.Println("Response:", string(respBody))
return res, nil
}

114
api/saft_hat/hat_time.go Normal file
View File

@ -0,0 +1,114 @@
package saft_hat
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"io/ioutil"
"net/http"
"strconv"
"strings"
"time"
)
// 更新设备开关机时间
type UpdateTimeReq struct {
g.Meta `path:"/device/uploadTime" method:"post" tags:"安全帽相关" summary:"更新设备开关机时间"`
Data string `json:"data" dc:"设备号"` // 设备数据
OnTime string `json:"onTime" dc:"设备开机时间,例如 09:00"` // 设备开机时间
OffTime string `json:"OffTime" dc:"设备关机时间,例如 23:00"` // 设备关机时间
}
type UpdateTimeRes struct {
Code int `json:"code"` // 响应代码
Data string `json:"data"` // 设备数据
Msg string `json:"msg"` // 响应消息
}
// 处理设备开关机时间的更新
func (h Hat) UpdateTime(ctx context.Context, req *UpdateTimeReq) (res *UpdateTimeRes, err error) {
res = new(UpdateTimeRes)
data := req.Data
timestamp := time.Now().Unix()
// 准备生成签名的参数
params := map[string]string{
"appid": appid,
"data": data,
"off_time": req.OffTime,
"on_time": req.OnTime,
"secret": secret,
"timestamp": strconv.FormatInt(timestamp, 10),
}
// 生成签名
sign := GenerateSignature(params)
// 构造请求体
payload := map[string]interface{}{
"appid": appid,
"data": data,
"off_time": req.OffTime,
"on_time": req.OnTime,
"secret": secret,
"timestamp": timestamp,
"sign": sign,
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return res, err
}
// 发送POST请求
resp, err := http.Post("https://www.loctp.com/api/crm/v1/uploadTime", "application/json", bytes.NewBuffer(payloadBytes))
if err != nil {
return res, err
}
defer resp.Body.Close()
// 检查响应
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return res, err
}
// 解析包含多个设备号的字符串,设备号通过英文逗号分割
devNums := strings.Split(req.Data, ",")
// 对每个设备号执行插入或更新操作
for _, devNum := range devNums {
// 首先去除可能的空格
devNum = strings.TrimSpace(devNum)
type StutusType struct {
Status int
}
statusType := new(StutusType)
// 先查看该设备的状态
g.Model("device").Fields("status").Where("dev_num = ?", devNum).Scan(&statusType)
// 如果设备在线status=1才允许更新它的开关机时间信息
if statusType.Status == 1 {
// 插入或更新电池开关机时间
_, err := g.Model("device").Data(g.Map{
"dev_num": devNum,
"battery_on": req.OnTime,
"battery_off": req.OffTime,
}).Where("dev_num", devNum).Save()
// 如果发生错误,立即返回
if err != nil {
fmt.Println("Error updating/inserting for device", devNum, ":", err)
return nil, err
}
} else {
fmt.Println("Device", devNum, "is not online; skipping update.")
}
}
err = json.Unmarshal(respBody, res)
if err != nil {
return res, err
}
return res, nil
}

53
api/saft_hat/hat_tip.go Normal file
View File

@ -0,0 +1,53 @@
package saft_hat
import (
"context"
"errors"
"fmt"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
"io/ioutil"
"log"
)
// 脱帽提示数据上传结构体
type HatTip struct {
UTCDateTime int64 `json:"utcDateTime"` // 设备内部上传时间戳
IMEI string `json:"IMEI"` // 设备IMEI编号
}
type HatTipReq struct {
g.Meta `path:"/device/tip" method:"post" tags:"安全帽相关" summary:"脱帽提醒(不需要前端调用)"`
}
type HatTipRes struct {
}
func (h Hat) HatTip(ctx context.Context, req *HatTipReq) (res *HatTipRes, err error) {
res = new(HatTipRes)
r := g.RequestFromCtx(ctx)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Failed to read request body: %v", err)
return nil, err
}
defer r.Body.Close()
timestamp := r.GetHeader("timestamp")
signature := r.GetHeader("signature")
if !VerifySignature(string(body)+timestamp, signature, secret) {
glog.Errorf(ctx, "Signature verification failed")
return nil, errors.New("signature verification failed")
}
var hatTip HatTip
if err := gjson.DecodeTo(body, &hatTip); err != nil {
return nil, err
}
fmt.Println("脱帽提示数据:", hatTip)
return res, nil
}

81
api/saft_hat/hat_voice.go Normal file
View File

@ -0,0 +1,81 @@
package saft_hat
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"io/ioutil"
"net/http"
"strconv"
"time"
)
// 定义发送内置语音的请求结构
type SendVoiceReq struct {
g.Meta `path:"/device/voice" method:"post" tags:"安全帽相关" summary:"发送内置语音"`
Data string `json:"data" dc:"设备号"`
VoiceType int `json:"voice_type" dc:"内置语音种类具体参照温度1、2、3、4、5....."`
}
type SendVoiceRes struct {
Code int `json:"code"` // 响应代码
Data string `json:"data"` // 设备数据
Msg string `json:"msg"` // 响应消息
}
// 处理设备开关机时间的更新
func (h Hat) SendVoice(ctx context.Context, req *SendVoiceReq) (res *SendVoiceRes, err error) {
res = new(SendVoiceRes)
data := req.Data
timestamp := time.Now().Unix()
// 准备生成签名的参数
params := map[string]string{
"appid": appid,
"data": data,
"secret": secret,
"timestamp": strconv.FormatInt(timestamp, 10),
"voice_type": strconv.Itoa(req.VoiceType),
}
// 生成签名
sign := GenerateSignature(params)
// 构造请求体
payload := map[string]interface{}{
"appid": appid,
"data": data,
"secret": secret,
"sign": sign,
"timestamp": timestamp,
"voice_type": strconv.Itoa(req.VoiceType),
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return res, err
}
// 发送POST请求
resp, err := http.Post("https://www.loctp.com/api/crm/v1/sendVoice", "application/json", bytes.NewBuffer(payloadBytes))
if err != nil {
return res, err
}
defer resp.Body.Close()
// 检查响应
respBody, err := ioutil.ReadAll(resp.Body)
fmt.Println(string(respBody))
if err != nil {
return res, err
}
err = json.Unmarshal(respBody, res)
if err != nil {
return res, err
}
return res, nil
}

14
api/saft_hat/router.go Normal file
View File

@ -0,0 +1,14 @@
package saft_hat
import (
"github.com/gogf/gf/v2/net/ghttp"
)
func InitHatAPI(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareCORS)
group.Group("/manage", func(group *ghttp.RouterGroup) {
group.Group("/api/v1", func(group *ghttp.RouterGroup) {
group.Bind(new(Hat))
})
})
}

35
api/saft_hat/util.go Normal file
View File

@ -0,0 +1,35 @@
package saft_hat
import (
"crypto/md5"
"encoding/hex"
"sort"
"strings"
)
// 使用参数名的ASCII升序排序后将参数的值进行拼接并加密生成签名
func GenerateSignature(params map[string]string) string {
// 获取所有的键并按照ASCII码排序
var keys []string
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
// 按排序后的键,拼接它们的值
var values []string
for _, k := range keys {
values = append(values, params[k])
}
signStr := strings.Join(values, "&")
// 将拼接后的字符串转换为小写
signStr = strings.ToLower(signStr)
// 使用MD5加密
hash := md5.New()
hash.Write([]byte(signStr))
md5String := hex.EncodeToString(hash.Sum(nil))
return md5String
}

21
api/saft_hat/verify.go Normal file
View File

@ -0,0 +1,21 @@
package saft_hat
import (
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"log"
)
// 函数用于验证请求的签名
func VerifySignature(message, messageSignature, secret string) bool {
mac := hmac.New(sha1.New, []byte(secret)) // 初始化HMAC-SHA1
mac.Write([]byte(message)) // 写入消息体以计算摘要
expectedMAC := mac.Sum(nil) // 计算消息的摘要
signature, err := hex.DecodeString(messageSignature) // 将签名从十六进制字符串解码
if err != nil {
log.Printf("解码失败: %v", err)
return false
}
return hmac.Equal(signature, expectedMAC) // 比较计算得到的摘要和传入的签名
}

View File

@ -0,0 +1,91 @@
// ==========================================================================
// GFast自动生成api操作代码。
// 生成日期2023-09-01 17:36:18
// 生成路径: api/v1/test/test_contact_info.go
// 生成人yqq
// desc:业主方联系人关联相关参数
// company:云南奇讯科技有限公司
// ==========================================================================
package test
import (
"github.com/gogf/gf/v2/frame/g"
commonApi "github.com/tiger1103/gfast/v3/api/v1/common"
"github.com/tiger1103/gfast/v3/internal/app/test/model"
)
// TestContactInfoSearchReq 分页请求参数
type TestContactInfoSearchReq struct {
g.Meta `path:"/list" tags:"业主方联系人关联" method:"get" summary:"业主方联系人关联列表"`
Id string `p:"id"` //
OwenerId string `p:"owenerId" v:"owenerId@integer#需为整数"` //
ContactName string `p:"contactName"` //
ContactPost string `p:"contactPost"` //
ContactPhone string `p:"contactPhone"` //
commonApi.PageReq
commonApi.Author
}
// TestContactInfoSearchRes 列表返回结果
type TestContactInfoSearchRes struct {
g.Meta `mime:"application/json"`
commonApi.ListRes
List []*model.TestContactInfoListRes `json:"list"`
}
// TestContactInfoAddReq 添加操作请求参数
type TestContactInfoAddReq struct {
g.Meta `path:"/add" tags:"业主方联系人关联" method:"post" summary:"业主方联系人关联添加"`
commonApi.Author
OwenerId uint `p:"owenerId" `
ContactName string `p:"contactName" v:"required#不能为空"`
ContactPost string `p:"contactPost" `
ContactPhone string `p:"contactPhone" `
}
// TestContactInfoAddRes 添加操作返回结果
type TestContactInfoAddRes struct {
commonApi.EmptyRes
}
// TestContactInfoEditReq 修改操作请求参数
type TestContactInfoEditReq struct {
g.Meta `path:"/edit" tags:"业主方联系人关联" method:"put" summary:"业主方联系人关联修改"`
commonApi.Author
Id uint `p:"id" v:"required#主键ID不能为空"`
OwenerId uint `p:"owenerId" `
ContactName string `p:"contactName" v:"required#不能为空"`
ContactPost string `p:"contactPost" `
ContactPhone string `p:"contactPhone" `
}
// TestContactInfoEditRes 修改操作返回结果
type TestContactInfoEditRes struct {
commonApi.EmptyRes
}
// TestContactInfoGetReq 获取一条数据请求
type TestContactInfoGetReq struct {
g.Meta `path:"/get" tags:"业主方联系人关联" method:"get" summary:"获取业主方联系人关联信息"`
commonApi.Author
Id uint `p:"id" v:"required#主键必须"` //通过主键获取
}
// TestContactInfoGetRes 获取一条数据结果
type TestContactInfoGetRes struct {
g.Meta `mime:"application/json"`
*model.TestContactInfoInfoRes
}
// TestContactInfoDeleteReq 删除数据请求
type TestContactInfoDeleteReq struct {
g.Meta `path:"/delete" tags:"业主方联系人关联" method:"delete" summary:"删除业主方联系人关联"`
commonApi.Author
Ids []uint `p:"ids" v:"required#主键必须"` //通过主键删除
}
// TestContactInfoDeleteRes 删除数据返回
type TestContactInfoDeleteRes struct {
commonApi.EmptyRes
}

View File

@ -0,0 +1,104 @@
// ==========================================================================
// GFast自动生成api操作代码。
// 生成日期2023-09-01 16:38:07
// 生成路径: api/v1/test/test_follow_info.go
// 生成人yqq
// desc:跟进信息相关参数
// company:云南奇讯科技有限公司
// ==========================================================================
package test
import (
"github.com/gogf/gf/v2/frame/g"
commonApi "github.com/tiger1103/gfast/v3/api/v1/common"
"github.com/tiger1103/gfast/v3/internal/app/test/model"
)
// TestFollowInfoSearchReq 分页请求参数
type TestFollowInfoSearchReq struct {
g.Meta `path:"/list" tags:"跟进信息" method:"get" summary:"跟进信息列表"`
Id string `p:"id"` //
ProjectId string `p:"projectId" v:"projectId@integer#关联的项目需为整数"` //关联的项目
FollowName string `p:"followName"` //跟进人姓名
OwnerId string `p:"ownerId" v:"ownerId@integer#业主名需为整数"` //业主名
ContactName string `p:"contactName"` //对接人姓名
ConPostName string `p:"conPostName"` //对接人职称
ContactPhone string `p:"contactPhone"` //对接人电话
FollowInfo string `p:"followInfo"` //跟进情况
FollowFile string `p:"followFile"` //相关附件
CreatedAt string `p:"createdAt" v:"createdAt@datetime#创建日期需为YYYY-MM-DD hh:mm:ss格式"` //创建日期
commonApi.PageReq
commonApi.Author
}
// TestFollowInfoSearchRes 列表返回结果
type TestFollowInfoSearchRes struct {
g.Meta `mime:"application/json"`
commonApi.ListRes
List []*model.TestFollowInfoListRes `json:"list"`
}
// TestFollowInfoAddReq 添加操作请求参数
type TestFollowInfoAddReq struct {
g.Meta `path:"/add" tags:"跟进信息" method:"post" summary:"跟进信息添加"`
commonApi.Author
ProjectId int `p:"projectId" v:"required#关联的项目不能为空"`
FollowName string `p:"followName" v:"required#跟进人姓名不能为空"`
OwnerId uint `p:"ownerId" `
ContactName string `p:"contactName" v:"required#对接人姓名不能为空"`
ConPostName string `p:"conPostName" v:"required#对接人职称不能为空"`
ContactPhone string `p:"contactPhone" `
FollowInfo string `p:"followInfo" `
FollowFile string `p:"followFile" `
}
// TestFollowInfoAddRes 添加操作返回结果
type TestFollowInfoAddRes struct {
commonApi.EmptyRes
}
// TestFollowInfoEditReq 修改操作请求参数
type TestFollowInfoEditReq struct {
g.Meta `path:"/edit" tags:"跟进信息" method:"put" summary:"跟进信息修改"`
commonApi.Author
Id uint `p:"id" v:"required#主键ID不能为空"`
ProjectId int `p:"projectId" v:"required#关联的项目不能为空"`
FollowName string `p:"followName" v:"required#跟进人姓名不能为空"`
OwnerId uint `p:"ownerId" `
ContactName string `p:"contactName" v:"required#对接人姓名不能为空"`
ConPostName string `p:"conPostName" v:"required#对接人职称不能为空"`
ContactPhone string `p:"contactPhone" `
FollowInfo string `p:"followInfo" `
FollowFile string `p:"followFile" `
}
// TestFollowInfoEditRes 修改操作返回结果
type TestFollowInfoEditRes struct {
commonApi.EmptyRes
}
// TestFollowInfoGetReq 获取一条数据请求
type TestFollowInfoGetReq struct {
g.Meta `path:"/get" tags:"跟进信息" method:"get" summary:"获取跟进信息信息"`
commonApi.Author
Id uint `p:"id" v:"required#主键必须"` //通过主键获取
}
// TestFollowInfoGetRes 获取一条数据结果
type TestFollowInfoGetRes struct {
g.Meta `mime:"application/json"`
*model.TestFollowInfoInfoRes
}
// TestFollowInfoDeleteReq 删除数据请求
type TestFollowInfoDeleteReq struct {
g.Meta `path:"/delete" tags:"跟进信息" method:"delete" summary:"删除跟进信息"`
commonApi.Author
Ids []uint `p:"ids" v:"required#主键必须"` //通过主键删除
}
// TestFollowInfoDeleteRes 删除数据返回
type TestFollowInfoDeleteRes struct {
commonApi.EmptyRes
}

View File

@ -0,0 +1,98 @@
// ==========================================================================
// GFast自动生成api操作代码。
// 生成日期2023-09-01 17:51:41
// 生成路径: api/v1/test/test_owner_info.go
// 生成人yqq
// desc:业主方基本情况相关参数
// company:云南奇讯科技有限公司
// ==========================================================================
package test
import (
"github.com/gogf/gf/v2/frame/g"
commonApi "github.com/tiger1103/gfast/v3/api/v1/common"
"github.com/tiger1103/gfast/v3/internal/app/test/model"
)
// TestOwnerInfoSearchReq 分页请求参数
type TestOwnerInfoSearchReq struct {
g.Meta `path:"/list" tags:"业主方基本情况" method:"get" summary:"业主方基本情况列表"`
Id string `p:"id"` //
CompanyName string `p:"companyName"` //企业名称
CompanyAddress string `p:"companyAddress"` //单位地址
RegistrationType string `p:"registrationType"` //企业登记注册类型
RegisteredCapital string `p:"registeredCapital" v:"registeredCapital@integer#注册资金需为整数"` //注册资金
Legaler string `p:"legaler"` //法人代表
LegalerPhone string `p:"legalerPhone"` //法人电话
CreatedAt string `p:"createdAt" v:"createdAt@datetime#创建日期需为YYYY-MM-DD hh:mm:ss格式"` //创建日期
commonApi.PageReq
commonApi.Author
}
// TestOwnerInfoSearchRes 列表返回结果
type TestOwnerInfoSearchRes struct {
g.Meta `mime:"application/json"`
commonApi.ListRes
List []*model.TestOwnerInfoListRes `json:"list"`
}
// TestOwnerInfoAddReq 添加操作请求参数
type TestOwnerInfoAddReq struct {
g.Meta `path:"/add" tags:"业主方基本情况" method:"post" summary:"业主方基本情况添加"`
commonApi.Author
CompanyName string `p:"companyName" v:"required#企业名称不能为空"`
CompanyAddress string `p:"companyAddress" `
RegistrationType string `p:"registrationType" `
RegisteredCapital int `p:"registeredCapital" `
Legaler string `p:"legaler" `
LegalerPhone string `p:"legalerPhone" `
}
// TestOwnerInfoAddRes 添加操作返回结果
type TestOwnerInfoAddRes struct {
commonApi.EmptyRes
}
// TestOwnerInfoEditReq 修改操作请求参数
type TestOwnerInfoEditReq struct {
g.Meta `path:"/edit" tags:"业主方基本情况" method:"put" summary:"业主方基本情况修改"`
commonApi.Author
Id uint `p:"id" v:"required#主键ID不能为空"`
CompanyName string `p:"companyName" v:"required#企业名称不能为空"`
CompanyAddress string `p:"companyAddress" `
RegistrationType string `p:"registrationType" `
RegisteredCapital int `p:"registeredCapital" `
Legaler string `p:"legaler" `
LegalerPhone string `p:"legalerPhone" `
}
// TestOwnerInfoEditRes 修改操作返回结果
type TestOwnerInfoEditRes struct {
commonApi.EmptyRes
}
// TestOwnerInfoGetReq 获取一条数据请求
type TestOwnerInfoGetReq struct {
g.Meta `path:"/get" tags:"业主方基本情况" method:"get" summary:"获取业主方基本情况信息"`
commonApi.Author
Id uint `p:"id" v:"required#主键必须"` //通过主键获取
}
// TestOwnerInfoGetRes 获取一条数据结果
type TestOwnerInfoGetRes struct {
g.Meta `mime:"application/json"`
*model.TestOwnerInfoInfoRes
}
// TestOwnerInfoDeleteReq 删除数据请求
type TestOwnerInfoDeleteReq struct {
g.Meta `path:"/delete" tags:"业主方基本情况" method:"delete" summary:"删除业主方基本情况"`
commonApi.Author
Ids []uint `p:"ids" v:"required#主键必须"` //通过主键删除
}
// TestOwnerInfoDeleteRes 删除数据返回
type TestOwnerInfoDeleteRes struct {
commonApi.EmptyRes
}

View File

@ -0,0 +1,104 @@
// ==========================================================================
// GFast自动生成api操作代码。
// 生成日期2023-09-01 16:15:09
// 生成路径: api/v1/test/test_project_info.go
// 生成人yqq
// desc:项目备案信息相关参数
// company:云南奇讯科技有限公司
// ==========================================================================
package test
import (
"github.com/gogf/gf/v2/frame/g"
commonApi "github.com/tiger1103/gfast/v3/api/v1/common"
"github.com/tiger1103/gfast/v3/internal/app/test/model"
)
// TestProjectInfoSearchReq 分页请求参数
type TestProjectInfoSearchReq struct {
g.Meta `path:"/list" tags:"项目备案信息" method:"get" summary:"项目备案信息列表"`
Id string `p:"id"` //
ProjectName string `p:"projectName"` //项目名称
ProjectAddress string `p:"projectAddress"` //单位地址
ProjectLeader string `p:"projectLeader"` //项目负责人
ResourceName string `p:"resourceName"` //资源方
OwnerId string `p:"ownerId" v:"ownerId@integer#业主名id需为整数"` //业主名id
ProjectType string `p:"projectType"` //项目类型
ProjectInfo string `p:"projectInfo"` //项目概况
ProjectState string `p:"projectState" v:"projectState@integer#项目状态(0未开始 1进行中 2已完成需为整数"` //项目状态(0未开始 1进行中 2已完成
CreatedAt string `p:"createdAt" v:"createdAt@datetime#创建日期需为YYYY-MM-DD hh:mm:ss格式"` //创建日期
commonApi.PageReq
commonApi.Author
}
// TestProjectInfoSearchRes 列表返回结果
type TestProjectInfoSearchRes struct {
g.Meta `mime:"application/json"`
commonApi.ListRes
List []*model.TestProjectInfoListRes `json:"list"`
}
// TestProjectInfoAddReq 添加操作请求参数
type TestProjectInfoAddReq struct {
g.Meta `path:"/add" tags:"项目备案信息" method:"post" summary:"项目备案信息添加"`
commonApi.Author
ProjectName string `p:"projectName" v:"required#项目名称不能为空"`
ProjectAddress string `p:"projectAddress" `
ProjectLeader string `p:"projectLeader" `
ResourceName string `p:"resourceName" v:"required#资源方不能为空"`
OwnerId int `p:"ownerId" v:"required#业主名id不能为空"`
ProjectType string `p:"projectType" `
ProjectInfo string `p:"projectInfo" `
ProjectState int `p:"projectState" `
}
// TestProjectInfoAddRes 添加操作返回结果
type TestProjectInfoAddRes struct {
commonApi.EmptyRes
}
// TestProjectInfoEditReq 修改操作请求参数
type TestProjectInfoEditReq struct {
g.Meta `path:"/edit" tags:"项目备案信息" method:"put" summary:"项目备案信息修改"`
commonApi.Author
Id uint `p:"id" v:"required#主键ID不能为空"`
ProjectName string `p:"projectName" v:"required#项目名称不能为空"`
ProjectAddress string `p:"projectAddress" `
ProjectLeader string `p:"projectLeader" `
ResourceName string `p:"resourceName" v:"required#资源方不能为空"`
OwnerId int `p:"ownerId" v:"required#业主名id不能为空"`
ProjectType string `p:"projectType" `
ProjectInfo string `p:"projectInfo" `
ProjectState int `p:"projectState" `
}
// TestProjectInfoEditRes 修改操作返回结果
type TestProjectInfoEditRes struct {
commonApi.EmptyRes
}
// TestProjectInfoGetReq 获取一条数据请求
type TestProjectInfoGetReq struct {
g.Meta `path:"/get" tags:"项目备案信息" method:"get" summary:"获取项目备案信息信息"`
commonApi.Author
Id uint `p:"id" v:"required#主键必须"` //通过主键获取
}
// TestProjectInfoGetRes 获取一条数据结果
type TestProjectInfoGetRes struct {
g.Meta `mime:"application/json"`
*model.TestProjectInfoInfoRes
}
// TestProjectInfoDeleteReq 删除数据请求
type TestProjectInfoDeleteReq struct {
g.Meta `path:"/delete" tags:"项目备案信息" method:"delete" summary:"删除项目备案信息"`
commonApi.Author
Ids []uint `p:"ids" v:"required#主键必须"` //通过主键删除
}
// TestProjectInfoDeleteRes 删除数据返回
type TestProjectInfoDeleteRes struct {
commonApi.EmptyRes
}

19
api/v1/common/captcha.go Normal file
View File

@ -0,0 +1,19 @@
/*
* @desc:验证码参数
* @company:云南奇讯科技有限公司
* @Author: yixiaohu
* @Date: 2022/3/2 17:47
*/
package common
import "github.com/gogf/gf/v2/frame/g"
type CaptchaReq struct {
g.Meta `path:"/get" tags:"验证码" method:"get" summary:"获取验证码"`
}
type CaptchaRes struct {
g.Meta `mime:"application/json"`
Key string `json:"key"`
Img string `json:"img"`
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,278 @@
package fileUpload
import (
"bytes"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/tiger1103/gfast/v3/api/v1/common/globe"
"io"
"log"
"os"
"path"
"strconv"
"strings"
)
func InitUploadApi(group *ghttp.RouterGroup) {
group.POST("/source/upload", SourceUploadFunc)
//group.Bind(new(SourceUpload))
}
type SourceUpload struct {
}
type SourceUploadReq struct {
g.Meta `path:"source/upload" dc:"上传资源" method:"post" tags:"资源相关" `
}
type SourceUploadRes struct {
}
/*func (SourceUpload) UploadFile(ctx context.Context, req *SourceUploadReq) (res *SourceUploadRes, err error) {
err = startSaveFile(g.RequestFromCtx(ctx))
return
}*/
func SourceUploadFunc(request *ghttp.Request) {
projectId := request.Get("projectId")
fmt.Println("projectId", projectId)
startSaveFile(request)
}
func startSaveFile(request *ghttp.Request) error {
err, filename := Upload(request, globe.SOURCE)
fmt.Println("结束了")
fmt.Println(err)
if err != nil {
return err
}
fmt.Println(filename)
/* arr := strings.Split(filename, ".")
arr = arr[:len(arr)-1]
suffix := path.Ext(filename)
var SourceType = ""
switch suffix {
case globe.CLT:
SourceType = globe.TILESET
break
case globe.JCT:
SourceType = globe.TILESET
break
case globe.MBTILES:
SourceType = globe.LAYER
break
case globe.PAK:
//此时需要判断是地形还是正射
SourceType = globe.LAYER
break
}*/
//source := database.SOURCE{
// SourceID: tool.GetUuid(),
// SourceName: strings.Join(arr, "."),
// SourceType: SourceType,
// SourcePath: filename,
//}
//database.GetORMDBInstance().Model(&database.SOURCE{}).Create(&source)
return err
}
func Upload(r *ghttp.Request, dir string) (error, string) {
var contentLength int64
contentLength = r.Request.ContentLength
if contentLength <= 0 {
return globe.GetErrors("content_length error"), ""
}
content_type_, has_key := r.Request.Header["Content-Type"]
if !has_key {
return globe.GetErrors("Content-Type error"), ""
}
if len(content_type_) != 1 {
return globe.GetErrors("Content-Type count error"), ""
}
contentType := content_type_[0]
const BOUNDARY string = "; boundary="
loc := strings.Index(contentType, BOUNDARY)
if -1 == loc {
return globe.GetErrors("Content-Type error, no boundary"), ""
}
boundary := []byte(contentType[(loc + len(BOUNDARY)):])
readData := make([]byte, 1024*12)
var readTotal = 0
var des = ""
var filename = ""
for {
fileHeader, fileData, err := ParseFromHead(readData, readTotal, append(boundary, []byte("\r\n")...), r.Request.Body)
if err != nil {
return err, ""
}
filename = fileHeader.FileName
des = path.Join(dir, filename)
f, err := os.Create(des)
if err != nil {
return err, ""
}
f.Write(fileData)
fileData = nil
//需要反复搜索boundary
tempData, reachEnd, err := ReadToBoundary(boundary, r.Request.Body, f)
f.Close()
if err != nil {
return err, ""
}
if reachEnd {
break
} else {
copy(readData[0:], tempData)
readTotal = len(tempData)
continue
}
}
return nil, filename
}
// / 解析多个文件上传中,每个具体的文件的信息
type FileHeader struct {
ContentDisposition string
Name string
FileName string ///< 文件名
ContentType string
ContentLength int64
}
// / 解析描述文件信息的头部
// / @return FileHeader 文件名等信息的结构体
// / @return bool 解析成功还是失败
func ParseFileHeader(h []byte) (FileHeader, bool) {
arr := bytes.Split(h, []byte("\r\n"))
var out_header FileHeader
out_header.ContentLength = -1
const (
CONTENT_DISPOSITION = "Content-Disposition: "
NAME = "name=\""
FILENAME = "filename=\""
CONTENT_TYPE = "Content-Type: "
CONTENT_LENGTH = "Content-Length: "
)
for _, item := range arr {
if bytes.HasPrefix(item, []byte(CONTENT_DISPOSITION)) {
l := len(CONTENT_DISPOSITION)
arr1 := bytes.Split(item[l:], []byte("; "))
out_header.ContentDisposition = string(arr1[0])
if bytes.HasPrefix(arr1[1], []byte(NAME)) {
out_header.Name = string(arr1[1][len(NAME) : len(arr1[1])-1])
}
fmt.Println(arr1)
l = len(arr1[2])
if bytes.HasPrefix(arr1[2], []byte(FILENAME)) && arr1[2][l-1] == 0x22 {
out_header.FileName = string(arr1[2][len(FILENAME) : l-1])
}
} else if bytes.HasPrefix(item, []byte(CONTENT_TYPE)) {
l := len(CONTENT_TYPE)
out_header.ContentType = string(item[l:])
} else if bytes.HasPrefix(item, []byte(CONTENT_LENGTH)) {
l := len(CONTENT_LENGTH)
s := string(item[l:])
content_length, err := strconv.ParseInt(s, 10, 64)
if err != nil {
log.Printf("content length error:%s", string(item))
return out_header, false
} else {
out_header.ContentLength = content_length
}
} else {
log.Printf("unknown:%s\n", string(item))
}
}
if len(out_header.FileName) == 0 {
return out_header, false
}
return out_header, true
}
// / 从流中一直读到文件的末位
// / @return []byte 没有写到文件且又属于下一个文件的数据
// / @return bool 是否已经读到流的末位了
// / @return error 是否发生错误
func ReadToBoundary(boundary []byte, stream io.ReadCloser, target io.WriteCloser) ([]byte, bool, error) {
read_data := make([]byte, 1024*8)
read_data_len := 0
buf := make([]byte, 1024*4)
b_len := len(boundary)
reach_end := false
for !reach_end {
read_len, err := stream.Read(buf)
if err != nil {
if err != io.EOF && read_len <= 0 {
return nil, true, err
}
reach_end = true
}
//todo: 下面这一句很蠢,值得优化
copy(read_data[read_data_len:], buf[:read_len]) //追加到另一块buffer仅仅只是为了搜索方便
read_data_len += read_len
if read_data_len < b_len+4 {
continue
}
loc := bytes.Index(read_data[:read_data_len], boundary)
if loc >= 0 {
//找到了结束位置
target.Write(read_data[:loc-4])
return read_data[loc:read_data_len], reach_end, nil
}
target.Write(read_data[:read_data_len-b_len-4])
copy(read_data[0:], read_data[read_data_len-b_len-4:])
read_data_len = b_len + 4
}
target.Write(read_data[:read_data_len])
return nil, reach_end, nil
}
// / 解析表单的头部
// / @param read_data 已经从流中读到的数据
// / @param read_total 已经从流中读到的数据长度
// / @param boundary 表单的分割字符串
// / @param stream 输入流
// / @return FileHeader 文件名等信息头
// / []byte 已经从流中读到的部分
// / error 是否发生错误
func ParseFromHead(read_data []byte, readTotal int, boundary []byte, stream io.ReadCloser) (FileHeader, []byte, error) {
buf := make([]byte, 1024*4)
foundBoundary := false
boundaryLoc := -1
var file_header FileHeader
for {
read_len, err := stream.Read(buf)
fmt.Println("read_len", read_len)
if err != nil {
if err != io.EOF {
return file_header, nil, err
}
break
}
if readTotal+read_len > cap(read_data) {
return file_header, nil, fmt.Errorf("not found boundary")
}
copy(read_data[readTotal:], buf[:read_len])
readTotal += read_len
if !foundBoundary {
boundaryLoc = bytes.Index(read_data[:readTotal], boundary)
if -1 == boundaryLoc {
continue
}
foundBoundary = true
}
start_loc := boundaryLoc + len(boundary)
file_head_loc := bytes.Index(read_data[start_loc:readTotal], []byte("\r\n\r\n"))
if -1 == file_head_loc {
continue
}
file_head_loc += start_loc
ret := false
file_header, ret = ParseFileHeader(read_data[start_loc:file_head_loc])
if !ret {
return file_header, nil, fmt.Errorf("ParseFileHeader fail:%s", string(read_data[start_loc:file_head_loc]))
}
return file_header, read_data[file_head_loc+4 : readTotal], nil
}
return file_header, nil, fmt.Errorf("reach to stream EOF")
}

View File

@ -0,0 +1,96 @@
package globe
import (
"errors"
"github.com/gogf/gf/v2/net/ghttp"
"gorm.io/gorm"
"net/http"
"strconv"
)
const (
ALL = -1 //所有
ENABLE = 1
DISABLE = 0
DESC = "desc"
ASC = "asc"
PAGE = 1
PAGESIZE = 10
ONLINE = 1
OFFLINE = 0
PREFFIX = "yjearth4.0"
)
var IS_OFFLINE_VERSION = true //是否为单机版本
const SOURCE = "resource/public/clt/"
const (
TILESET = "tileset"
BIM = "bim"
LAYER = "layer"
TERRAIN = "terrain"
POINT = "point"
LINE = "line"
AREA = "area"
MODEL = "model"
KML = "kml"
GEOJSON = "geojson"
DIRECTORY = "directory"
SHP = "shp"
)
const (
PAK = ".pak"
MBTILES = ".mbtiles"
CLT = ".clt"
JCT = ".jct"
DOTGEOJSON = ".geojson"
DOTSHP = ".shp"
)
var (
PORT = "80"
HOST = ""
PROTOCOL = ""
KEY = ""
CRT = ""
)
const (
HTTP = "http"
HTTPS = "https"
)
func GetErrors(msg string) error {
return errors.New(msg)
}
func GetAddr() string {
//单机版本时 无代理,需要补全地址
//if IS_OFFLINE_VERSION {
// return PROTOCOL + "://" + HOST + ":" + PORT + "/" + PREFFIX
//}
//网络版时 有代理 不需要补全地址
return PREFFIX
}
/*clt数据包*/
type Tile struct {
MD5 string `json:"md5"`
PATH string `json:"path"`
Tile []byte `json:"tile"`
Type string `json:"type"`
}
func RenderData(request *ghttp.Request, data []byte) {
request.Response.Header().Set("Cache-Control", "private,max-age="+strconv.Itoa(60*60))
request.Response.WriteHeader(http.StatusOK)
request.Response.Writer.Write(data)
}
func CloseDB(db *gorm.DB) {
s, err := db.DB()
if err != nil {
return
}
s.Close()
}

25
api/v1/common/req.go Normal file
View File

@ -0,0 +1,25 @@
/*
* @desc:公共接口相关
* @company:云南奇讯科技有限公司
* @Author: yixiaohu<yxh669@qq.com>
* @Date: 2022/3/30 9:28
*/
package common
// PageReq 公共请求参数
type PageReq struct {
DateRange []string `p:"dateRange"` //日期范围
PageNum int `p:"pageNum"` //当前页码
PageSize int `p:"pageSize"` //每页数
OrderBy string //排序方式
NotInPlan bool `p:"notInPlan"` //是否过滤周计划中的id
}
type Author struct {
Authorization string `p:"Authorization" in:"header" dc:"Bearer {{token}}"`
}
type Paging struct {
IsPaging string `json:"isPaging" dc:"是否开启分页功能 YES开启 NO不开启空字符串也不开启分页默认"` //是否开启分页功能 YES开启 NO不开启空字符串也不开启分页默认
}

21
api/v1/common/res.go Normal file
View File

@ -0,0 +1,21 @@
/*
* @desc:返回响应公共参数
* @company:云南奇讯科技有限公司
* @Author: yixiaohu<yxh669@qq.com>
* @Date: 2022/10/27 16:30
*/
package common
import "github.com/gogf/gf/v2/frame/g"
// EmptyRes 不响应任何数据
type EmptyRes struct {
g.Meta `mime:"application/json"`
}
// ListRes 列表公共返回
type ListRes struct {
CurrentPage int `json:"currentPage"`
Total interface{} `json:"total"`
}

254
api/v1/common/shp/shp.go Normal file
View File

@ -0,0 +1,254 @@
package shp
import (
"fmt"
"github.com/tomchavakis/turf-go"
"github.com/tiger1103/gfast/v3/api/v1/common/globe"
"github.com/tiger1103/gfast/v3/api/v1/common/tool"
"github.com/tiger1103/gfast/v3/api/v1/common/tool/shp"
"github.com/tomchavakis/geojson/geometry"
)
const (
DefaultColor = "#12f6f6"
DefaultWidth = "2"
)
type Point struct {
Lng float64 `json:"lng"`
Lat float64 `json:"lat"`
Alt float64 `json:"alt"` // 裝點更新 只更新這一個,更新立柱的高程時 這個字段不動
Width float64 `json:"width"`
Property
}
type Polyline struct {
Positions []Point `json:"positions"`
Width string `json:"width"`
Color string `json:"color"`
Alpha string `json:"alpha"`
Degree string `json:"degree"`
// Name string `json:"name"` // text
// Property string `json:"property"`
Range Box `json:"range"`
Property
}
type Polygon struct {
Positions []Point `json:"positions"`
Color string `json:"color"`
Range Box `json:"range"`
}
type Box struct {
MinX float64 `json:"min_x"`
MinY float64 `json:"min_y"`
MaxX float64 `json:"max_x"`
MaxY float64 `json:"max_y"`
}
type ShpObj struct {
Points []Point `json:"points"`
Polylines []Polyline `json:"polylines"`
Polygons []Polygon `json:"polygons"`
}
type Detail struct {
// Rotation []interfac e{} `json:"rotation"`
Position Point `json:"position"`
}
type Degree struct {
Position PointDegree `json:"position"`
}
type PointDegree struct {
Lng float64 `json:"lng"`
Lat float64 `json:"lat"`
Alt float64 `json:"alt"` // 裝點更新 只更新這一個,更新立柱的高程時 這個字段不動
Degree string `json:"degree"`
}
type Property struct {
Name string `json:"name"`
Beizhu string `json:"beizhu"`
Tishi string `json:"tishi"`
Height float64 `json:"height"` // 更新立柱的時 更新這個字段
Difference float64 `json:"difference"` // height - alt
SourceId string `json:"sourceId"`
}
/*读取shp数据*/
func ReadShp(file string) (error, *ShpObj) {
//if !globe.IS_OFFLINE_VERSION {
// file = globe.SOURCE + file
//}
if !tool.PathExists(file) {
return globe.GetErrors("资源不存在," + file), nil
}
shape, err := shp.Open(file)
if err != nil {
return err, nil
}
defer shape.Close()
obj := ShpObj{
Polygons: []Polygon{},
Polylines: []Polyline{},
Points: []Point{},
}
fields := shape.Fields()
for shape.Next() {
n, p := shape.Shape()
name := ""
beizhu := ""
tishi := ""
var O_LClr, O_LWidth, O_LAlpha /*, O_LType, O_SType, O_TType, O_Name, O_Comment*/ string
O_LClr = DefaultColor
O_LWidth = DefaultWidth
// Text := ""
for k, f := range fields {
val := shape.ReadAttribute(n, k)
bb := f.String()
// // 记录本次判断开始前的名字
// temp := name
switch bb {
// case "名称": // 方阵的名称
// if len(name) == 0 {
// name = val
// }
// case "TxtMemo": // 方阵的名称
// if len(name) == 0 {
// name = val
// }
case "name": // 方阵的名称
if len(name) == 0 {
name = val
}
// case "O_Name": // 方阵的名称
// if len(name) == 0 {
// name = val
// }
case "Text": // 方阵的名称
if len(name) == 0 {
name = val
}
case "备注": // 方阵的名称
beizhu = val
case "提示": // 方阵的名称
tishi = val
}
// 如果本次循环后名字被清空,则替换为原本的名字
// if name == "" {
// name = temp
// }
// fmt.Printf("\t%v: %v\n", f, val)
}
// fmt.Println(O_Name, O_Comment, O_LClr, O_LWidth, O_LAlpha, O_LType, O_SType, O_TType, Shape_Leng, Text, TxtMemo)
// fmt.Println("Text", Text)
// fmt.Println("O_Name", O_Name)
// fmt.Println("O_Comment", O_Comment)
// fmt.Println("O_LType", O_LType)
// fmt.Println("O_SType", O_SType)
// fmt.Println("O_TType", O_TType)
if p2, ok := p.(*shp.PolyLine); ok {
polyline := Polyline{}
polyline.Alpha = O_LAlpha
polyline.Color = O_LClr
polyline.Width = O_LWidth
polyline.Name = name
polyline.Range.MinX = p.BBox().MinX
polyline.Range.MinY = p.BBox().MinY
polyline.Range.MaxX = p.BBox().MaxX
polyline.Range.MaxY = p.BBox().MaxX
for _, po := range p2.Points {
point := Point{Lng: po.X, Lat: po.Y}
polyline.Positions = append(polyline.Positions, point)
}
obj.Polylines = append(obj.Polylines, polyline)
} else if p3, ok2 := p.(*shp.Polygon); ok2 {
polyline := Polyline{}
polyline.Alpha = O_LAlpha
polyline.Color = O_LClr
polyline.Width = O_LWidth
polyline.Name = name
polyline.Beizhu = beizhu
polyline.Tishi = tishi
// polyline.Property = Property
polyline.Range.MinX = p.BBox().MinX
polyline.Range.MinY = p.BBox().MinY
polyline.Range.MaxX = p.BBox().MaxX
polyline.Range.MaxY = p.BBox().MaxX
for _, po := range p3.Points {
point := Point{Lng: po.X, Lat: po.Y}
polyline.Positions = append(polyline.Positions, point)
}
obj.Polylines = append(obj.Polylines, polyline)
// fmt.Println(p3.Points)
} else if p3, ok3 := p.(*shp.Point); ok3 {
point := Point{Lng: p3.X, Lat: p3.Y}
point.Name = name
point.Tishi = tishi
point.Beizhu = beizhu
obj.Points = append(obj.Points, point)
} else if p3, ok2 := p.(*shp.PolygonZ); ok2 {
polyline := Polyline{}
polyline.Alpha = O_LAlpha
polyline.Color = O_LClr
polyline.Width = O_LWidth
polyline.Name = name
polyline.Beizhu = beizhu
polyline.Tishi = tishi
// polyline.Property = Property
polyline.Range.MinX = p.BBox().MinX
polyline.Range.MinY = p.BBox().MinY
polyline.Range.MaxX = p.BBox().MaxX
polyline.Range.MaxY = p.BBox().MaxX
for _, po := range p3.Points {
point := Point{Lng: po.X, Lat: po.Y}
polyline.Positions = append(polyline.Positions, point)
}
obj.Polylines = append(obj.Polylines, polyline)
// fmt.Println(p3.Points)
} else if p3, ok3 := p.(*shp.PointZ); ok3 {
point := Point{Lng: p3.X, Lat: p3.Y}
point.Name = name
point.Tishi = tishi
point.Beizhu = beizhu
obj.Points = append(obj.Points, point)
} else {
fmt.Println("其他类型")
}
}
return nil, &obj
}
/*判断点是否被区域包含*/
func PointInPolygon(point Point, positions []Point) (bool, error) {
if len(positions) < 3 {
return false, globe.GetErrors("坐标点数量不能小于3")
}
polygon := geometry.Polygon{}
var pts []geometry.Point
for _, position := range positions {
pts = append(pts, geometry.Point{Lat: position.Lat, Lng: point.Lng})
}
// pts = append(pts, pts[len(pts)-1])
LineString := geometry.LineString{}
LineString.Coordinates = pts
polygon.Coordinates = []geometry.LineString{LineString}
return turf.PointInPolygon(geometry.Point{Lat: point.Lat, Lng: point.Lng}, polygon)
}

View File

@ -0,0 +1,133 @@
package clt
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/tiger1103/gfast/v3/api/v1/common/globe"
"github.com/tiger1103/gfast/v3/api/v1/common/tool"
"github.com/tiger1103/gfast/v3/database"
"github.com/tiger1103/gfast/v3/database/sqlite"
"io"
"net/http"
"os"
"path"
"strings"
)
func InitCltData(group *ghttp.RouterGroup) {
group.GET("/data/tileset/{source_id}/*.action", cltCallback)
group.GET("/data/bim/{source_id}/*.action", cltCallback)
}
func GetTile(sourceid, p string) []byte {
md5 := tool.Md5V(p)
tile := globe.Tile{}
database.GetSourceDB(sourceid).DB.Select("tile").Where("md5=?", md5).First(&tile)
// 创建一个字节缓冲区,并将压缩数据写入其中
buf := bytes.NewBuffer(tile.Tile)
// 创建一个gzip.Reader对象用于解压缩数据
reader, _ := gzip.NewReader(buf)
defer reader.Close()
// 读取解压缩后的数据
decompressedData, _ := io.ReadAll(reader)
return decompressedData
}
func cltCallback(request *ghttp.Request) {
sourceId := request.Get("source_id").String()
cltObj := database.GetSourceDB(sourceId)
if cltObj.DB == nil {
request.Response.WriteStatus(http.StatusNotFound)
return
}
argcs := strings.Split(request.RequestURI, "/")
argcs = argcs[7:]
md5 := tool.Md5V(strings.Join(argcs, "/"))
tile := globe.Tile{}
RowsAffected := cltObj.DB.Select("tile").Where("md5=?", md5).Find(&tile).RowsAffected
if RowsAffected == 0 {
request.Response.WriteStatus(http.StatusNotFound)
return
}
suffix := path.Ext(request.RequestURI)
if suffix == ".json" {
request.Response.Header().Set("content-type", "application/json")
} else {
request.Response.Header().Set("content-type", "application/octet-stream")
}
if cltObj.Gzip {
request.Response.Header().Set("Content-Encoding", "gzip")
}
globe.RenderData(request, tile.Tile)
}
type Info struct {
Params string `json:"params"`
}
type parseIsZip struct {
Zip bool `json:"zip"`
}
type IsJct struct {
Jct bool `json:"jct"`
}
func OpenClt(cltPath string, sourceID string) (error, *database.SourceObj) {
//if !globe.IS_OFFLINE_VERSION {
// //网络版事 需要拼接数据地址,方便服务器迁移
if !tool.PathExists(cltPath) {
getwd, err := os.Getwd()
if err != nil {
return err, nil
}
cltPath = path.Join(getwd, cltPath)
}
//}
fmt.Println(cltPath)
if tool.PathExists(cltPath) {
db, err := sqlite.OpenDB(cltPath)
if err != nil {
return err, nil
}
var obj database.SourceObj
obj.DB = db
var info []Info
db.Model(&Info{}).Find(&info)
p := parseIsZip{}
errs := json.Unmarshal([]byte(info[0].Params), &p)
if errs == nil {
obj.Gzip = p.Zip
}
suffix := path.Ext(cltPath)
if suffix == globe.CLT {
obj.Type = globe.TILESET
obj.Url = "/zm/api/v1/data/tileset/" + sourceID + "/tileset.json"
} else {
obj.Type = globe.BIM
obj.Url = "/zm/api/v1/data/bim/" + sourceID + "/tileset.json"
if len(info) < 2 {
//非jct资源
globe.CloseDB(db)
return globe.GetErrors("非jct资源"), nil
}
isjct := IsJct{}
errs := json.Unmarshal([]byte(info[1].Params), &isjct)
if errs != nil {
globe.CloseDB(db)
return globe.GetErrors("jct资源检测失败"), nil
}
}
database.SetSourceDB(sourceID, obj)
return err, &obj
}
fmt.Println("资源不存在:" + cltPath)
return globe.GetErrors("资源不存在:" + cltPath), nil
}

View File

@ -0,0 +1,123 @@
package mbt
import (
"github.com/gogf/gf/v2/net/ghttp"
"github.com/tiger1103/gfast/v3/api/v1/common/globe"
"github.com/tiger1103/gfast/v3/api/v1/common/tool"
"github.com/tiger1103/gfast/v3/database"
"github.com/tiger1103/gfast/v3/database/sqlite"
"gorm.io/gorm"
"math"
"net/http"
"os"
"path"
"strings"
)
func InitMbtData(group *ghttp.RouterGroup) {
group.GET("/data/mbt/{source_id}/{z}/{x}/{y}.*", mbtCallback)
}
func mbtCallback(request *ghttp.Request) {
sourceId := request.Get("source_id").String()
mbtobj := database.GetSourceDB(sourceId)
if mbtobj.DB == nil {
request.Response.WriteStatus(http.StatusNotFound)
return
}
z := request.Get("z").Int()
x := request.Get("x").Int()
y := request.Get("y").Int()
y = int(math.Pow(2, float64(z))) - 1 - y
tile := Tile{}
RowsAffected := mbtobj.DB.Model(&Tile{}).
Select("tile_data").
Where(&Tile{ZoomLevel: z, TileColumn: x, TileRow: y}).First(&tile).RowsAffected
if RowsAffected > 0 {
request.Response.Header().Set("content-type", mbtobj.ContentType)
globe.RenderData(request, tile.TileData)
return
} else {
request.Response.WriteStatus(http.StatusNotFound)
}
}
type Tile struct {
TileData []byte `json:"tile_data"`
ZoomLevel int `json:"zoom_level"`
TileColumn int `json:"tile_column"`
TileRow int `json:"tile_row"`
}
type Metadata struct {
Name string
Value string
}
func OpenMbt(mbtPath string, sourceID string) (error, *database.SourceObj) {
//if !globe.IS_OFFLINE_VERSION {
// //网络版事 需要拼接数据地址,方便服务器迁移
// mbtPath = path.Join(globe.SOURCE, mbtPath)
//}
getwd, err := os.Getwd()
if err != nil {
return err, nil
}
mbtPath = path.Join(getwd, mbtPath)
if !tool.PathExists(mbtPath) {
return globe.GetErrors("资源不存在," + mbtPath), nil
}
db, err := sqlite.OpenDB(mbtPath)
if err != nil {
return err, nil
}
var obj database.SourceObj
obj.DB = db
obj.Type = globe.LAYER
var meta []Metadata
db.Model(&Metadata{}).Find(&meta)
obj.Info.MinLevel, obj.Info.MaxLevel = startQueryLevel(db)
var format = "png"
for _, v := range meta {
if v.Name == "format" {
format = v.Value
}
if v.Name == "bounds" {
arr := strings.Split(v.Value, ",")
obj.Info.West = arr[0]
obj.Info.South = arr[1]
obj.Info.East = arr[2]
obj.Info.North = arr[3]
}
if v.Name == "profile" {
obj.Info.ProFile = v.Value
}
if v.Name == "description" {
//lsv下载的 自带投影,此时不需要加
if strings.Contains(v.Value, "LSV") {
obj.Info.TilingScheme = 0
} else {
obj.Info.TilingScheme = 1
}
}
}
obj.ContentType = "image/" + format
obj.Url = "/zm/api/v1/data/mbt/" + sourceID + "/{z}/{x}/{y}." + format
database.SetSourceDB(sourceID, obj)
return err, &obj
}
func startQueryLevel(db *gorm.DB) (min, max int) {
zoom_level := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}
var existsLevels []int
for i := 0; i < len(zoom_level); i++ {
RowsAffected := db.Model(&Tile{}).Select("zoom_level").Where(&Tile{ZoomLevel: i}).Find(&Tile{}).RowsAffected
if RowsAffected > 0 {
existsLevels = append(existsLevels, i)
}
}
if len(existsLevels) > 0 {
min = existsLevels[0]
max = existsLevels[len(existsLevels)-1]
}
return
}

View File

@ -0,0 +1,148 @@
package pak
import (
"fmt"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/tiger1103/gfast/v3/api/v1/common/globe"
"github.com/tiger1103/gfast/v3/api/v1/common/tool"
"github.com/tiger1103/gfast/v3/database"
"github.com/tiger1103/gfast/v3/database/sqlite"
"math"
"net/http"
"os"
"path"
"strconv"
"strings"
)
const (
image = "image"
terrain = "terrain"
)
func InitPakData(group *ghttp.RouterGroup) {
//group.GET("/data/pak/{source_id}/{z}/{x}/{y}.*", pakCallback)
//group.GET("/data/pak/{source_id}/layer.json", pakCallback)
group.GET("/data/pak/{source_id}/*.action", pakCallback)
}
type Json struct {
Layerjson []byte `json:"layerjson"`
}
// 获取pak文件中的表名
func gettablename(x int, y int, z int) string {
if z < 10 {
return "blocks"
} else {
tx := math.Ceil(float64(x / 512))
ty := math.Ceil(float64(y / 512))
return "blocks_" + strconv.Itoa(z) + "_" + strconv.Itoa(int(tx)) + "_" + strconv.Itoa(int(ty))
}
}
type Tile struct {
Tile []byte `json:"tile"`
Z int `json:"z"`
X int `json:"x"`
Y int `json:"y"`
}
func pakCallback(request *ghttp.Request) {
sourceId := request.Get("source_id").String()
pakobj := database.GetSourceDB(sourceId)
if pakobj.DB == nil {
request.Response.WriteStatus(http.StatusNotFound)
return
}
suffix := path.Ext(request.RequestURI)
if suffix == ".json" {
json := Json{}
pakobj.DB.Model(&Info{}).First(&json)
request.Response.Header().Set("content-type", "application/json")
globe.RenderData(request, json.Layerjson)
return
} else {
uri := request.RequestURI
arr := strings.Split(uri, "/")
//z := request.Get("z").Int()
//x := request.Get("x").Int()
//y := request.Get("y").Int()
z, _ := strconv.Atoi(arr[7])
x, _ := strconv.Atoi(arr[8])
y, _ := strconv.Atoi(strings.Split(arr[9], ".")[0])
//y = int(math.Pow(2, float64(z))) - 1 - y
tile := Tile{}
RowsAffected := pakobj.DB.Table(gettablename(x, y, z)).Select("tile").Where(&Tile{Z: z, X: x, Y: y}).First(&tile).RowsAffected
if RowsAffected > 0 {
request.Response.Header().Set("content-type", pakobj.ContentType)
if pakobj.Gzip {
request.Response.Header().Set("Content-Encoding", "gzip")
}
globe.RenderData(request, tile.Tile)
return
} else {
request.Response.WriteStatus(http.StatusNotFound)
}
}
}
type Info struct {
Minx float64 `json:"minx"`
Miny float64 `json:"miny"`
Maxx float64 `json:"maxx"`
Maxy float64 `json:"maxy"`
Minlevel int `json:"minlevel"`
Maxlevel int `json:"maxlevel"`
Type string `json:"type"`
Zip int `json:"zip"`
//Layerjson []byte `json:"layerjson"`
Contenttype string `json:"contenttype"`
}
func OpenPak(pakPath string, sourceID string) (error, *database.SourceObj) {
//if !globe.IS_OFFLINE_VERSION {
// //网络版事 需要拼接数据地址,方便服务器迁移
// pakPath = path.Join(globe.SOURCE, pakPath)
//}
getwd, err := os.Getwd()
if err != nil {
return err, nil
}
pakPath = path.Join(getwd, pakPath)
if !tool.PathExists(pakPath) {
return globe.GetErrors("资源不存在," + pakPath), nil
}
fmt.Println("资源存在")
db, err := sqlite.OpenDB(pakPath)
if err != nil {
fmt.Println(err)
return err, nil
}
var obj database.SourceObj
obj.DB = db
info := Info{}
db.Model(&Info{}).First(&info)
if info.Type == image {
obj.Type = globe.LAYER
obj.ContentType = info.Contenttype
obj.Url = "/zm/api/v1/data/pak/" + sourceID + "/{z}/{x}/{y}." + strings.Split(obj.ContentType, "/")[1]
}
if info.Type == terrain {
obj.Type = globe.TERRAIN
obj.ContentType = "application/octet-stream"
obj.Url = "/zm/api/v1/data/pak/" + sourceID + "/"
}
if info.Zip > 0 {
obj.Gzip = true
}
obj.Info.MaxLevel = info.Maxlevel
obj.Info.MinLevel = info.Minlevel
obj.Info.West = strconv.FormatFloat(info.Minx, 'f', -1, 64)
obj.Info.South = strconv.FormatFloat(info.Miny, 'f', -1, 64)
obj.Info.East = strconv.FormatFloat(info.Maxx, 'f', -1, 64)
obj.Info.North = strconv.FormatFloat(info.Maxy, 'f', -1, 64)
database.SetSourceDB(sourceID, obj)
return err, &obj
}

View File

@ -0,0 +1,295 @@
package shp
import (
"context"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/tidwall/gjson"
shp2 "github.com/tiger1103/gfast/v3/api/v1/common/shp"
"io"
"net/http"
"regexp"
"strings"
)
const MaxNeighborsLen = 8 //最大邻居个数
const SOURCE = "static/source/"
const Gisfile = "gisfile/"
// 84的投影文件
const WGS84_PRJ = "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0.0174532925199433]]"
func InitShp(group *ghttp.RouterGroup) {
group.Group("/shp", func(group *ghttp.RouterGroup) {
group.Bind(new(SHP))
})
}
type SHP struct {
}
type SHPLoadReq struct {
g.Meta `path:"load" summary:"cesium加载shp" method:"get" tags:"shp相关" `
//SourceID string `json:"source_id" dc:"资源id" v:"required"`
Path string `json:"path" dc:"路径" v:"required"`
}
type SHPLoadRes struct {
shp2.ShpObj
}
//func (receiver SHP) LoadSHP(ctx context.Context, req *SHPLoadReq) (res *SHPLoadRes, err error) {
// err, obj := shp2.ReadShp(req.Path)
// if err != nil {
// return nil, err
// }
// res = &SHPLoadRes{}
// res.Points = obj.Points
// res.Polylines = obj.Polylines
// res.Polygons = obj.Polygons
// return res, err
//}
type Range struct {
MinX float64 `json:"min_x"`
MinY float64 `json:"min_y"`
MaxX float64 `json:"max_x"`
MaxY float64 `json:"max_y"`
}
type Position struct {
X float64 `json:"x"`
Y float64 `json:"y"`
Z float64 `json:"z"`
Attr map[string]interface{} `json:"attr"`
}
type Text struct {
X float64 `json:"x"`
Y float64 `json:"y"`
Text string `json:"text"`
}
type Circle struct {
X float64 `json:"x"`
Y float64 `json:"y"`
Z float64 `json:"z"`
Radius float64 `json:"radius"`
}
type Polyline struct {
Positions []Position `json:"positions"`
Attr map[string]interface{} `json:"attr"`
Range Range `json:"range"`
}
type Polygon struct {
Positions []Position `json:"positions"`
Attr map[string]interface{} `json:"attr"`
Range Range `json:"range"`
}
type MultiPolygon struct {
Polygons []Polygon `json:"polygons"`
Attr map[string]interface{} `json:"attr"`
Range Range `json:"range"`
}
type MultiPolyline struct {
Polylines []Polyline `json:"polylines"`
Attr map[string]interface{} `json:"attr"`
Range Range `json:"range"`
}
type LayerData struct {
LayerName string `json:"layer_name"`
Proj4 string `json:"proj4"`
Texts []Text `json:"texts"`
Circles []Circle `json:"circles"`
Points []Position `json:"points"`
Polylines []Polyline `json:"polylines"`
Polygons []Polygon `json:"polygons"`
MultiPolygons []MultiPolygon `json:"multi_polygons"`
MultiPolylines []MultiPolyline `json:"multi_polylines"`
}
type Directory struct {
Name string `json:"name"`
IsDir bool `json:"is_dir"`
Data []LayerData `json:"data"`
Children []Directory `json:"children"`
}
type Response struct {
Code int `json:"code"`
Data Directory `json:"data"`
}
// JoinLoadSHP 原本的LoadSHP有问题直接调用远程的接口
func (receiver SHP) JoinLoadSHP(ctx context.Context, req *SHPLoadReq) (res *SHPLoadRes, err error) {
res = new(SHPLoadRes)
if req.Path[0] == '/' {
req.Path = req.Path[1:]
}
url := "http://192.168.1.177:8921/yjearth5/api/v1/vector/load?path=/project/zmkg/" + req.Path
reqs, err := http.Get(url)
if err != nil {
return nil, err
}
defer reqs.Body.Close()
body, err := io.ReadAll(reqs.Body)
if err != nil {
return nil, err
}
if gjson.Get(string(body), "message").String() == "资源不存在" {
return nil, fmt.Errorf("资源不存在")
}
var list shp2.ShpObj
processShapes(body, "data.children.0.data.0.polylines", &list)
processShapes(body, "data.children.0.data.0.polygons", &list)
processShapes(body, "data.children.0.data", &list)
if list.Polylines == nil {
list.Polylines = []shp2.Polyline{}
}
if list.Polygons == nil {
list.Polygons = []shp2.Polygon{}
}
if list.Points == nil {
list.Points = []shp2.Point{}
}
res.Polygons = list.Polygons
res.Polylines = list.Polylines
res.Points = list.Points
return res, err
}
func Adasda(path string) (res *SHPLoadRes) {
fmt.Println("加载shp文件", path)
res = new(SHPLoadRes)
if path[0] == '/' {
path = path[1:]
}
url := "http://192.168.1.177:8921/yjearth5/api/v1/vector/load?path=/project/zmkg/" + path
reqs, err := http.Get(url)
if err != nil {
fmt.Println("请求数据失败")
}
defer reqs.Body.Close()
body, err := io.ReadAll(reqs.Body)
if err != nil {
fmt.Errorf("读取资源失败")
}
if gjson.Get(string(body), "message").String() == "资源不存在" {
fmt.Errorf("资源不存在")
}
var list = shp2.ShpObj{
Polylines: []shp2.Polyline{},
Polygons: []shp2.Polygon{},
Points: []shp2.Point{},
}
processShapes(body, "data.children.0.data.0.polylines", &list)
processShapes(body, "data.children.0.data.0.polygons", &list)
processShapes(body, "data.children.0.data", &list)
res.Polygons = list.Polygons
res.Polylines = list.Polylines
res.Points = list.Points
return
}
func processShapes(data []byte, path string, shapes *shp2.ShpObj) {
gjson.GetBytes(data, path).ForEach(func(key, shape gjson.Result) bool {
// 面
var pointsList []shp2.Point
psGet := shape.Get("positions")
if psGet.Exists() {
psGet.ForEach(func(posKey, value gjson.Result) bool {
longitude := value.Get("x").Float()
latitude := value.Get("y").Float()
altitude := value.Get("z").Float()
pointsList = append(pointsList, shp2.Point{
Lng: longitude,
Lat: latitude,
Alt: altitude,
})
return true
})
if len(pointsList) > 0 {
shapes.Polylines = append(shapes.Polylines, shp2.Polyline{
Positions: pointsList,
Width: "5",
Color: "#f00",
})
}
if shape.Get("attr").Exists() {
aName := shape.Get("attr.NAME").String()
fmt.Println("!!! ", aName)
shapes.Polylines[len(shapes.Polylines)-1].Property = shp2.Property{
Name: aName,
}
}
if shape.Get("range").Exists() {
minX := shape.Get("range.min_x").Float()
minY := shape.Get("range.min_y").Float()
maxX := shape.Get("range.max_x").Float()
maxY := shape.Get("range.max_y").Float()
shapes.Polylines[len(shapes.Polylines)-1].Range = shp2.Box{
MinX: minX,
MinY: minY,
MaxX: maxX,
MaxY: maxY,
}
}
} else {
//fmt.Println("!!! ", shape.Get("points"))
// 点
var point []shp2.Point
shape.Get("points").ForEach(func(posKey, value gjson.Result) bool {
aName := value.Get("attr.NAME")
if value.Get("attr.NAME").Exists() {
//排除nc 和 空
isPureNumber, _ := regexp.MatchString(`^\d+$`, aName.String())
if strings.Contains(aName.String(), "NC") || strings.TrimSpace(aName.String()) == "" || isPureNumber {
return true
}
longitude := value.Get("x").Float()
latitude := value.Get("y").Float()
altitude := value.Get("z").Float()
point = append(point, shp2.Point{
Lng: longitude,
Lat: latitude,
Alt: altitude,
Property: shp2.Property{
Name: aName.String(),
},
})
}
return true
})
if len(point) > 0 {
shapes.Points = append(shapes.Points, point...)
}
}
return true
})
}

View File

@ -0,0 +1,85 @@
package source
import (
"context"
"encoding/json"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gctx"
"github.com/tiger1103/gfast/v3/api/v1/common/globe"
"github.com/tiger1103/gfast/v3/api/v1/common/source/clt"
"github.com/tiger1103/gfast/v3/api/v1/common/source/pak"
"github.com/tiger1103/gfast/v3/database"
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
"path"
)
func InitSource(group *ghttp.RouterGroup) {
ReadAllSourceFromDB()
group.Group("/data/service", func(group *ghttp.RouterGroup) {
group.Bind(new(LoadSource))
})
}
type LoadSource struct {
}
type LoadSourceReq struct {
g.Meta `path:"load-compact-service" summary:"引擎加载资源" method:"post" tags:"资源相关" `
SourceID string `json:"source_id" v:"required" dc:"资源id"`
}
type LoadSourceRes struct {
Type string `json:"type"`
Url string `json:"url"`
database.SourceInfo
}
func (LoadSource) LoadCompactService(ctx context.Context, req *LoadSourceReq) (res *LoadSourceRes, err error) {
obj := database.GetSourceDB(req.SourceID)
res = &LoadSourceRes{
Url: obj.Url,
Type: obj.Type,
}
res.North = obj.Info.North
res.West = obj.Info.West
res.East = obj.Info.East
res.South = obj.Info.South
res.ProFile = obj.Info.ProFile
res.TilingScheme = obj.Info.TilingScheme
res.MaxLevel = obj.Info.MaxLevel
res.MinLevel = obj.Info.MinLevel
return
}
func ReadAllSourceFromDB() {
var sources []database.SOURCE
var gfb []database.SOURCE
g.DB().Model(&database.SOURCE{})
ctx := gctx.New()
//模型
dao.QianqiMoxing.Ctx(ctx).Scan(&sources)
//光伏板
dao.QianqiGuangfuban.Ctx(ctx).Scan(&gfb)
sources = append(sources, gfb...)
for _, v := range sources {
suffix := path.Ext(v.SourcePath)
switch suffix {
case globe.CLT:
err, obj := clt.OpenClt(v.SourcePath, v.SourceID)
if err != nil {
fmt.Println(err)
}
marshal, _ := json.Marshal(obj)
fmt.Println(string(marshal), v.SourceID)
break
case globe.PAK:
err, obj := pak.OpenPak(v.SourcePath, v.SourceID)
if err != nil {
fmt.Println(err)
}
marshal, _ := json.Marshal(obj)
fmt.Println(string(marshal), v.SourceID)
break
}
}
}

View File

@ -0,0 +1,49 @@
package excel
import (
"errors"
"fmt"
"github.com/tiger1103/gfast/v3/api/v1/common/tool"
"github.com/xuri/excelize/v2"
)
type Sheet struct {
Name string `json:"name"`
Rows [][]string `json:"rows"`
}
func ReadXlsx(xlsx string) (err error, sheet []Sheet) {
if !tool.PathExists(xlsx) {
return errors.New("文件不存在:" + xlsx), sheet
}
f, err := excelize.OpenFile(xlsx)
if err != nil {
fmt.Println(err)
return err, sheet
}
defer func() {
// 关闭工作簿
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
list := f.GetSheetList()
// 获取 Sheet1 上所有单元格
for _, sheetName := range list {
result, err := f.GetRows(sheetName)
if err != nil {
fmt.Println(err)
continue
}
sheet = append(sheet, Sheet{sheetName, result})
//rows = append(rows, result...)
}
//for _, row := range rows {
// for _, colCell := range row {
// fmt.Print(colCell, "\t")
// }
// fmt.Println()
//}
return nil, sheet
}

View File

@ -0,0 +1,25 @@
package proj
import (
_ "embed"
"github.com/dop251/goja"
)
//go:embed proj4.js
var proj4 string
var CGCS2000_to_WGS84 func(degrees int, cscs2000 [][]string) string
var WGS84_to_CGCS2000 func(degrees int, wgs84 [][]string) string
func InitProj() {
vm := goja.New()
vm.RunString(proj4)
vm.ExportTo(vm.Get("CGCS2000_to_WGS84"), &CGCS2000_to_WGS84)
vm.ExportTo(vm.Get("WGS84_to_CGCS2000"), &WGS84_to_CGCS2000)
//var ss [][]string
//ss = append(ss, []string{
// "106.545463204423", "23.467020901621", "805.6832",
//})
//s := WGS84_to_CGCS2000(108, ss)
//fmt.Println(s)
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More