初始
This commit is contained in:
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
.buildpath
|
||||
.hgignore.swp
|
||||
.project
|
||||
.orig
|
||||
.swp
|
||||
.idea/
|
||||
.settings/
|
||||
.vscode/
|
||||
vendor/
|
||||
composer.lock
|
||||
gitpush.sh
|
||||
pkg/
|
||||
bin/
|
||||
cbuild
|
||||
**/.DS_Store
|
||||
.vscode/
|
||||
.test/
|
||||
main
|
||||
output/
|
||||
manifest/output/
|
||||
*.exe
|
||||
tmp/
|
||||
resource/data/gen_sql
|
||||
resource/log/
|
||||
resource/public/big_file
|
||||
resource/public/upload_file
|
||||
manifest/config/config.yaml
|
||||
GFastV3
|
||||
/manifest/config/db.yaml
|
||||
/public
|
||||
/resource/
|
||||
.xlsx
|
201
LICENSE
Normal file
201
LICENSE
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
4
api/app/process/process.go
Normal file
4
api/app/process/process.go
Normal file
@ -0,0 +1,4 @@
|
||||
package process
|
||||
|
||||
type ProcessApi struct {
|
||||
}
|
47
api/app/process/req.go
Normal file
47
api/app/process/req.go
Normal 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
56
api/app/process/res.go
Normal 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"`
|
||||
}
|
74
api/app/process/service.go
Normal file
74
api/app/process/service.go
Normal 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
|
||||
}
|
4
api/app/project/project.go
Normal file
4
api/app/project/project.go
Normal file
@ -0,0 +1,4 @@
|
||||
package project
|
||||
|
||||
type Project struct {
|
||||
}
|
44
api/app/project/req.go
Normal file
44
api/app/project/req.go
Normal 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
126
api/app/project/res.go
Normal 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
279
api/app/project/service.go
Normal 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
31
api/app/stage/req.go
Normal 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
24
api/app/stage/res.go
Normal 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
52
api/app/stage/service.go
Normal 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
4
api/app/stage/stage.go
Normal file
@ -0,0 +1,4 @@
|
||||
package stage
|
||||
|
||||
type StageApi struct {
|
||||
}
|
48
api/app/visual/req.go
Normal file
48
api/app/visual/req.go
Normal 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
36
api/app/visual/res.go
Normal 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
13
api/app/visual/router.go
Normal 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
96
api/app/visual/service.go
Normal 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
4
api/app/visual/visual.go
Normal file
@ -0,0 +1,4 @@
|
||||
package visual
|
||||
|
||||
type Visual struct {
|
||||
}
|
38
api/app/visual_remark/req.go
Normal file
38
api/app/visual_remark/req.go
Normal 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"`
|
||||
}
|
31
api/app/visual_remark/res.go
Normal file
31
api/app/visual_remark/res.go
Normal 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{}
|
115
api/app/visual_remark/service.go
Normal file
115
api/app/visual_remark/service.go
Normal 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
|
||||
}
|
4
api/app/visual_remark/visual_remark.go
Normal file
4
api/app/visual_remark/visual_remark.go
Normal file
@ -0,0 +1,4 @@
|
||||
package visual_remark
|
||||
|
||||
type VisualRemark struct {
|
||||
}
|
30
api/attendanceMachine/req.go
Normal file
30
api/attendanceMachine/req.go
Normal 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"`
|
||||
}
|
15
api/attendanceMachine/router.go
Normal file
15
api/attendanceMachine/router.go
Normal 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))
|
||||
})
|
||||
})
|
||||
}
|
308
api/attendanceMachine/service.go
Normal file
308
api/attendanceMachine/service.go
Normal 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
15
api/constant/video_hat.go
Normal 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
48
api/hwy/client/client.go
Normal 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
74
api/hwy/detail/detail.go
Normal 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"`
|
||||
}
|
335
api/hwy/equipment/equipment.go
Normal file
335
api/hwy/equipment/equipment.go
Normal 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
296
api/hwy/equipment/res.go
Normal 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
217
api/hwy/plant/plant.go
Normal 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
189
api/hwy/plant/res.go
Normal 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
17
api/hwy/router/router.go
Normal 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
92
api/hwy/sign/sign.go
Normal 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
39
api/pilot/manage/login.go
Normal 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
122
api/pilot/manage/manage.go
Normal 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
51
api/pilot/map/map.go
Normal 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
32
api/pilot/pilot.go
Normal 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))
|
||||
})
|
||||
})
|
||||
}
|
48
api/pilot/wayline/wayline.go
Normal file
48
api/pilot/wayline/wayline.go
Normal 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
32
api/pilot/ws/ws.go
Normal 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
162
api/project/index_count.go
Normal 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
71
api/project/index_pv.go
Normal 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
61
api/project/model.go
Normal 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
4
api/project/project.go
Normal file
@ -0,0 +1,4 @@
|
||||
package project
|
||||
|
||||
type Project struct {
|
||||
}
|
46
api/project/pv_update.go
Normal file
46
api/project/pv_update.go
Normal 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
12
api/project/router.go
Normal 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
38
api/project/type_map.go
Normal 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
98
api/saft_hat/device.go
Normal 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
39
api/saft_hat/entity.go
Normal 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
8
api/saft_hat/hat.go
Normal file
@ -0,0 +1,8 @@
|
||||
package saft_hat
|
||||
|
||||
// 定义签名的 APPID 和密钥常量
|
||||
const secret = "5366474e589c4dcfadeef223a466ca0b"
|
||||
const appid = "0c9ab925c6684ab4a33350e15ee35062"
|
||||
|
||||
type Hat struct {
|
||||
}
|
54
api/saft_hat/hat_danger.go
Normal file
54
api/saft_hat/hat_danger.go
Normal 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
259
api/saft_hat/hat_data.go
Normal 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
|
||||
}
|
||||
}
|
78
api/saft_hat/hat_location.go
Normal file
78
api/saft_hat/hat_location.go
Normal 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
81
api/saft_hat/hat_rate.go
Normal 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
62
api/saft_hat/hat_sos.go
Normal 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
133
api/saft_hat/hat_status.go
Normal 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 // 如果更新失败,则跳过本次设备的更新
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
78
api/saft_hat/hat_text_audio.go
Normal file
78
api/saft_hat/hat_text_audio.go
Normal 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
114
api/saft_hat/hat_time.go
Normal 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
53
api/saft_hat/hat_tip.go
Normal 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
81
api/saft_hat/hat_voice.go
Normal 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
14
api/saft_hat/router.go
Normal 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
35
api/saft_hat/util.go
Normal 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
21
api/saft_hat/verify.go
Normal 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) // 比较计算得到的摘要和传入的签名
|
||||
}
|
91
api/test/test_contact_info.go
Normal file
91
api/test/test_contact_info.go
Normal 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
|
||||
}
|
104
api/test/test_follow_info.go
Normal file
104
api/test/test_follow_info.go
Normal 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
|
||||
}
|
98
api/test/test_owner_info.go
Normal file
98
api/test/test_owner_info.go
Normal 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
|
||||
}
|
104
api/test/test_project_info.go
Normal file
104
api/test/test_project_info.go
Normal 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
19
api/v1/common/captcha.go
Normal 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"`
|
||||
}
|
491
api/v1/common/coryCommon/baiduOCR.go
Normal file
491
api/v1/common/coryCommon/baiduOCR.go
Normal file
@ -0,0 +1,491 @@
|
||||
package coryCommon
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/gclient"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/tiger1103/gfast-cache/cache"
|
||||
commonService "github.com/tiger1103/gfast/v3/internal/app/common/service"
|
||||
"github.com/tiger1103/gfast/v3/library/liberr"
|
||||
tool "github.com/tiger1103/gfast/v3/utility/coryUtils"
|
||||
"golang.org/x/net/context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
/**
|
||||
功能:
|
||||
文字识别:百度API识别身份证、银行卡内容、人脸检测、人脸对比
|
||||
*/
|
||||
|
||||
// OcrReq 请求体参数 身份证和银行卡都是此结构体,
|
||||
type OcrReq struct {
|
||||
Image string `json:"image"` // 二选一:图像数据,base64编码后进行urlencode,需去掉编码头(data:image/jpeg;base64, ) 要求base64编码和urlencode后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/jpeg/png/bmp格式
|
||||
Url string `json:"url"` // 二选一:图片完整URL,URL长度不超过1024字节,URL对应的图片base64编码后大小不超过4M,最短边至少15px,最长边最大4096px,支持jpg/jpeg/png/bmp格式,当image字段存在时url字段失效 请注意关闭URL防盗链
|
||||
IdCardSide string `json:"id_card_side"` // 身份证需要此参数(人头面填写:front,国徽面填写:back) 银行卡不需要
|
||||
DetectPhoto bool `json:"detect_photo"` // 是否检测身份证进行裁剪,默认不检测。可选值:true-检测身份证并返回证照的 base64 编码及位置信息
|
||||
}
|
||||
|
||||
/*
|
||||
1、clientId 必须参数,应用的APIKey
|
||||
2、clientSecret 必须参数,应用的Secret Key;
|
||||
3、存在redis的数据前缀
|
||||
*/
|
||||
var clientId = ""
|
||||
var clientSecret = ""
|
||||
var cacheRedis = "zmGoBaiDuOcrAccessToken"
|
||||
|
||||
func init() {
|
||||
one, err := g.Cfg().Get(gctx.New(), "baiDuYun.clientId")
|
||||
if err != nil {
|
||||
fmt.Println("百度云API未找到!")
|
||||
}
|
||||
two, err := g.Cfg().Get(gctx.New(), "baiDuYun.clientSecret")
|
||||
if err != nil {
|
||||
fmt.Println("百度云Secret未找到!")
|
||||
}
|
||||
clientId = one.String()
|
||||
clientSecret = two.String()
|
||||
}
|
||||
|
||||
/*
|
||||
IDCardInfo 获取身份证相关数据
|
||||
*/
|
||||
type IDCardInfo struct {
|
||||
WordsResult WordsResult `json:"words_result"`
|
||||
WordsResultNum int `json:"words_result_num"`
|
||||
IDCardNumberType int `json:"idcard_number_type"`
|
||||
ImageStatus string `json:"image_status"`
|
||||
LogID int64 `json:"log_id"`
|
||||
Photo string `json:"photo"`
|
||||
}
|
||||
type WordsResult struct {
|
||||
Name Field `json:"姓名"`
|
||||
Nation Field `json:"民族"`
|
||||
Address Field `json:"住址"`
|
||||
CitizenIdentification Field `json:"公民身份号码"`
|
||||
Birth Field `json:"出生"`
|
||||
Gender Field `json:"性别"`
|
||||
ExpirationDate Field `json:"失效日期"`
|
||||
IssuingAuthority Field `json:"签发机关"`
|
||||
IssueDate Field `json:"签发日期"`
|
||||
}
|
||||
type Field struct {
|
||||
Location Location `json:"location"`
|
||||
Words string `json:"words"`
|
||||
}
|
||||
type Location struct {
|
||||
Top int `json:"top"`
|
||||
Left int `json:"left"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
}
|
||||
|
||||
func ImgOCR(vr OcrReq) (m map[string]interface{}) {
|
||||
//请求路径+token
|
||||
baseUrl := "https://aip.baidubce.com/rest/2.0/ocr/v1/idcard"
|
||||
//先从缓存里面捞取token,如果没得就重新获取token
|
||||
var atStr = redisCacheStr()
|
||||
// 构造 URL 参数
|
||||
params := url.Values{}
|
||||
params.Set("access_token", atStr)
|
||||
// 构造完整的请求 URL
|
||||
requestURL := fmt.Sprintf("%s?%s", baseUrl, params.Encode())
|
||||
|
||||
response, err := gclient.New().ContentJson().ContentType("application/x-www-form-urlencoded").Post(gctx.New(), requestURL, vr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var dataInfo = strings.ReplaceAll(response.ReadAllString(), " ", "")
|
||||
|
||||
//解析数据
|
||||
bodyData := []byte(dataInfo)
|
||||
var idCardInfo IDCardInfo
|
||||
err = json.Unmarshal(bodyData, &idCardInfo)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to parse JSON data:", err)
|
||||
return
|
||||
}
|
||||
m = make(map[string]interface{})
|
||||
//身份证正反面颠倒了,直接返回空
|
||||
if idCardInfo.ImageStatus == "reversed_side" {
|
||||
return
|
||||
}
|
||||
|
||||
result := idCardInfo.WordsResult
|
||||
//if idCardInfo.Photo != "" {
|
||||
// m["pacePhoto"] = idCardInfo.Photo
|
||||
//}
|
||||
if result.Name.Words != "" {
|
||||
m["userName"] = result.Name.Words
|
||||
}
|
||||
if result.Nation.Words != "" {
|
||||
m["sfzNation"] = result.Nation.Words
|
||||
}
|
||||
if result.Address.Words != "" {
|
||||
m["sfzSite"] = result.Address.Words
|
||||
}
|
||||
if result.CitizenIdentification.Words != "" {
|
||||
m["sfzNumber"] = result.CitizenIdentification.Words
|
||||
}
|
||||
if result.Birth.Words != "" {
|
||||
str, _ := tool.New().TimeCycle(result.Birth.Words)
|
||||
m["sfzBirth"] = str
|
||||
}
|
||||
if result.Gender.Words != "" {
|
||||
var se = result.Gender.Words
|
||||
if se == "男" {
|
||||
se = "1"
|
||||
} else if se == "女" {
|
||||
se = "2"
|
||||
} else {
|
||||
se = "3"
|
||||
}
|
||||
m["sex"] = se
|
||||
}
|
||||
if result.ExpirationDate.Words != "" {
|
||||
str, err := tool.New().TimeCycle(result.ExpirationDate.Words)
|
||||
if err == nil {
|
||||
m["sfzEnd"] = str
|
||||
} else {
|
||||
str := result.ExpirationDate.Words
|
||||
m["sfzEnd"] = str
|
||||
}
|
||||
}
|
||||
if result.IssuingAuthority.Words != "" {
|
||||
m["IssuingAuthority"] = result.IssuingAuthority.Words
|
||||
}
|
||||
if result.IssueDate.Words != "" {
|
||||
str, _ := tool.New().TimeCycle(result.IssueDate.Words)
|
||||
m["sfzStart"] = str
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
/*
|
||||
BankData 获取银行卡相关数据
|
||||
针对卡号、有效期、发卡行、卡片类型、持卡人5个关键字段进行结构化识别,识别准确率超过99%
|
||||
*/
|
||||
type BankData struct {
|
||||
ValidDate string `json:"valid_date"` //有效期
|
||||
BankCardNumber string `json:"bank_card_number"` //银行卡卡号
|
||||
BankName string `json:"bank_name"` //银行名,不能识别时为空
|
||||
BankCardType int `json:"bank_card_type"` //银行卡类型,0:不能识别; 1:借记卡; 2:贷记卡(原信用卡大部分为贷记卡); 3:准贷记卡; 4:预付费卡
|
||||
HolderName string `json:"holder_name"` //持卡人姓名,不能识别时为空
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Res BankData `json:"result"` //具体数据
|
||||
Direction int `json:"direction"` //图像方向。 - - 1:未定义; - 0:正向; - 1:逆时针90度; - 2:逆时针180度; - 3:逆时针270度
|
||||
LogID int64 `json:"log_id"` //请求标识码,随机数,唯一。
|
||||
}
|
||||
|
||||
func ImgYhkOCR(vr OcrReq) (m map[string]interface{}) {
|
||||
m = make(map[string]interface{})
|
||||
//请求路径+token
|
||||
baseUrl := "https://aip.baidubce.com/rest/2.0/ocr/v1/bankcard"
|
||||
//先从缓存里面捞取token
|
||||
var atStr = redisCacheStr()
|
||||
// 构造 URL 参数
|
||||
params := url.Values{}
|
||||
params.Set("access_token", atStr)
|
||||
// 构造完整的请求 URL
|
||||
requestURL := fmt.Sprintf("%s?%s", baseUrl, params.Encode())
|
||||
response, err := gclient.New().ContentJson().ContentType("application/x-www-form-urlencoded").Post(gctx.New(), requestURL, vr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
//解析数据
|
||||
allString := response.ReadAllString()
|
||||
bodyData := []byte(allString)
|
||||
var result Result
|
||||
err = json.Unmarshal(bodyData, &result)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to parse JSON data:", err)
|
||||
return
|
||||
}
|
||||
if result.Res.ValidDate != "" {
|
||||
m["ValidDate"] = result.Res.ValidDate
|
||||
}
|
||||
if result.Res.BankCardNumber != "" {
|
||||
m["yhkNumber"] = strings.ReplaceAll(result.Res.BankCardNumber, " ", "")
|
||||
}
|
||||
if result.Res.BankName != "" {
|
||||
m["yhkOpeningBank"] = result.Res.BankName
|
||||
}
|
||||
if result.Res.BankCardType >= 0 {
|
||||
m["BankCardType"] = result.Res.BankCardType
|
||||
}
|
||||
if result.Res.HolderName != "" {
|
||||
m["yhkCardholder"] = result.Res.HolderName
|
||||
}
|
||||
return m
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
HumanFaceReq 请求参数 人脸识别+人脸检测
|
||||
*/
|
||||
type HumanFaceReq struct {
|
||||
Image string `json:"image"` //图片此处填写base64
|
||||
ImageType string `json:"image_type" gf:"default:BASE64" ` //图片类型-BASE64-URL-BASE64(此封装固定用base64)
|
||||
FaceField string `json:"face_field" gf:"default:face_type,quality"` //包括age,expression,face_shape,gender,glasses,landmark,landmark150, quality,eye_status,emotion,face_type,mask,spoofing信息 逗号分隔. 默认只返回face_token、人脸框、概率和旋转角度
|
||||
}
|
||||
|
||||
/*
|
||||
HumanFaceRep 返回参数 人脸识别
|
||||
*/
|
||||
type HumanFaceRep struct {
|
||||
ErrorCode int `json:"error_code"`
|
||||
ErrorMsg string `json:"error_msg"`
|
||||
LogId int `json:"log_id"`
|
||||
Timestamp int `json:"timestamp"`
|
||||
Cached int `json:"cached"`
|
||||
Result struct {
|
||||
FaceNum int `json:"face_num"`
|
||||
FaceList []struct {
|
||||
FaceToken string `json:"face_token"`
|
||||
Location struct {
|
||||
Left float64 `json:"left"`
|
||||
Top float64 `json:"top"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Rotation int `json:"rotation"`
|
||||
} `json:"location"`
|
||||
FaceProbability float64 `json:"face_probability"`
|
||||
Angle struct {
|
||||
Yaw float64 `json:"yaw"`
|
||||
Pitch float64 `json:"pitch"`
|
||||
Roll float64 `json:"roll"`
|
||||
} `json:"angle"`
|
||||
FaceType struct {
|
||||
Type string `json:"type"`
|
||||
Probability float64 `json:"probability"`
|
||||
} `json:"face_type"`
|
||||
Quality struct {
|
||||
Occlusion struct {
|
||||
LeftEye float64 `json:"left_eye"`
|
||||
RightEye float64 `json:"right_eye"`
|
||||
Nose float64 `json:"nose"`
|
||||
Mouth float64 `json:"mouth"`
|
||||
LeftCheek float64 `json:"left_cheek"`
|
||||
RightCheek float64 `json:"right_cheek"`
|
||||
ChinContour float64 `json:"chin_contour"`
|
||||
} `json:"occlusion"`
|
||||
Blur float64 `json:"blur"`
|
||||
Illumination float64 `json:"illumination"`
|
||||
Completeness int64 `json:"completeness"`
|
||||
} `json:"quality"`
|
||||
} `json:"face_list"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
func HumanFace(hf *HumanFaceReq) (err error) {
|
||||
ctx := gctx.New()
|
||||
err = g.Try(ctx, func(ctx context.Context) {
|
||||
err = nil
|
||||
//1、请求地址+token
|
||||
url := "https://aip.baidubce.com/rest/2.0/face/v3/detect?access_token=" + redisCacheStr()
|
||||
marshal, err := json.Marshal(hf)
|
||||
if err != nil {
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
return
|
||||
}
|
||||
//3、准备请求
|
||||
payload := strings.NewReader(string(marshal))
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("POST", url, payload)
|
||||
if err != nil {
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
return
|
||||
}
|
||||
//4、设置请求头
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
//5、发送请求
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
//6、返回数据
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
return
|
||||
}
|
||||
var aaa = body
|
||||
//解析数据
|
||||
var result HumanFaceRep
|
||||
err = json.Unmarshal(aaa, &result)
|
||||
if err != nil {
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
return
|
||||
}
|
||||
if result.ErrorCode != 0 {
|
||||
if result.ErrorMsg == "pic not has face" {
|
||||
err = errors.New("这张照片没有人脸")
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
} else {
|
||||
err = errors.New(result.ErrorMsg)
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//1、人脸置信度,范围【0~1】,代表这是一张人脸的概率,0最小、1最大。其中返回0或1时,数据类型为Integer
|
||||
dataInfo := result.Result.FaceList[0]
|
||||
reliabilityOne := dataInfo.FaceProbability
|
||||
if reliabilityOne != 1.0 {
|
||||
err = errors.New("识别不清晰!")
|
||||
}
|
||||
//2、判断是否真是人脸 human: 真实人脸 cartoon: 卡通人脸
|
||||
reliabilityTwo := dataInfo.FaceType.Type
|
||||
if reliabilityTwo == "cartoon" {
|
||||
err = errors.New("请传入真实人脸!")
|
||||
} else {
|
||||
//判断可信度 置信度,范围0~1
|
||||
reliabilityThree := dataInfo.FaceType.Probability
|
||||
if reliabilityThree < 0.8 {
|
||||
err = errors.New("请勿化妆太过夸张!")
|
||||
}
|
||||
}
|
||||
//3、人脸模糊程度,范围[0~1],0表示清晰,1表示模糊
|
||||
reliabilityFour := dataInfo.Quality.Blur
|
||||
if reliabilityFour >= 0.1 {
|
||||
err = errors.New("人脸过于模糊!")
|
||||
}
|
||||
//4、光线太暗 0~255 值越大光线越好
|
||||
reliabilityFive := dataInfo.Quality.Illumination
|
||||
if reliabilityFive < 80.0 {
|
||||
err = errors.New("光线太暗!")
|
||||
}
|
||||
//5、人脸是否完整 1完整 0不完整
|
||||
reliabilitySix := dataInfo.Quality.Completeness
|
||||
if reliabilitySix != 1 {
|
||||
err = errors.New("请确定人脸在图框内!")
|
||||
}
|
||||
return
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// ComparisonRep 人脸检测返回数据
|
||||
type ComparisonRep struct {
|
||||
ErrorCode int `json:"error_code"`
|
||||
ErrorMsg string `json:"error_msg"`
|
||||
LogId int `json:"log_id"`
|
||||
Timestamp int `json:"timestamp"`
|
||||
Cached int `json:"cached"`
|
||||
Result struct {
|
||||
Score float64 `json:"score"` //人脸相似度得分,推荐阈值80分
|
||||
FaceList []struct {
|
||||
FaceToken string `json:"face_token"`
|
||||
} `json:"face_list"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
func Comparison(arrObject []*HumanFaceReq) (score float64, err error) {
|
||||
//1、请求地址+token
|
||||
url := "https://aip.baidubce.com/rest/2.0/face/v3/match?access_token=" + redisCacheStr()
|
||||
//2、请求参数转字符串json
|
||||
marshal, err := json.Marshal(arrObject)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
payload := strings.NewReader(string(marshal))
|
||||
//3、准备post请求
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("POST", url, payload)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
//4、设置请求头
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
//5、发送请求关闭连接
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
//6、数据结果
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
//7、解析数据
|
||||
var result ComparisonRep
|
||||
err = json.Unmarshal(body, &result)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to parse JSON data:", err)
|
||||
return
|
||||
}
|
||||
score = result.Result.Score
|
||||
return score, err
|
||||
}
|
||||
|
||||
/*
|
||||
AccessTokenResponse 获取Access_token,有效期(秒为单位,有效期30天);
|
||||
*/
|
||||
type AccessTokenResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
||||
func AccessTokenFunc() (str string) {
|
||||
url := "https://aip.baidubce.com/oauth/2.0/token?client_id=" + clientId + "&client_secret=" + clientSecret + "&grant_type=client_credentials"
|
||||
payload := strings.NewReader(``)
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("POST", url, payload)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("Accept", "application/json")
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
var tokenResponse AccessTokenResponse
|
||||
json.Unmarshal(body, &tokenResponse)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
return tokenResponse.AccessToken
|
||||
}
|
||||
|
||||
// 缓存捞取token,如果没得就重新获取token
|
||||
func redisCacheStr() (atStr string) {
|
||||
atStr = ""
|
||||
ctx := gctx.New()
|
||||
prefix := g.Cfg().MustGet(ctx, "system.cache.prefix").String()
|
||||
gfCache := cache.New(prefix)
|
||||
at := commonService.Cache().Get(ctx, gfCache.CachePrefix+cacheRedis)
|
||||
if at == nil || at.String() == "" {
|
||||
atStr = AccessTokenFunc()
|
||||
//存储到redis,时间为29天
|
||||
commonService.Cache().Set(ctx, cacheRedis, atStr, time.Hour*24*29)
|
||||
} else {
|
||||
atStr = at.String()
|
||||
}
|
||||
//atStr = "24.c8814d3fc7961820f0e23ee9d80cf96c.2592000.1696671067.282335-38777216"
|
||||
return atStr
|
||||
}
|
104
api/v1/common/coryCommon/base64ToImg.go
Normal file
104
api/v1/common/coryCommon/base64ToImg.go
Normal file
@ -0,0 +1,104 @@
|
||||
package coryCommon
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Base64ToImgFunc 将base64转成图片保存在本地
|
||||
func Base64ToImgFunc(base64Str string, numTyoe string, cdPath string) (outputPath string, err error) {
|
||||
// 获取当前时间+随机数得到文件名
|
||||
currentTime := time.Now()
|
||||
timestamp := currentTime.UnixNano() / int64(time.Millisecond)
|
||||
randomNum := rand.Intn(1000)
|
||||
uniqueFileName := fmt.Sprintf("%d_%d", timestamp, randomNum)
|
||||
// 用户指定的本地文件路径
|
||||
//ynr := Ynr(Portrait + "/")
|
||||
ynr := Ynr(cdPath + "/")
|
||||
path := ynr + uniqueFileName + ".png"
|
||||
path = filepath.ToSlash(path)
|
||||
// Base64编码的图像字符串
|
||||
b64 := "data:image/png;"
|
||||
base64Image := ""
|
||||
if strings.Contains(base64Str, "base64,") { // 判断是否有【base64,】如果有就替换
|
||||
base64Image = strings.Replace(base64Str, base64Str[:strings.Index(base64Str, "base64,")], b64, 1)
|
||||
} else {
|
||||
base64Image = b64 + "base64," + base64Str
|
||||
}
|
||||
|
||||
// 调用函数将Base64图像保存到指定路径
|
||||
err = SaveBase64ImageToFile(base64Image, path)
|
||||
if err != nil {
|
||||
return
|
||||
} else {
|
||||
if numTyoe == "1" {
|
||||
outputPath = strings.Replace(path, "resource/public", "file", 1)
|
||||
return
|
||||
} else if numTyoe == "2" {
|
||||
outputPath = strings.Replace(path, "resource/public", "wxfile", 1)
|
||||
return
|
||||
} else {
|
||||
err = errors.New("第二参数只能为1 or 2!")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Base64ToFileFunc(base64Str string, filePath string, suffix string, numTyoe string) (outputPath string, err error) {
|
||||
|
||||
// 获取当前时间+随机数得到文件名
|
||||
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
uniqueFileName := fmt.Sprintf("%d_%d", timestamp, rand.Intn(1000))
|
||||
// 用户指定的本地文件路径,filePath路径最后必须是/
|
||||
path := filepath.ToSlash(filePath + uniqueFileName + suffix)
|
||||
// 调用函数将Base64图像保存到指定路径
|
||||
err = SaveBase64ImageToFile(base64Str, path)
|
||||
if err != nil {
|
||||
return
|
||||
} else {
|
||||
if numTyoe == "1" {
|
||||
outputPath = strings.Replace(path, "resource/public", "file", 1)
|
||||
return
|
||||
} else if numTyoe == "2" {
|
||||
outputPath = strings.Replace(path, "resource/public", "wxfile", 1)
|
||||
return
|
||||
} else {
|
||||
err = errors.New("第二参数只能为1 or 2!")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SaveBase64ImageToFile 将Base64编码的图像保存到指定的本地文件路径
|
||||
func SaveBase64ImageToFile(base64Image string, outputPath string) error {
|
||||
if len(outputPath) > 0 && outputPath[0] == '/' {
|
||||
outputPath = outputPath[1:]
|
||||
}
|
||||
getwd, _ := os.Getwd()
|
||||
outputPath = gfile.Join(getwd, outputPath)
|
||||
outputPath = strings.ReplaceAll(outputPath, "\\", "/")
|
||||
// 1. 解码Base64字符串
|
||||
parts := strings.Split(base64Image, ",")
|
||||
if len(parts) != 2 {
|
||||
return errors.New("Base64字符串格式不正确!")
|
||||
}
|
||||
data, err := base64.StdEncoding.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
return errors.New("转码错误!")
|
||||
}
|
||||
|
||||
// 2. 将字节数组保存为图像文件
|
||||
err = ioutil.WriteFile(outputPath, data, 0644)
|
||||
if err != nil {
|
||||
return errors.New("报错图像失败!")
|
||||
}
|
||||
return nil
|
||||
}
|
107
api/v1/common/coryCommon/basics.go
Normal file
107
api/v1/common/coryCommon/basics.go
Normal file
@ -0,0 +1,107 @@
|
||||
package coryCommon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func New() *coryCom {
|
||||
return &coryCom{}
|
||||
}
|
||||
|
||||
type coryCom struct{}
|
||||
|
||||
// CreateByOrUpdateBy 专门用来反射结构体中的创建人和更新人,然后返回回去,避免重复造轮子去写重复代码
|
||||
/**
|
||||
*使用实例代码
|
||||
* by := coryCommon.New().CreateByOrUpdateBy(ctx, res)
|
||||
* infoRes := by.(model.BusCompanyInfoRes)
|
||||
* res = &infoRes
|
||||
*
|
||||
*其中 updateByFieldVal.Set(reflect.ValueOf(updateByValue.Interface().(*gvar.Var).String())) 必须类型一致
|
||||
*/
|
||||
func (s *coryCom) CreateByOrUpdateBy(ctx context.Context, data interface{}) (dataRes interface{}) {
|
||||
// 使用反射获取 data 的值和类型信息
|
||||
val := reflect.ValueOf(data)
|
||||
typ := reflect.TypeOf(data)
|
||||
|
||||
// 判断 data 是否为指针类型,并获取实际值
|
||||
if val.Kind() == reflect.Ptr {
|
||||
val = val.Elem()
|
||||
typ = typ.Elem()
|
||||
}
|
||||
|
||||
flagCreate := true
|
||||
flagUpdate := true
|
||||
// 获取 createBy 字段在结构体中的索引
|
||||
createByField, ok := typ.FieldByName("CreateBy")
|
||||
if !ok {
|
||||
flagCreate = false
|
||||
//return data // 如果结构体中不存在 createBy 字段,则直接返回原始值
|
||||
createByField2, ok2 := typ.FieldByName("CreatedBy")
|
||||
if ok2 {
|
||||
createByField = createByField2
|
||||
flagCreate = true
|
||||
}
|
||||
}
|
||||
updateByField, ok := typ.FieldByName("UpdateBy")
|
||||
if !ok {
|
||||
flagUpdate = false
|
||||
//return data // 如果结构体中不存在 createBy 字段,则直接返回原始值
|
||||
updateByField2, ok2 := typ.FieldByName("UpdatedBy")
|
||||
if ok2 {
|
||||
updateByField = updateByField2
|
||||
flagCreate = true
|
||||
}
|
||||
}
|
||||
if flagCreate {
|
||||
// 判断 createBy 字段的类型是否为 string
|
||||
if createByField.Type.Kind() != reflect.String {
|
||||
// 如果 createBy 字段的类型不是 string,请根据需要进行相应的处理或返回错误
|
||||
// 此处假设 createBy 必须为 string 类型,如果不是则返回错误
|
||||
return errors.New("createBy字段类型不匹配")
|
||||
}
|
||||
// 获取原始的 createBy 字段的值并获取创建人
|
||||
createId := val.FieldByIndex(createByField.Index).String()
|
||||
ve := SelectByString(ctx, IsNumeric(createId), createId)
|
||||
// 设置 createBy 字段的值为指定参数
|
||||
createByFieldVal := val.FieldByIndex(createByField.Index)
|
||||
createByFieldVal.SetString(ve)
|
||||
}
|
||||
if flagUpdate {
|
||||
// 判断 updateBy 字段的类型是否为 string
|
||||
if updateByField.Type.Kind() != reflect.String {
|
||||
// 如果 createBy 字段的类型不是 string,请根据需要进行相应的处理或返回错误
|
||||
// 此处假设 createBy 必须为 string 类型,如果不是则返回错误
|
||||
return errors.New("updateBy字段类型不匹配")
|
||||
}
|
||||
// 获取原始的 createBy 字段的值并获取创建人
|
||||
updateId := val.FieldByIndex(updateByField.Index).String()
|
||||
ve := SelectByString(ctx, IsNumeric(updateId), updateId)
|
||||
// 设置 createBy 字段的值为指定参数
|
||||
updateByFieldVal := val.FieldByIndex(updateByField.Index)
|
||||
updateByFieldVal.SetString(ve)
|
||||
}
|
||||
// 返回修改后的结构体
|
||||
return val.Interface()
|
||||
}
|
||||
|
||||
func IsNumeric(str string) bool {
|
||||
_, err := strconv.ParseFloat(str, 64)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// SelectByString 根据字符串查询到具体人对应的名称
|
||||
func SelectByString(ctx context.Context, flag bool, str string) (ve string) {
|
||||
if flag {
|
||||
value, _ := dao.SysUser.Ctx(ctx).Fields("user_nickname").Where("id", str).Value()
|
||||
ve = value.String()
|
||||
} else {
|
||||
value, _ := dao.BusConstructionUser.Ctx(ctx).Fields("user_name").Where("openid", str).Value()
|
||||
ve = value.String()
|
||||
}
|
||||
return
|
||||
}
|
17
api/v1/common/coryCommon/camera/cameraEntity.go
Normal file
17
api/v1/common/coryCommon/camera/cameraEntity.go
Normal file
@ -0,0 +1,17 @@
|
||||
package camera
|
||||
|
||||
type LoginCamera struct {
|
||||
URLToken string `json:"URLToken"`
|
||||
TokenTimeout int64 `json:"TokenTimeout"`
|
||||
}
|
||||
|
||||
type ChannelSnapReq struct {
|
||||
Serial string `json:"serial" dc:"设备编号"`
|
||||
Stime int64 `json:"stime" dc:"快照时间, 从录像截取指定时间的历史快照, now 表示取实时快照, 即抓图允许值: now, YYYYMMDDHHmmss"`
|
||||
Format int64 `json:"format" dc:"stime 快照格式 允许值: jpeg, png"`
|
||||
}
|
||||
type ChannelSnapRes struct {
|
||||
Serial string `json:"serial" dc:"设备编号"`
|
||||
Stime int64 `json:"stime" dc:"快照时间, 从录像截取指定时间的历史快照, now 表示取实时快照, 即抓图允许值: now, YYYYMMDDHHmmss"`
|
||||
Format int64 `json:"format" dc:"stime 快照格式 允许值: jpeg, png"`
|
||||
}
|
303
api/v1/common/coryCommon/camera/cameraUtil.go
Normal file
303
api/v1/common/coryCommon/camera/cameraUtil.go
Normal file
@ -0,0 +1,303 @@
|
||||
package camera
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/gogf/gf/v2/crypto/gmd5"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/tiger1103/gfast-cache/cache"
|
||||
"github.com/tiger1103/gfast/v3/api/v1/common/coryCommon"
|
||||
"github.com/tiger1103/gfast/v3/api/v1/system"
|
||||
commonService "github.com/tiger1103/gfast/v3/internal/app/common/service"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/model"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/model/do"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/service"
|
||||
"github.com/tiger1103/gfast/v3/third/arithmetic/SpartaApi"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LoginCameraFunc 登录获取 身份凭证
|
||||
func LoginCameraFunc(ctx context.Context) (token string) {
|
||||
api, _ := g.Cfg().Get(ctx, "LiveGBS.safety.api")
|
||||
acc, _ := g.Cfg().Get(ctx, "LiveGBS.safety.acc")
|
||||
pas, _ := g.Cfg().Get(ctx, "LiveGBS.safety.pas")
|
||||
key := "loginCamera"
|
||||
//从缓存捞取key
|
||||
prefix := g.Cfg().MustGet(ctx, "system.cache.prefix").String()
|
||||
gfCache := cache.New(prefix)
|
||||
get := commonService.Cache().Get(ctx, gfCache.CachePrefix+key)
|
||||
if get != nil && get.String() != "" {
|
||||
token = get.String()
|
||||
return token
|
||||
} else {
|
||||
account := acc.String()
|
||||
password := gmd5.MustEncryptString(pas.String())
|
||||
uri := api.String() + "api/v1/login?username=" + account + "&password=" + password + "&url_token_only=true"
|
||||
response, err := g.Client().Get(gctx.New(), uri)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var lc *LoginCamera
|
||||
err = json.Unmarshal([]byte(response.ReadAllString()), &lc)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
//将token存储到redis中,tiken默认时间为秒,实际计算为7天,(这里少100秒,防止token过期还存在redis中)
|
||||
commonService.Cache().Set(ctx, key, lc.URLToken, time.Duration(lc.TokenTimeout-100)*time.Second)
|
||||
token = lc.URLToken
|
||||
return token
|
||||
}
|
||||
}
|
||||
|
||||
var Rdb *redis.Client
|
||||
|
||||
// camera.DYFunc()
|
||||
// 发布小程序需要关闭 camera.DYFunc()
|
||||
// DYFunc 连接redis
|
||||
func DYFunc() {
|
||||
ctx := gctx.New()
|
||||
err := g.Try(ctx, func(ctx context.Context) {
|
||||
fmt.Println("redis订阅已开启")
|
||||
// 创建第一个 Redis 连接
|
||||
address, _ := g.Cfg().Get(ctx, "LiveGBS.redis.address")
|
||||
password, _ := g.Cfg().Get(ctx, "LiveGBS.redis.password")
|
||||
// 创建一个 Redis 客户端连接
|
||||
options := &redis.Options{
|
||||
Addr: address.String(), // 替换为你的 Redis 地址和端口
|
||||
Password: password.String(), // 替换为你的 Redis 密码
|
||||
DB: 1, // 替换为你的 Redis 数据库索引
|
||||
}
|
||||
Rdb = redis.NewClient(options)
|
||||
|
||||
// 创建一个订阅频道
|
||||
channelName := "device" // 替换为你要订阅的频道名
|
||||
pubsub := Rdb.Subscribe(context.Background(), channelName)
|
||||
|
||||
// 处理订阅消息的 Goroutine
|
||||
go func() {
|
||||
for {
|
||||
msg, err := pubsub.ReceiveMessage(context.Background())
|
||||
if err != nil {
|
||||
log.Println("Error receiving message:", err)
|
||||
time.Sleep(time.Second) // 出错时等待一段时间后重试
|
||||
continue
|
||||
}
|
||||
var strData = msg.Payload
|
||||
log.Println("Received message:", strData)
|
||||
//1、获取到数据,然后查看是否':'拼接(先不管拼接),不是':'就直接操作数据库
|
||||
split := strings.Split(strData, ":")
|
||||
if len(split) < 2 {
|
||||
onOrOff := strings.Split(split[0], " ")[1]
|
||||
if strings.EqualFold(onOrOff, "on") {
|
||||
onOrOff = "1"
|
||||
} else {
|
||||
onOrOff = "0"
|
||||
}
|
||||
_, err = dao.QianqiCamera.Ctx(ctx).Where("code", split[0]).Update(g.Map{"country_state": onOrOff})
|
||||
//if err != nil {
|
||||
// id, _ := result.LastInsertId()
|
||||
// if id > 0 {
|
||||
// dao.BusCameraChannel.Ctx(ctx).Where("country_id", id).Update(g.Map{"status": onOrOff})
|
||||
// }
|
||||
//}
|
||||
}
|
||||
time.Sleep(time.Second * 3)
|
||||
}
|
||||
}()
|
||||
})
|
||||
fmt.Println("订阅错误问题:", err)
|
||||
}
|
||||
|
||||
// ChannelSnapFunc 快照
|
||||
func ChannelSnapFunc(ctx context.Context, id int64, serial string, code string, projectId int64) (err error) {
|
||||
//获取预制位,前提是 PresetEnable == true
|
||||
pb, err := PeeeresettingBitFunc(gctx.New(), serial, code)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, pi := range pb.PresetItemList {
|
||||
if pi.PresetEnable == true {
|
||||
//0、预置位
|
||||
err = PresettingBitFunc(ctx, serial, code, "goto", pi.PresetID, "")
|
||||
//1、请求接口得到二进制数据
|
||||
suffix := "png"
|
||||
api, _ := g.Cfg().Get(ctx, "LiveGBS.safety.api")
|
||||
tokens := LoginCameraFunc(ctx)
|
||||
uri := api.String() + "api/v1/device/channelsnap?serial=" + serial + "&code=" + code + "&stime=now&format=" + suffix + "&token=" + tokens
|
||||
response, err := g.Client().Get(gctx.New(), uri)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//2、生成时间文件夹;生成文件名;然后同一斜杠得到完成路径
|
||||
ht := coryCommon.Helmet
|
||||
str := coryCommon.Ynr(ht)
|
||||
fn := coryCommon.FileName("helmet")
|
||||
dir, err := os.Getwd()
|
||||
str = dir + "/" + str + fn + "." + suffix
|
||||
str = filepath.ToSlash(str)
|
||||
//3、创建一个文件来保存图片数据;将响应体中的图片数据复制到文件中
|
||||
file, err := os.Create(str)
|
||||
if err != nil {
|
||||
err = errors.New("创建文件出错")
|
||||
return err
|
||||
}
|
||||
// 创建一个缓冲区,用于读取数据(从 body 中读取数据)
|
||||
var re = response
|
||||
var by = re.Body
|
||||
|
||||
lenStr, err := io.Copy(file, by)
|
||||
if err != nil {
|
||||
err = errors.New("复制图片数据到文件出错")
|
||||
return err
|
||||
} else {
|
||||
file.Close()
|
||||
}
|
||||
if lenStr < 10240 {
|
||||
file.Close() // 关闭文件
|
||||
os.Remove(str)
|
||||
return err
|
||||
}
|
||||
//4、《斯巴达》调用算法接口圈出未带安全帽的人
|
||||
req := SpartaApi.RecognizeReq{
|
||||
CapUrl: str,
|
||||
//RecType: "head smoke belt waste excavator Roller Truck_crane Loader Submersible_drilling_rig Sprinkler Truck_mounted_crane Truck",
|
||||
RecType: "head smoke",
|
||||
Async: "False",
|
||||
CallBackUrl: "",
|
||||
AreaHigh: "",
|
||||
}
|
||||
mp, flag, num, err := SpartaApi.CommonAlgorithmFunc(ctx, &req)
|
||||
if err != nil {
|
||||
os.Remove(str)
|
||||
return err
|
||||
}
|
||||
////4、《ys7》调用算法接口圈出未带安全帽的人
|
||||
//flag, num, err := ys7.InitYs7(str)
|
||||
//if err != nil {
|
||||
// os.Remove(str)
|
||||
// return err
|
||||
//}
|
||||
//5、flag为true表示有违规数据
|
||||
if flag {
|
||||
// 使用range遍历map
|
||||
mpkStr := ""
|
||||
mpvStr := ""
|
||||
for key, value := range mp {
|
||||
mpkStr = mpkStr + key + ","
|
||||
mpvStr = mpvStr + value + "、"
|
||||
}
|
||||
mpkStr = strings.TrimRight(mpkStr, ",")
|
||||
mpvStr = strings.TrimRight(mpvStr, "、")
|
||||
//5、生成数据存储到数据表中(识别记录)
|
||||
path := strings.ReplaceAll(ht, "/resource/public/", coryCommon.GetWd)
|
||||
currentTime := time.Now()
|
||||
dateString := currentTime.Format("2006-01-02")
|
||||
path = path + dateString + "/" + fn + "." + suffix
|
||||
addReq := system.BusTourAddReq{
|
||||
ProjectId: projectId,
|
||||
TourCategory: "2",
|
||||
TourType: "1",
|
||||
Picture: path,
|
||||
Describe: mpvStr,
|
||||
Num: num,
|
||||
TableName: dao.QianqiCamera.Table(),
|
||||
TableId: id,
|
||||
}
|
||||
service.BusTour().Add(ctx, &addReq)
|
||||
//6、生成数据存储到违规记录里面
|
||||
var bvl model.BusViolationLevelInfoRes
|
||||
err = dao.BusViolationLevel.Ctx(ctx).Where("tour_type", mpkStr).Fields("id,grade").Scan(&bvl)
|
||||
recordAddReq := do.BusViolationRecord{
|
||||
ProjectId: projectId,
|
||||
LevelId: bvl.Id,
|
||||
Level: bvl.Grade,
|
||||
TourType: mpkStr,
|
||||
DataSource: "camera",
|
||||
//Picture: path,
|
||||
//WxOrPc: "1",
|
||||
}
|
||||
dao.BusViolationRecord.Ctx(ctx).Insert(recordAddReq)
|
||||
}
|
||||
time.Sleep(20 * time.Second)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
/*
|
||||
PresettingBitFunc 设备控制-预置位控制
|
||||
serial: 国标号
|
||||
code: 通道号
|
||||
command: 指令
|
||||
preset: 预置位编号
|
||||
name: 预置位名称
|
||||
*/
|
||||
func PresettingBitFunc(ctx context.Context, serial string, code string, command string, preset int, name string) (err error) {
|
||||
if name != "" && (name == "set" || name == "goto" || name == "remove") {
|
||||
err = errors.New("控制指令允许值: set, goto, remove")
|
||||
return err
|
||||
}
|
||||
if !(preset > 0 && preset <= 255) {
|
||||
err = errors.New("预置位编号范围为:1~255")
|
||||
return err
|
||||
}
|
||||
tokens := LoginCameraFunc(ctx)
|
||||
api, _ := g.Cfg().Get(ctx, "LiveGBS.safety.api")
|
||||
uri := api.String() + "api/v1/control/preset?serial=" + serial +
|
||||
"&code=" + code +
|
||||
"&command=" + command +
|
||||
"&preset=" + strconv.Itoa(preset) +
|
||||
"&token=" + tokens
|
||||
if name != "" {
|
||||
uri = uri + "&name=" + name
|
||||
}
|
||||
g.Client().Get(gctx.New(), uri)
|
||||
return err
|
||||
}
|
||||
|
||||
func PeeeresettingBitFunc(ctx context.Context, serial string, code string) (pb *PeeeresettingBitEntity, err error) {
|
||||
tokens := LoginCameraFunc(ctx)
|
||||
api, _ := g.Cfg().Get(ctx, "LiveGBS.safety.api")
|
||||
uri := api.String() + "api/v1/device/fetchpreset?serial=" + serial +
|
||||
"&code=" + code +
|
||||
"&token=" + tokens +
|
||||
"&fill=false"
|
||||
ft, errft := g.Client().Get(gctx.New(), uri)
|
||||
var str = ft.ReadAllString()
|
||||
err = json.Unmarshal([]byte(str), &pb)
|
||||
if err != nil {
|
||||
if strings.Contains(str, "offline") {
|
||||
err = errors.New("当前设备不在线!")
|
||||
} else if strings.Contains(str, "not found") {
|
||||
err = errors.New("当前设备沒找到!")
|
||||
} else {
|
||||
err = errft
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type PeeeresettingBitEntity struct {
|
||||
DeviceID string `json:"DeviceID"`
|
||||
Result string `json:"Result"`
|
||||
SumNum int `json:"SumNum"`
|
||||
PresetItemList []*PeeeresettingBitListEntity `json:"PresetItemList"`
|
||||
}
|
||||
|
||||
type PeeeresettingBitListEntity struct {
|
||||
PresetID int `json:"PresetID"`
|
||||
PresetName string `json:"PresetName"`
|
||||
PresetEnable bool `json:"PresetEnable"`
|
||||
}
|
58
api/v1/common/coryCommon/constant.go
Normal file
58
api/v1/common/coryCommon/constant.go
Normal file
@ -0,0 +1,58 @@
|
||||
package coryCommon
|
||||
|
||||
const Global = "xny.yj-3d.com"
|
||||
|
||||
var GlobalPath = "http://" + Global + ":7363"
|
||||
var GlobalFile = "/file"
|
||||
|
||||
// ==========================错误类型==========================
|
||||
|
||||
// SYSERRHINT 系统错误
|
||||
var syserrhint = "系统错误,请联系管理员!"
|
||||
|
||||
// ImportFile 系统错误
|
||||
var ImportFile = "导入文件有误!"
|
||||
|
||||
// ===========================文件===========================
|
||||
|
||||
var GetWd = "/file/" // GetWd 静态文件路径
|
||||
var LargeFileClt = "/resource/public/clt/" // LargeFileClt 文件上传地址-【倾斜模型/光伏板】
|
||||
var LargeFileShp = "/resource/public/shp/" // LargeFileShp 文件上传地址-shp 红线
|
||||
var ProjectSplitTable = "/resource/public/projectSplitTable/" // ProjectSplitTable 项目划分表临时文件
|
||||
var Temporary = "/resource/public/temporary" // Temporary 临时文件存放地址
|
||||
var LargeFilePrivacy = "/resource/public/privacy/" // LargeFilePrivacy 身份证银行卡的存储位置
|
||||
var Portrait = "/resource/public/portrait" // Portrait 人像 - 人脸存储的地方(人脸对比的人脸和打卡的人脸都存放在此处)
|
||||
var Helmet = "/resource/public/upload_file/" // Helmet 公共位置
|
||||
var GisModelLib = "/resource/public/mx/" // GisModelLib 模型库
|
||||
var Uav = "/resource/public/uav/" // uav 无人机资源
|
||||
var UavMerge = "resource/public/tif" // uav 无人机资源大图合并资源
|
||||
/*
|
||||
网盘位置
|
||||
*/
|
||||
var Template = "/resource/public/masterMask"
|
||||
var Template2 = "/resource/public/masterMask/dataFolder" //(工程资料)资料文件保存的位置
|
||||
var Template3 = "/resource/public/masterMask/sourceData" //(工程资料)源数据文件保存的位置
|
||||
var Report = "/resource/public/networkDisk/report" //(网盘)科研及专题报告
|
||||
var ProductionDrawing = "/resource/public/networkDisk/productionDrawing" //(网盘)施工图
|
||||
var Completion = "/resource/public/networkDisk/completion" //(网盘)竣工图
|
||||
var QualityMeeting = "/resource/public/networkDisk/qualityMeeting" //(网盘)质量会议
|
||||
var SafetyMeeting = "/resource/public/networkDisk/safetyMeeting" //(网盘)安全会议
|
||||
|
||||
// ==========================文件后缀==========================
|
||||
|
||||
// PictureSuffix 常见图片后缀
|
||||
var PictureSuffix = "JPEG|JPG|HEIF|PNG"
|
||||
|
||||
// ==========================项目常量==========================
|
||||
|
||||
// gispath gis文件
|
||||
var gispath = "gisfile/"
|
||||
|
||||
// commonpath common文件
|
||||
var commonpath = "commonfile/"
|
||||
|
||||
// addFilePath GIS路径
|
||||
var addFilePath = "/yjearth4.0/static/source/"
|
||||
|
||||
// suffix 文件后缀名称
|
||||
var suffix = "clt,pdf,zip"
|
277
api/v1/common/coryCommon/coordinateTransform.go
Normal file
277
api/v1/common/coryCommon/coordinateTransform.go
Normal file
@ -0,0 +1,277 @@
|
||||
package coryCommon
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// WGS84坐标系:即地球坐标系,国际上通用的坐标系。
|
||||
// GCJ02坐标系:即火星坐标系,WGS84坐标系经加密后的坐标系。Google Maps,高德在用。
|
||||
// BD09坐标系:即百度坐标系,GCJ02坐标系经加密后的坐标系。
|
||||
|
||||
const (
|
||||
X_PI = math.Pi * 3000.0 / 180.0
|
||||
OFFSET = 0.00669342162296594323
|
||||
AXIS = 6378245.0
|
||||
)
|
||||
|
||||
// BD09toGCJ02 百度坐标系->火星坐标系
|
||||
func BD09toGCJ02(lon, lat float64) (float64, float64) {
|
||||
x := lon - 0.0065
|
||||
y := lat - 0.006
|
||||
|
||||
z := math.Sqrt(x*x+y*y) - 0.00002*math.Sin(y*X_PI)
|
||||
theta := math.Atan2(y, x) - 0.000003*math.Cos(x*X_PI)
|
||||
|
||||
gLon := z * math.Cos(theta)
|
||||
gLat := z * math.Sin(theta)
|
||||
|
||||
return gLon, gLat
|
||||
}
|
||||
|
||||
// GCJ02toBD09 火星坐标系->百度坐标系
|
||||
func GCJ02toBD09(lon, lat float64) (float64, float64) {
|
||||
z := math.Sqrt(lon*lon+lat*lat) + 0.00002*math.Sin(lat*X_PI)
|
||||
theta := math.Atan2(lat, lon) + 0.000003*math.Cos(lon*X_PI)
|
||||
|
||||
bdLon := z*math.Cos(theta) + 0.0065
|
||||
bdLat := z*math.Sin(theta) + 0.006
|
||||
|
||||
return bdLon, bdLat
|
||||
}
|
||||
|
||||
// WGS84toGCJ02 WGS84坐标系->火星坐标系
|
||||
func WGS84toGCJ02(lon, lat float64) (float64, float64) {
|
||||
if isOutOFChina(lon, lat) {
|
||||
return lon, lat
|
||||
}
|
||||
|
||||
mgLon, mgLat := delta(lon, lat)
|
||||
|
||||
return mgLon, mgLat
|
||||
}
|
||||
|
||||
// GCJ02toWGS84 火星坐标系->WGS84坐标系
|
||||
func GCJ02toWGS84(lon, lat float64) (float64, float64) {
|
||||
if isOutOFChina(lon, lat) {
|
||||
return lon, lat
|
||||
}
|
||||
|
||||
mgLon, mgLat := delta(lon, lat)
|
||||
|
||||
return lon*2 - mgLon, lat*2 - mgLat
|
||||
}
|
||||
|
||||
// BD09toWGS84 百度坐标系->WGS84坐标系
|
||||
func BD09toWGS84(lon, lat float64) (float64, float64) {
|
||||
lon, lat = BD09toGCJ02(lon, lat)
|
||||
return GCJ02toWGS84(lon, lat)
|
||||
}
|
||||
|
||||
// WGS84toBD09 WGS84坐标系->百度坐标系
|
||||
func WGS84toBD09(lon, lat float64) (float64, float64) {
|
||||
lon, lat = WGS84toGCJ02(lon, lat)
|
||||
return GCJ02toBD09(lon, lat)
|
||||
}
|
||||
|
||||
func delta(lon, lat float64) (float64, float64) {
|
||||
dlat := transformlat(lon-105.0, lat-35.0)
|
||||
dlon := transformlng(lon-105.0, lat-35.0)
|
||||
|
||||
radlat := lat / 180.0 * math.Pi
|
||||
magic := math.Sin(radlat)
|
||||
magic = 1 - OFFSET*magic*magic
|
||||
sqrtmagic := math.Sqrt(magic)
|
||||
|
||||
dlat = (dlat * 180.0) / ((AXIS * (1 - OFFSET)) / (magic * sqrtmagic) * math.Pi)
|
||||
dlon = (dlon * 180.0) / (AXIS / sqrtmagic * math.Cos(radlat) * math.Pi)
|
||||
|
||||
mgLat := lat + dlat
|
||||
mgLon := lon + dlon
|
||||
|
||||
return mgLon, mgLat
|
||||
}
|
||||
|
||||
func transformlat(lon, lat float64) float64 {
|
||||
var ret = -100.0 + 2.0*lon + 3.0*lat + 0.2*lat*lat + 0.1*lon*lat + 0.2*math.Sqrt(math.Abs(lon))
|
||||
ret += (20.0*math.Sin(6.0*lon*math.Pi) + 20.0*math.Sin(2.0*lon*math.Pi)) * 2.0 / 3.0
|
||||
ret += (20.0*math.Sin(lat*math.Pi) + 40.0*math.Sin(lat/3.0*math.Pi)) * 2.0 / 3.0
|
||||
ret += (160.0*math.Sin(lat/12.0*math.Pi) + 320*math.Sin(lat*math.Pi/30.0)) * 2.0 / 3.0
|
||||
return ret
|
||||
}
|
||||
|
||||
func transformlng(lon, lat float64) float64 {
|
||||
var ret = 300.0 + lon + 2.0*lat + 0.1*lon*lon + 0.1*lon*lat + 0.1*math.Sqrt(math.Abs(lon))
|
||||
ret += (20.0*math.Sin(6.0*lon*math.Pi) + 20.0*math.Sin(2.0*lon*math.Pi)) * 2.0 / 3.0
|
||||
ret += (20.0*math.Sin(lon*math.Pi) + 40.0*math.Sin(lon/3.0*math.Pi)) * 2.0 / 3.0
|
||||
ret += (150.0*math.Sin(lon/12.0*math.Pi) + 300.0*math.Sin(lon/30.0*math.Pi)) * 2.0 / 3.0
|
||||
return ret
|
||||
}
|
||||
|
||||
func isOutOFChina(lon, lat float64) bool {
|
||||
return !(lon > 73.66 && lon < 135.05 && lat > 3.86 && lat < 53.55)
|
||||
}
|
||||
|
||||
/*
|
||||
===========================================================================================
|
||||
===========================================================================================
|
||||
===========================================================================================
|
||||
===========================================================================================
|
||||
===========================================================================================
|
||||
===========================================================================================
|
||||
===========================================================================================
|
||||
*/
|
||||
|
||||
// GPSUtil is a utility class for GPS calculations.
|
||||
// 小写方法是私有方法,大写方法是公有方法 可根据需要调整
|
||||
|
||||
func News() *GPSUtil {
|
||||
return &GPSUtil{}
|
||||
}
|
||||
|
||||
type GPSUtil struct {
|
||||
}
|
||||
|
||||
const (
|
||||
pi = 3.1415926535897932384626 // 圆周率
|
||||
x_pi = 3.14159265358979324 * 3000.0 / 180.0 // 圆周率对应的经纬度偏移
|
||||
a = 6378245.0 // 长半轴
|
||||
ee = 0.00669342162296594323 // 扁率
|
||||
)
|
||||
|
||||
func (receiver *GPSUtil) transformLat(x, y float64) float64 {
|
||||
ret := -100.0 + 2.0*x + 3.0*y + 0.2*y*y + 0.1*x*y + 0.2*math.Sqrt(math.Abs(x))
|
||||
ret += (20.0*math.Sin(6.0*x*pi) + 20.0*math.Sin(2.0*x*pi)) * 2.0 / 3.0
|
||||
ret += (20.0*math.Sin(y*pi) + 40.0*math.Sin(y/3.0*pi)) * 2.0 / 3.0
|
||||
ret += (160.0*math.Sin(y/12.0*pi) + 320*math.Sin(y*pi/30.0)) * 2.0 / 3.0
|
||||
return ret
|
||||
}
|
||||
|
||||
func (receiver *GPSUtil) transformlng(x, y float64) float64 {
|
||||
ret := 300.0 + x + 2.0*y + 0.1*x*x + 0.1*x*y + 0.1*math.Sqrt(math.Abs(x))
|
||||
ret += (20.0*math.Sin(6.0*x*pi) + 20.0*math.Sin(2.0*x*pi)) * 2.0 / 3.0
|
||||
ret += (20.0*math.Sin(x*pi) + 40.0*math.Sin(x/3.0*pi)) * 2.0 / 3.0
|
||||
ret += (150.0*math.Sin(x/12.0*pi) + 300.0*math.Sin(x/30.0*pi)) * 2.0 / 3.0
|
||||
return ret
|
||||
}
|
||||
|
||||
func (receiver *GPSUtil) outOfChina(lat, lng float64) bool {
|
||||
if lng < 72.004 || lng > 137.8347 {
|
||||
return true
|
||||
}
|
||||
if lat < 0.8293 || lat > 55.8271 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (receiver *GPSUtil) transform(lat, lng float64) []float64 {
|
||||
if receiver.outOfChina(lat, lng) {
|
||||
return []float64{lat, lng}
|
||||
}
|
||||
dLat := receiver.transformLat(lng-105.0, lat-35.0)
|
||||
dlng := receiver.transformlng(lng-105.0, lat-35.0)
|
||||
radLat := lat / 180.0 * pi
|
||||
magic := math.Sin(radLat)
|
||||
magic = 1 - ee*magic*magic
|
||||
SqrtMagic := math.Sqrt(magic)
|
||||
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * SqrtMagic) * pi)
|
||||
dlng = (dlng * 180.0) / (a / SqrtMagic * math.Cos(radLat) * pi)
|
||||
mgLat := lat + dLat
|
||||
mglng := lng + dlng
|
||||
return []float64{mgLat, mglng}
|
||||
}
|
||||
|
||||
// WGS84_To_Gcj02 84 to 火星坐标系 (GCJ-02) World Geodetic System ==> Mars Geodetic System
|
||||
// @param lat
|
||||
// @param lng
|
||||
// @return
|
||||
func (receiver *GPSUtil) WGS84_To_Gcj02(lat, lng float64) []float64 {
|
||||
if receiver.outOfChina(lat, lng) {
|
||||
return []float64{lat, lng}
|
||||
}
|
||||
dLat := receiver.transformLat(lng-105.0, lat-35.0)
|
||||
dlng := receiver.transformlng(lng-105.0, lat-35.0)
|
||||
radLat := lat / 180.0 * pi
|
||||
magic := math.Sin(radLat)
|
||||
magic = 1 - ee*magic*magic
|
||||
SqrtMagic := math.Sqrt(magic)
|
||||
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * SqrtMagic) * pi)
|
||||
dlng = (dlng * 180.0) / (a / SqrtMagic * math.Cos(radLat) * pi)
|
||||
mgLat := lat + dLat
|
||||
mglng := lng + dlng
|
||||
return []float64{mgLat, mglng}
|
||||
}
|
||||
|
||||
// GCJ02_To_WGS84
|
||||
// 火星坐标系 (GCJ-02) to WGS84
|
||||
// @param lng
|
||||
// @param lat
|
||||
// @return
|
||||
func (receiver *GPSUtil) GCJ02_To_WGS84(lng, lat float64) []float64 {
|
||||
gps := receiver.transform(lat, lng)
|
||||
lngtitude := lng*2 - gps[1]
|
||||
latitude := lat*2 - gps[0]
|
||||
return []float64{lngtitude, latitude}
|
||||
}
|
||||
|
||||
/**
|
||||
* 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换算法 将 GCJ-02 坐标转换成 BD-09 坐标
|
||||
*
|
||||
* @param lat
|
||||
* @param lng
|
||||
*/
|
||||
func (receiver *GPSUtil) gcj02_To_Bd09(lat, lng float64) []float64 {
|
||||
x := lng
|
||||
y := lat
|
||||
z := math.Sqrt(x*x+y*y) + 0.00002*math.Sin(y*x_pi)
|
||||
theta := math.Atan2(y, x) + 0.000003*math.Cos(x*x_pi)
|
||||
templng := z*math.Cos(theta) + 0.0065
|
||||
tempLat := z*math.Sin(theta) + 0.006
|
||||
gps := []float64{tempLat, templng}
|
||||
return gps
|
||||
}
|
||||
|
||||
/**
|
||||
* * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换算法 * * 将 BD-09 坐标转换成GCJ-02 坐标 * * @param
|
||||
* bd_lat * @param bd_lng * @return
|
||||
*/
|
||||
func (receiver *GPSUtil) bd09_To_Gcj02(lat, lng float64) []float64 {
|
||||
x := lng - 0.0065
|
||||
y := lat - 0.006
|
||||
z := math.Sqrt(x*x+y*y) - 0.00002*math.Sin(y*x_pi)
|
||||
theta := math.Atan2(y, x) - 0.000003*math.Cos(x*x_pi)
|
||||
templng := z * math.Cos(theta)
|
||||
tempLat := z * math.Sin(theta)
|
||||
gps := []float64{tempLat, templng}
|
||||
return gps
|
||||
}
|
||||
|
||||
/**将WGS84转为bd09
|
||||
* @param lat
|
||||
* @param lng
|
||||
* @return
|
||||
*/
|
||||
func (receiver *GPSUtil) WGS84_To_bd09(lat, lng float64) []float64 {
|
||||
gcj02 := receiver.WGS84_To_Gcj02(lat, lng)
|
||||
bd09 := receiver.gcj02_To_Bd09(gcj02[0], gcj02[1])
|
||||
return bd09
|
||||
}
|
||||
|
||||
func (receiver *GPSUtil) bd09_To_WGS84(lat, lng float64) []float64 {
|
||||
gcj02 := receiver.bd09_To_Gcj02(lat, lng)
|
||||
WGS84 := receiver.GCJ02_To_WGS84(gcj02[0], gcj02[1])
|
||||
//保留小数点后六位
|
||||
WGS84[0] = receiver.retain6(WGS84[0])
|
||||
WGS84[1] = receiver.retain6(WGS84[1])
|
||||
return WGS84
|
||||
}
|
||||
|
||||
/**保留小数点后六位
|
||||
* @param num
|
||||
* @return
|
||||
*/
|
||||
func (receiver *GPSUtil) retain6(num float64) float64 {
|
||||
value, _ := strconv.ParseFloat(strconv.FormatFloat(num, 'f', 6, 64), 64)
|
||||
return value
|
||||
}
|
329
api/v1/common/coryCommon/downloadFile.go
Normal file
329
api/v1/common/coryCommon/downloadFile.go
Normal file
@ -0,0 +1,329 @@
|
||||
package coryCommon
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/model"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CreateZipFile 生成一个压缩文件夹,然后将指定文件夹的数据(文件及文件夹)存放到压缩文件下
|
||||
func CreateZipFile(sourceDir, zipFile string) error {
|
||||
zipFileToCreate, err := os.Create(zipFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer zipFileToCreate.Close()
|
||||
|
||||
zipWriter := zip.NewWriter(zipFileToCreate)
|
||||
defer zipWriter.Close()
|
||||
|
||||
err = filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return addFileToZip(zipWriter, path, sourceDir)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addFileToZip(zipWriter *zip.Writer, filePath, baseDir string) error {
|
||||
fileToZip, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileToZip.Close()
|
||||
|
||||
info, err := fileToZip.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 获取文件相对路径
|
||||
relPath, err := filepath.Rel(baseDir, filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 替换路径分隔符确保在压缩文件中使用正斜杠
|
||||
relPath = strings.ReplaceAll(relPath, `\`, "/")
|
||||
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header.Name = relPath
|
||||
|
||||
if info.IsDir() {
|
||||
header.Name += "/"
|
||||
header.Method = zip.Store // Directory
|
||||
} else {
|
||||
header.Method = zip.Deflate // File
|
||||
}
|
||||
|
||||
writer, err := zipWriter.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
_, err = io.Copy(writer, fileToZip)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MultifileDownload 【功能:多文件下载】===【参数:relativelyTemporaryPath相对路径、filesToCopy需要放在压缩包下载的文件】
|
||||
func MultifileDownload(relativelyTemporaryPath string, filesToCopy []string) (path string, err error) {
|
||||
//网络资源下载到本地
|
||||
for i := range filesToCopy {
|
||||
url := filesToCopy[i]
|
||||
pathParts := strings.Split(url, "/")
|
||||
fileName := pathParts[len(pathParts)-1]
|
||||
filePath := filepath.ToSlash(GetCWD() + Temporary + "/" + fileName)
|
||||
err = DownloadFile(url, filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
filesToCopy[i] = filePath
|
||||
}
|
||||
|
||||
// 1、创建临时压缩包
|
||||
zipFile, zipWriter, err := createTempZip(relativelyTemporaryPath)
|
||||
if err != nil {
|
||||
fmt.Println("Error creating temp zip:", err)
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
zipWriter.Close()
|
||||
zipFile.Close()
|
||||
//暂时不删除、创建了个每月定时清除临时文件的定时器
|
||||
//for i := range filesToCopy {
|
||||
// delFile(filesToCopy[i], 0) //删除临时文件
|
||||
//}
|
||||
//go delFile(zipFile.Name(), 20) // 删除临时压缩文件,20秒后执行,防止文件还没下载完成就给删除了
|
||||
}()
|
||||
|
||||
// 2、复制文件夹到压缩包
|
||||
for _, filePath := range filesToCopy {
|
||||
err := copyFileToZip(zipWriter, filePath)
|
||||
if err != nil {
|
||||
fmt.Printf("Error copying %s to zip: %s\n", filePath, err)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
path = strings.ReplaceAll(filepath.ToSlash(zipFile.Name()), filepath.ToSlash(GetCWD())+"/resource/public", "/file") //如果服务器同步需要注意wxfile
|
||||
return
|
||||
}
|
||||
|
||||
// 创建临时压缩包,并且提供写入数据
|
||||
func createTempZip(relativelyTemporaryPath string) (*os.File, *zip.Writer, error) {
|
||||
cwd := GetCWD() + relativelyTemporaryPath
|
||||
zipFile, err := os.CreateTemp(cwd, "temp_zip_*.zip") //*自动分配
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
zipWriter := zip.NewWriter(zipFile)
|
||||
return zipFile, zipWriter, nil
|
||||
}
|
||||
|
||||
// 复制文件到压缩包
|
||||
func copyFileToZip(zipWriter *zip.Writer, filePath string) error {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
// 获取文件信息
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 创建zip文件中的文件头
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 指定文件名
|
||||
header.Name = filepath.Base(filePath)
|
||||
// 创建zip文件中的文件
|
||||
writer, err := zipWriter.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 复制文件内容到zip文件中
|
||||
_, err = io.Copy(writer, file)
|
||||
return err
|
||||
}
|
||||
|
||||
// delFile 删除文件
|
||||
func delFile(file string, second int) {
|
||||
if second > 0 {
|
||||
time.Sleep(time.Duration(second) * time.Second)
|
||||
}
|
||||
if err := os.Remove(file); err != nil {
|
||||
fmt.Println("Failed to delete temporary file:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DownloadFile URL资源下载到自定位置
|
||||
func DownloadFile(url, localPath string) error {
|
||||
// 发起HTTP GET请求
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 检查响应状态码
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("HTTP request failed with status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// 创建本地文件
|
||||
file, err := os.Create(localPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 将HTTP响应体的内容拷贝到本地文件
|
||||
_, err = io.Copy(file, resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveAllFilesInDirectory 删除指定目录下的所有文件 例子:"D:\\Cory\\go\\中煤\\zmkg-back\\resource\\public\\temporary"
|
||||
func RemoveAllFilesInDirectory(directoryPath string) error {
|
||||
// 获取指定目录下的所有文件和子目录
|
||||
files, err := filepath.Glob(filepath.Join(directoryPath, "*"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 遍历所有文件并删除
|
||||
for _, file := range files {
|
||||
if err := os.RemoveAll(file); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Deleted:", file)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFiles 获取目录下所有文件(包括文件夹中的文件)
|
||||
func GetFiles(folder string) (filesList []string) {
|
||||
files, _ := ioutil.ReadDir(folder)
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
GetFiles(folder + "/" + file.Name())
|
||||
} else {
|
||||
filesList = append(filesList, file.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetAllFile 获取目录下直属所有文件(不包括文件夹及其中的文件)
|
||||
func GetAllFile(pathname string) (s []string, err error) {
|
||||
rd, err := ioutil.ReadDir(pathname)
|
||||
if err != nil {
|
||||
fmt.Println("read dir fail:", err)
|
||||
return s, err
|
||||
}
|
||||
|
||||
for _, fi := range rd {
|
||||
if !fi.IsDir() {
|
||||
fullName := pathname + "/" + fi.Name()
|
||||
s = append(s, fullName)
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Xcopy 复制文件
|
||||
func Xcopy(source, target string) (err error) {
|
||||
// 打开源文件
|
||||
sourceFile, err := os.Open(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sourceFile.Close()
|
||||
|
||||
// 创建目标文件
|
||||
destinationFile, err := os.Create(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destinationFile.Close()
|
||||
|
||||
// 使用 io.Copy() 函数复制文件内容
|
||||
_, err = io.Copy(destinationFile, sourceFile)
|
||||
return err
|
||||
}
|
||||
|
||||
// CustomizationMultifileDownload 定制下载(安全考试专用)
|
||||
func CustomizationMultifileDownload(relativelyTemporaryPath string, mw []*model.ModelWeChatPdfWoRes) (path string, err error) {
|
||||
//1、创建文件夹
|
||||
paht := filepath.ToSlash(GetCWD() + "/" + Ynr(Temporary+"/"))
|
||||
folder := paht + FileName("aqks") //文件夹名
|
||||
folder = filepath.ToSlash(folder)
|
||||
folder = folder[0 : len(folder)-1]
|
||||
err = os.MkdirAll(folder, 0777)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
//2、网络资源下载到本地
|
||||
for i := range mw {
|
||||
url := mw[i].Path
|
||||
str := folder + "/" + mw[i].UserName + mw[i].Openid
|
||||
os.MkdirAll(str, 0777) //根据名字创建子目录
|
||||
//url看看是几个文件
|
||||
fileNum := strings.Split(url, ",")
|
||||
for j := range fileNum {
|
||||
if fileNum[j] != "" {
|
||||
//因为pdf是另外一个服务器所以需要下载,但是有的又是本地服务器,所以直接复制
|
||||
if strings.Contains(fileNum[j], "/wxfile/") {
|
||||
pathstr := g.Cfg().MustGet(gctx.New(), "cory").String() + fileNum[j]
|
||||
pathParts := strings.Split(pathstr, "/")
|
||||
filePath := str + "/" + pathParts[len(pathParts)-1]
|
||||
err = DownloadFile(pathstr, filePath) //下载网络图片
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
source := FileToFunc(fileNum[j], 2)
|
||||
pathParts := strings.Split(fileNum[j], "/")
|
||||
target := str + "/" + pathParts[len(pathParts)-1]
|
||||
err := Xcopy(source, target)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//3、压缩成压缩包zip
|
||||
path = paht + FileName("aqks") + ".zip"
|
||||
err = CreateZipFile(folder, path)
|
||||
return
|
||||
}
|
515
api/v1/common/coryCommon/excelUtil/excel.go
Normal file
515
api/v1/common/coryCommon/excelUtil/excel.go
Normal file
@ -0,0 +1,515 @@
|
||||
package excelUtil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/tiger1103/gfast/v3/api/v1/common/coryCommon"
|
||||
"github.com/tiger1103/gfast/v3/api/wxApplet/wxApplet"
|
||||
"github.com/xuri/excelize/v2"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ExcelOne 每条数据一个工作簿
|
||||
func ExcelOne(oneDateOneList []*wxApplet.PunchingCardRecordOne) (file string, filePath string, fileName string) {
|
||||
f := excelize.NewFile()
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
|
||||
for x, one := range oneDateOneList {
|
||||
itoa := strconv.Itoa(x + 1)
|
||||
// -----创建一个工作表
|
||||
_, err := f.NewSheet("Sheet" + itoa)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
// -----表数据
|
||||
//设置1、2行高度
|
||||
err = f.SetRowHeight("Sheet"+itoa, 1, 36)
|
||||
err = f.SetRowHeight("Sheet"+itoa, 2, 14)
|
||||
//1、左侧固定 ABC的1~2
|
||||
f.SetCellValue("Sheet"+itoa, "A1", "序号")
|
||||
f.MergeCell("Sheet"+itoa, "A1", "A2")
|
||||
|
||||
f.SetCellValue("Sheet"+itoa, "B1", "姓名")
|
||||
f.MergeCell("Sheet"+itoa, "B1", "B2")
|
||||
|
||||
f.SetCellValue("Sheet"+itoa, "C1", "")
|
||||
f.MergeCell("Sheet"+itoa, "C1", "C2")
|
||||
//2、左侧固定 ABC的3~4
|
||||
f.SetCellValue("Sheet"+itoa, "A3", "1")
|
||||
f.MergeCell("Sheet"+itoa, "A3", "A4")
|
||||
|
||||
f.SetCellValue("Sheet"+itoa, "B3", one.Name)
|
||||
f.MergeCell("Sheet"+itoa, "B3", "B4")
|
||||
|
||||
f.SetCellValue("Sheet"+itoa, "C3", "打卡记录")
|
||||
f.SetCellValue("Sheet"+itoa, "C4", "工时")
|
||||
|
||||
//3、中间数据---------从4开始创建列
|
||||
var num = "0" //循环生成列
|
||||
var numInt = 1 //循环列对应的编号
|
||||
var numi = 0 //循环过后需要的列
|
||||
for i := 0; i < len(one.PunchCard); i++ {
|
||||
num = getColumnName(4 + i)
|
||||
f.SetCellValue("Sheet"+itoa, num+"2", numInt) //循环列对应的编号
|
||||
f.SetCellValue("Sheet"+itoa, num+"3", one.PunchCard[i].Clock) //循环生成列 时间
|
||||
f.SetCellValue("Sheet"+itoa, num+"4", one.PunchCard[i].Hour) //循环生成列 统计
|
||||
numi = 4 + i
|
||||
numInt = numInt + 1
|
||||
//最后一个总计
|
||||
if i == len(one.PunchCard)-1 {
|
||||
f.SetCellValue("Sheet"+itoa, num+"5", "总计") //循环生成列
|
||||
}
|
||||
}
|
||||
f.MergeCell("Sheet"+itoa, getColumnName(4)+"1", getColumnName(numi)+"1")
|
||||
f.SetCellValue("Sheet"+itoa, num+"1", "8月")
|
||||
|
||||
numi = numi + 1
|
||||
num = getColumnName(numi)
|
||||
//4、右侧不清楚
|
||||
f.SetCellValue("Sheet"+itoa, num+"1", "合计工时")
|
||||
f.SetCellValue("Sheet"+itoa, num+"4", one.SumHour)
|
||||
f.SetCellValue("Sheet"+itoa, num+"5", one.SumHour)
|
||||
f.MergeCell("Sheet"+itoa, num+"1", num+"2")
|
||||
|
||||
numi = numi + 1
|
||||
num = getColumnName(numi)
|
||||
f.SetCellValue("Sheet"+itoa, num+"1", "合计工天")
|
||||
f.SetCellValue("Sheet"+itoa, num+"4", one.SumDay)
|
||||
f.SetCellValue("Sheet"+itoa, num+"5", one.SumDay)
|
||||
f.MergeCell("Sheet"+itoa, num+"1", num+"2")
|
||||
|
||||
numi = numi + 1
|
||||
num = getColumnName(numi)
|
||||
f.SetCellValue("Sheet"+itoa, num+"1", "工人签名")
|
||||
f.MergeCell("Sheet"+itoa, num+"1", num+"2")
|
||||
|
||||
numi = numi + 1
|
||||
num = getColumnName(numi)
|
||||
f.SetCellValue("Sheet"+itoa, num+"1", "备注")
|
||||
f.MergeCell("Sheet"+itoa, num+"1", num+"2")
|
||||
}
|
||||
|
||||
// 设置工作簿的默认工作表
|
||||
f.SetActiveSheet(1)
|
||||
|
||||
// 根据指定路径保存文件
|
||||
str := FileName()
|
||||
filePath = coryCommon.Temporary + "/" + str
|
||||
getwd, err := os.Getwd()
|
||||
err = f.SaveAs(getwd + filePath)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return "", "", ""
|
||||
}
|
||||
return getwd + filePath, filePath, str
|
||||
}
|
||||
|
||||
// ExcelTwo 多条数据在一个工作簿
|
||||
func ExcelTwo(oneDateOneList []*wxApplet.PunchingCardRecordOne) (file string, filePath string, fileName string) {
|
||||
|
||||
f := excelize.NewFile()
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
|
||||
bottomStyleId, err := f.NewStyle(&excelize.Style{
|
||||
Border: []excelize.Border{
|
||||
{Type: "bottom", Color: "000000", Style: 1},
|
||||
//{Type: "diagonalDown", Color: "000000", Style: 5},
|
||||
//{Type: "diagonalUp", Color: "A020F0", Style: 6},
|
||||
},
|
||||
//背景
|
||||
Fill: excelize.Fill{
|
||||
Type: "pattern",
|
||||
Color: []string{"#e6ecf0"},
|
||||
Pattern: 1,
|
||||
},
|
||||
////字体
|
||||
//Font: &excelize.Font{
|
||||
// Color: "#ffffff", // 字体颜色,这里使用蓝色 (#0000FF)
|
||||
//},
|
||||
})
|
||||
|
||||
//全样式
|
||||
styleId, err := f.NewStyle(&excelize.Style{
|
||||
//边框
|
||||
Border: []excelize.Border{
|
||||
{Type: "left", Color: "000000", Style: 1},
|
||||
{Type: "top", Color: "000000", Style: 1},
|
||||
{Type: "bottom", Color: "000000", Style: 1},
|
||||
{Type: "right", Color: "000000", Style: 1},
|
||||
},
|
||||
//居中
|
||||
Alignment: &excelize.Alignment{
|
||||
Vertical: "center", // 上下居中
|
||||
Horizontal: "center", // 左右居中
|
||||
},
|
||||
//背景
|
||||
Fill: excelize.Fill{
|
||||
Type: "pattern",
|
||||
Color: []string{"#e6ecf0"},
|
||||
Pattern: 1,
|
||||
},
|
||||
////字体
|
||||
//Font: &excelize.Font{
|
||||
// Color: "#ffffff", // 字体颜色,这里使用蓝色 (#0000FF)
|
||||
//},
|
||||
})
|
||||
// 每组数据隔N行另起
|
||||
bookNum := 0
|
||||
for x, one := range oneDateOneList {
|
||||
if one.Name == "" {
|
||||
continue
|
||||
}
|
||||
itoa := "1" //第一个工作簿
|
||||
var num = "0" //循环生成列
|
||||
var numInt = 1 //循环列对应的编号
|
||||
var numi = 0 //循环过后需要的列
|
||||
for i := 0; i < len(one.PunchCard); i++ {
|
||||
//设置1、2行高度
|
||||
err := f.SetRowHeight("Sheet"+itoa, bookNum+1, 36)
|
||||
err = f.SetRowHeight("Sheet"+itoa, bookNum+2, 14)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
//1、左侧固定 ABC的1~2
|
||||
f.SetCellValue("Sheet"+itoa, "A"+strconv.Itoa((bookNum+1)), "序号")
|
||||
f.MergeCell("Sheet"+itoa, "A"+strconv.Itoa((bookNum+1)), "A"+strconv.Itoa((bookNum+2)))
|
||||
f.SetCellStyle("Sheet"+itoa, "A"+strconv.Itoa((bookNum+1)), "A"+strconv.Itoa((bookNum+2)), styleId)
|
||||
|
||||
f.SetCellValue("Sheet"+itoa, "B"+strconv.Itoa((bookNum+1)), "姓名")
|
||||
f.MergeCell("Sheet"+itoa, "B"+strconv.Itoa((bookNum+1)), "B"+strconv.Itoa((bookNum+2)))
|
||||
f.SetCellStyle("Sheet"+itoa, "B"+strconv.Itoa((bookNum+1)), "B"+strconv.Itoa((bookNum+2)), styleId)
|
||||
|
||||
f.SetCellValue("Sheet"+itoa, "C"+strconv.Itoa((bookNum+1)), "")
|
||||
f.MergeCell("Sheet"+itoa, "C"+strconv.Itoa((bookNum+1)), "C"+strconv.Itoa((bookNum+2)))
|
||||
f.SetCellStyle("Sheet"+itoa, "C"+strconv.Itoa((bookNum+1)), "C"+strconv.Itoa((bookNum+2)), styleId)
|
||||
//2、左侧固定 ABC的3~4
|
||||
f.SetCellValue("Sheet"+itoa, "A"+strconv.Itoa((bookNum+3)), x+1)
|
||||
f.MergeCell("Sheet"+itoa, "A"+strconv.Itoa((bookNum+3)), "A"+strconv.Itoa((bookNum+4)))
|
||||
f.SetCellStyle("Sheet"+itoa, "A"+strconv.Itoa((bookNum+3)), "A"+strconv.Itoa((bookNum+4)), styleId)
|
||||
|
||||
f.SetCellValue("Sheet"+itoa, "B"+strconv.Itoa((bookNum+3)), one.Name)
|
||||
f.MergeCell("Sheet"+itoa, "B"+strconv.Itoa((bookNum+3)), "B"+strconv.Itoa((bookNum+4)))
|
||||
f.SetCellStyle("Sheet"+itoa, "B"+strconv.Itoa((bookNum+3)), "B"+strconv.Itoa((bookNum+4)), styleId)
|
||||
|
||||
//f.SetCellValue("Sheet"+itoa, "c"+strconv.Itoa((bookNum+3)), "打卡记录")
|
||||
f.SetCellValue("Sheet"+itoa, "c"+strconv.Itoa((bookNum+3)), "工时")
|
||||
f.MergeCell("Sheet"+itoa, "c"+strconv.Itoa((bookNum+3)), "c"+strconv.Itoa((bookNum+4)))
|
||||
f.SetCellStyle("Sheet"+itoa, "c"+strconv.Itoa((bookNum+3)), "c"+strconv.Itoa((bookNum+4)), styleId)
|
||||
|
||||
//3、中间数据---------从4开始创建列
|
||||
num = getColumnName(4 + i)
|
||||
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+2)), numInt) //循环列对应的编号
|
||||
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa((bookNum+2)), num+strconv.Itoa((bookNum+2)), styleId) //循环列对应的编号
|
||||
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+3)), one.PunchCard[i].Clock) //循环生成列 时间
|
||||
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa((bookNum+3)), num+strconv.Itoa((bookNum+3)), styleId) //循环生成列 时间
|
||||
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), one.PunchCard[i].Hour) //循环生成列 统计
|
||||
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+4)), styleId) //循环生成列 统计
|
||||
numi = 4 + i
|
||||
numInt = numInt + 1
|
||||
//最后一个总计
|
||||
if i == len(one.PunchCard)-1 {
|
||||
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+5)), "总计") //循环生成列
|
||||
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa((bookNum+5)), "总计", styleId) //循环生成列
|
||||
}
|
||||
}
|
||||
f.SetCellValue("Sheet"+itoa, "D"+strconv.Itoa((bookNum+1)), one.Years)
|
||||
f.MergeCell("Sheet"+itoa, getColumnName(4)+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+1))
|
||||
f.SetCellStyle("Sheet"+itoa, getColumnName(4)+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+1), styleId)
|
||||
|
||||
numi = numi + 1
|
||||
num = getColumnName(numi)
|
||||
//4、右侧不清楚
|
||||
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+1)), "合计工时")
|
||||
f.MergeCell("Sheet"+itoa, num+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+3))
|
||||
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+3), styleId)
|
||||
|
||||
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), one.SumHour)
|
||||
f.MergeCell("Sheet"+itoa, num+strconv.Itoa(bookNum+4), getColumnName(numi)+strconv.Itoa(bookNum+4))
|
||||
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa(bookNum+4), getColumnName(numi)+strconv.Itoa(bookNum+4), styleId)
|
||||
|
||||
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+5)), one.SumHour)
|
||||
f.MergeCell("Sheet"+itoa, num+strconv.Itoa(bookNum+5), getColumnName(numi)+strconv.Itoa(bookNum+5))
|
||||
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa(bookNum+5), getColumnName(numi)+strconv.Itoa(bookNum+5), styleId)
|
||||
|
||||
numi = numi + 1
|
||||
num = getColumnName(numi)
|
||||
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+1)), "合计工天")
|
||||
f.MergeCell("Sheet"+itoa, num+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+3))
|
||||
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+3), styleId)
|
||||
|
||||
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), one.SumDay)
|
||||
f.MergeCell("Sheet"+itoa, num+strconv.Itoa(bookNum+4), getColumnName(numi)+strconv.Itoa(bookNum+4))
|
||||
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa(bookNum+4), getColumnName(numi)+strconv.Itoa(bookNum+4), styleId)
|
||||
|
||||
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+5)), one.SumDay)
|
||||
f.MergeCell("Sheet"+itoa, num+strconv.Itoa(bookNum+5), getColumnName(numi)+strconv.Itoa(bookNum+5))
|
||||
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa(bookNum+5), getColumnName(numi)+strconv.Itoa(bookNum+5), styleId)
|
||||
|
||||
numi = numi + 1
|
||||
num = getColumnName(numi)
|
||||
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+1)), "工人签名")
|
||||
f.MergeCell("Sheet"+itoa, num+strconv.Itoa((bookNum+1)), num+strconv.Itoa((bookNum+3)))
|
||||
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa((bookNum+1)), num+strconv.Itoa((bookNum+3)), styleId)
|
||||
|
||||
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), "")
|
||||
f.MergeCell("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+5)))
|
||||
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+5)), styleId)
|
||||
|
||||
numi = numi + 1
|
||||
num = getColumnName(numi)
|
||||
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+1)), "备注")
|
||||
f.MergeCell("Sheet"+itoa, num+strconv.Itoa((bookNum+1)), num+strconv.Itoa((bookNum+3)))
|
||||
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa((bookNum+1)), num+strconv.Itoa((bookNum+3)), styleId)
|
||||
|
||||
f.SetCellValue("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), "")
|
||||
f.MergeCell("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+5)))
|
||||
f.SetCellStyle("Sheet"+itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+5)), styleId)
|
||||
|
||||
f.SetCellStyle("Sheet"+itoa, "A"+strconv.Itoa((bookNum+5)), getColumnName(numi-4)+strconv.Itoa((bookNum+5)), bottomStyleId)
|
||||
|
||||
bookNum = bookNum + 10
|
||||
}
|
||||
|
||||
// 设置工作簿的默认工作表
|
||||
f.SetActiveSheet(1)
|
||||
|
||||
// 根据指定路径保存文件
|
||||
str := FileName()
|
||||
filePath = coryCommon.Temporary + "/" + str
|
||||
getwd, err := os.Getwd()
|
||||
err = f.SaveAs(getwd + filePath)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return "", "", ""
|
||||
}
|
||||
return getwd + filePath, filePath, str
|
||||
}
|
||||
|
||||
// ExcelThree 多个工作簿,每个工作簿有多条数据
|
||||
func ExcelThree(gey []*wxApplet.GroupEntity) (file string, filePath string, fileName string) {
|
||||
f := excelize.NewFile()
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}()
|
||||
|
||||
bottomStyleId, err := f.NewStyle(&excelize.Style{
|
||||
Border: []excelize.Border{
|
||||
{Type: "bottom", Color: "000000", Style: 1},
|
||||
//{Type: "diagonalDown", Color: "000000", Style: 5},
|
||||
//{Type: "diagonalUp", Color: "A020F0", Style: 6},
|
||||
},
|
||||
//背景
|
||||
Fill: excelize.Fill{
|
||||
Type: "pattern",
|
||||
Color: []string{"#e6ecf0"},
|
||||
Pattern: 1,
|
||||
},
|
||||
////字体
|
||||
//Font: &excelize.Font{
|
||||
// Color: "#ffffff", // 字体颜色,这里使用蓝色 (#0000FF)
|
||||
//},
|
||||
})
|
||||
|
||||
//全样式
|
||||
styleId, err := f.NewStyle(&excelize.Style{
|
||||
//边框
|
||||
Border: []excelize.Border{
|
||||
{Type: "left", Color: "000000", Style: 1},
|
||||
{Type: "top", Color: "000000", Style: 1},
|
||||
{Type: "bottom", Color: "000000", Style: 1},
|
||||
{Type: "right", Color: "000000", Style: 1},
|
||||
},
|
||||
//居中
|
||||
Alignment: &excelize.Alignment{
|
||||
Vertical: "center", // 上下居中
|
||||
Horizontal: "center", // 左右居中
|
||||
},
|
||||
//背景
|
||||
Fill: excelize.Fill{
|
||||
Type: "pattern",
|
||||
Color: []string{"#e6ecf0"},
|
||||
Pattern: 1,
|
||||
},
|
||||
////字体
|
||||
//Font: &excelize.Font{
|
||||
// Color: "#ffffff", // 字体颜色,这里使用蓝色 (#0000FF)
|
||||
//},
|
||||
})
|
||||
|
||||
//工作簿
|
||||
for _, data := range gey {
|
||||
//itoa := strconv.Itoa(y + 1)
|
||||
itoa := data.GroupName
|
||||
_, err = f.NewSheet(itoa)
|
||||
|
||||
oneDateOneList := data.PunchingCardRecordOne
|
||||
//工作簿里面的数据
|
||||
bookNum := 0 // 每组数据隔N行另起
|
||||
for x, one := range oneDateOneList {
|
||||
//itoa := "1" //第一个工作簿
|
||||
var num = "0" //循环生成列
|
||||
var numInt = 1 //循环列对应的编号
|
||||
var numi = 0 //循环过后需要的列
|
||||
for i := 0; i < len(one.PunchCard); i++ {
|
||||
//设置1、2行高度
|
||||
err := f.SetRowHeight(itoa, bookNum+1, 36)
|
||||
err = f.SetRowHeight(itoa, bookNum+2, 14)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
//1、左侧固定 ABC的1~2
|
||||
f.SetCellValue(itoa, "A"+strconv.Itoa((bookNum+1)), "序号")
|
||||
f.MergeCell(itoa, "A"+strconv.Itoa((bookNum+1)), "A"+strconv.Itoa((bookNum+2)))
|
||||
f.SetCellStyle(itoa, "A"+strconv.Itoa((bookNum+1)), "A"+strconv.Itoa((bookNum+2)), styleId)
|
||||
|
||||
f.SetCellValue(itoa, "B"+strconv.Itoa((bookNum+1)), "姓名")
|
||||
f.MergeCell(itoa, "B"+strconv.Itoa((bookNum+1)), "B"+strconv.Itoa((bookNum+2)))
|
||||
f.SetCellStyle(itoa, "B"+strconv.Itoa((bookNum+1)), "B"+strconv.Itoa((bookNum+2)), styleId)
|
||||
|
||||
f.SetCellValue(itoa, "C"+strconv.Itoa((bookNum+1)), "")
|
||||
f.MergeCell(itoa, "C"+strconv.Itoa((bookNum+1)), "C"+strconv.Itoa((bookNum+2)))
|
||||
f.SetCellStyle(itoa, "C"+strconv.Itoa((bookNum+1)), "C"+strconv.Itoa((bookNum+2)), styleId)
|
||||
//2、左侧固定 ABC的3~4
|
||||
f.SetCellValue(itoa, "A"+strconv.Itoa((bookNum+3)), x+1)
|
||||
f.MergeCell(itoa, "A"+strconv.Itoa((bookNum+3)), "A"+strconv.Itoa((bookNum+4)))
|
||||
f.SetCellStyle(itoa, "A"+strconv.Itoa((bookNum+3)), "A"+strconv.Itoa((bookNum+4)), styleId)
|
||||
|
||||
f.SetCellValue(itoa, "B"+strconv.Itoa((bookNum+3)), one.Name)
|
||||
f.MergeCell(itoa, "B"+strconv.Itoa((bookNum+3)), "B"+strconv.Itoa((bookNum+4)))
|
||||
f.SetCellStyle(itoa, "B"+strconv.Itoa((bookNum+3)), "B"+strconv.Itoa((bookNum+4)), styleId)
|
||||
|
||||
//f.SetCellValue(itoa, "c"+strconv.Itoa((bookNum+3)), "打卡记录")
|
||||
f.SetCellValue(itoa, "c"+strconv.Itoa((bookNum+3)), "工时")
|
||||
f.MergeCell(itoa, "c"+strconv.Itoa((bookNum+3)), "c"+strconv.Itoa((bookNum+4)))
|
||||
f.SetCellStyle(itoa, "c"+strconv.Itoa((bookNum+3)), "c"+strconv.Itoa((bookNum+4)), styleId)
|
||||
|
||||
//3、中间数据---------从4开始创建列
|
||||
num = getColumnName(4 + i)
|
||||
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+2)), numInt) //循环列对应的编号
|
||||
f.SetCellStyle(itoa, num+strconv.Itoa((bookNum+2)), num+strconv.Itoa((bookNum+2)), styleId) //循环列对应的编号
|
||||
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+3)), one.PunchCard[i].Clock) //循环生成列 时间
|
||||
f.SetCellStyle(itoa, num+strconv.Itoa((bookNum+3)), num+strconv.Itoa((bookNum+3)), styleId) //循环生成列 时间
|
||||
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+4)), one.PunchCard[i].Hour) //循环生成列 统计
|
||||
f.SetCellStyle(itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+4)), styleId) //循环生成列 统计
|
||||
numi = 4 + i
|
||||
numInt = numInt + 1
|
||||
//最后一个总计
|
||||
if i == len(one.PunchCard)-1 {
|
||||
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+5)), "总计") //循环生成列
|
||||
f.SetCellStyle(itoa, num+strconv.Itoa((bookNum+5)), "总计", styleId) //循环生成列
|
||||
}
|
||||
}
|
||||
f.SetCellValue(itoa, "D"+strconv.Itoa((bookNum+1)), one.Years)
|
||||
f.MergeCell(itoa, getColumnName(4)+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+1))
|
||||
f.SetCellStyle(itoa, getColumnName(4)+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+1), styleId)
|
||||
|
||||
numi = numi + 1
|
||||
num = getColumnName(numi)
|
||||
//4、右侧不清楚
|
||||
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+1)), "合计工时")
|
||||
f.MergeCell(itoa, num+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+3))
|
||||
f.SetCellStyle(itoa, num+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+3), styleId)
|
||||
|
||||
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+4)), one.SumHour)
|
||||
f.MergeCell(itoa, num+strconv.Itoa(bookNum+4), getColumnName(numi)+strconv.Itoa(bookNum+4))
|
||||
f.SetCellStyle(itoa, num+strconv.Itoa(bookNum+4), getColumnName(numi)+strconv.Itoa(bookNum+4), styleId)
|
||||
|
||||
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+5)), one.SumHour)
|
||||
f.MergeCell(itoa, num+strconv.Itoa(bookNum+5), getColumnName(numi)+strconv.Itoa(bookNum+5))
|
||||
f.SetCellStyle(itoa, num+strconv.Itoa(bookNum+5), getColumnName(numi)+strconv.Itoa(bookNum+5), styleId)
|
||||
|
||||
numi = numi + 1
|
||||
num = getColumnName(numi)
|
||||
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+1)), "合计工天")
|
||||
f.MergeCell(itoa, num+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+3))
|
||||
f.SetCellStyle(itoa, num+strconv.Itoa(bookNum+1), getColumnName(numi)+strconv.Itoa(bookNum+3), styleId)
|
||||
|
||||
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+4)), one.SumDay)
|
||||
f.MergeCell(itoa, num+strconv.Itoa(bookNum+4), getColumnName(numi)+strconv.Itoa(bookNum+4))
|
||||
f.SetCellStyle(itoa, num+strconv.Itoa(bookNum+4), getColumnName(numi)+strconv.Itoa(bookNum+4), styleId)
|
||||
|
||||
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+5)), one.SumDay)
|
||||
f.MergeCell(itoa, num+strconv.Itoa(bookNum+5), getColumnName(numi)+strconv.Itoa(bookNum+5))
|
||||
f.SetCellStyle(itoa, num+strconv.Itoa(bookNum+5), getColumnName(numi)+strconv.Itoa(bookNum+5), styleId)
|
||||
|
||||
numi = numi + 1
|
||||
num = getColumnName(numi)
|
||||
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+1)), "工人签名")
|
||||
f.MergeCell(itoa, num+strconv.Itoa((bookNum+1)), num+strconv.Itoa((bookNum+3)))
|
||||
f.SetCellStyle(itoa, num+strconv.Itoa((bookNum+1)), num+strconv.Itoa((bookNum+3)), styleId)
|
||||
|
||||
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+4)), "")
|
||||
f.MergeCell(itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+5)))
|
||||
f.SetCellStyle(itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+5)), styleId)
|
||||
|
||||
numi = numi + 1
|
||||
num = getColumnName(numi)
|
||||
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+1)), "备注")
|
||||
f.MergeCell(itoa, num+strconv.Itoa((bookNum+1)), num+strconv.Itoa((bookNum+3)))
|
||||
f.SetCellStyle(itoa, num+strconv.Itoa((bookNum+1)), num+strconv.Itoa((bookNum+3)), styleId)
|
||||
|
||||
f.SetCellValue(itoa, num+strconv.Itoa((bookNum+4)), "")
|
||||
f.MergeCell(itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+5)))
|
||||
f.SetCellStyle(itoa, num+strconv.Itoa((bookNum+4)), num+strconv.Itoa((bookNum+5)), styleId)
|
||||
|
||||
f.SetCellStyle(itoa, "A"+strconv.Itoa((bookNum+5)), getColumnName(numi-4)+strconv.Itoa((bookNum+5)), bottomStyleId)
|
||||
|
||||
//err = f.SetCellStyle("Sheet1", "A"+strconv.Itoa((bookNum+1)), "A"+strconv.Itoa((bookNum+5)), leftStyleId)
|
||||
//err = f.SetCellStyle("Sheet1", "I"+strconv.Itoa((bookNum+1)), "I"+strconv.Itoa((bookNum+5)), rightStyleId)
|
||||
//err = f.SetCellStyle("Sheet1", "A"+strconv.Itoa((bookNum+1)), "I"+strconv.Itoa((bookNum+1)), topStyleId)
|
||||
//err = f.SetCellStyle("Sheet1", "A"+strconv.Itoa((bookNum+5)), "I"+strconv.Itoa((bookNum+5)), bottomStyleId)
|
||||
|
||||
bookNum = bookNum + 10
|
||||
}
|
||||
}
|
||||
|
||||
// 设置工作簿的默认工作表
|
||||
f.SetActiveSheet(1)
|
||||
|
||||
// 根据指定路径保存文件
|
||||
str := FileName()
|
||||
filePath = coryCommon.Temporary + "/" + str
|
||||
getwd, err := os.Getwd()
|
||||
err = f.SaveAs(getwd + filePath)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return "", "", ""
|
||||
}
|
||||
return getwd + filePath, filePath, str
|
||||
}
|
||||
|
||||
// 根据列索引获取列的字母标识 比如1就是A 30就是AD
|
||||
func getColumnName(index int) string {
|
||||
var columnName string
|
||||
for index > 0 {
|
||||
mod := (index - 1) % 26
|
||||
columnName = string('A'+mod) + columnName
|
||||
index = (index - 1) / 26
|
||||
}
|
||||
return columnName
|
||||
}
|
||||
|
||||
// FileName 生成时间戳文件名
|
||||
func FileName() (str string) {
|
||||
// 获取当前时间
|
||||
currentTime := time.Now()
|
||||
// 格式化时间戳为字符串
|
||||
timestamp := currentTime.Format("20060102150405")
|
||||
// 生成文件名
|
||||
fileName := fmt.Sprintf("zm_%s.xlsx", timestamp)
|
||||
return fileName
|
||||
}
|
55
api/v1/common/coryCommon/excelUtil/excelEntity.go
Normal file
55
api/v1/common/coryCommon/excelUtil/excelEntity.go
Normal file
@ -0,0 +1,55 @@
|
||||
package excelUtil
|
||||
|
||||
type Style struct {
|
||||
Border []Border
|
||||
Fill Fill
|
||||
Font *Font
|
||||
Alignment *Alignment
|
||||
Protection *Protection
|
||||
NumFmt int
|
||||
DecimalPlaces int
|
||||
CustomNumFmt *string
|
||||
NegRed bool
|
||||
}
|
||||
|
||||
type Fill struct {
|
||||
Type string
|
||||
Pattern int
|
||||
Color []string
|
||||
Shading int
|
||||
}
|
||||
|
||||
type Protection struct {
|
||||
Hidden bool
|
||||
Locked bool
|
||||
}
|
||||
|
||||
type Font struct {
|
||||
Bold bool
|
||||
Italic bool
|
||||
Underline string
|
||||
Family string
|
||||
Size float64
|
||||
Strike bool
|
||||
Color string
|
||||
ColorIndexed int
|
||||
ColorTheme *int
|
||||
ColorTint float64
|
||||
VertAlign string
|
||||
}
|
||||
type Border struct {
|
||||
Type string
|
||||
Color string
|
||||
Style int
|
||||
}
|
||||
type Alignment struct {
|
||||
Horizontal string
|
||||
Indent int
|
||||
JustifyLastLine bool
|
||||
ReadingOrder uint64
|
||||
RelativeIndent int
|
||||
ShrinkToFit bool
|
||||
TextRotation int
|
||||
Vertical string
|
||||
WrapText bool
|
||||
}
|
209
api/v1/common/coryCommon/imgData.go
Normal file
209
api/v1/common/coryCommon/imgData.go
Normal file
@ -0,0 +1,209 @@
|
||||
package coryCommon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
MinShortEdge = 15
|
||||
MaxLongEdge = 4096
|
||||
MaxSize = 1024 * 1024 // 4MB
|
||||
SupportedExts = ".jpg .jpeg .png .bmp"
|
||||
)
|
||||
|
||||
func ImgDataBase64(filePath string) (err error, encodedURL string) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
fmt.Println("打开图像文件失败:", err)
|
||||
err = errors.New("打开图像文件失败")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
fileInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
fmt.Println("读取图像文件信息失败:", err)
|
||||
err = errors.New("读取图像文件信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
fileSize := fileInfo.Size()
|
||||
if fileSize > MaxSize {
|
||||
fmt.Println("图像文件大小超过限制:", fileSize)
|
||||
err = errors.New("图像文件大小超过限制")
|
||||
return
|
||||
}
|
||||
|
||||
imgData, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
fmt.Println("读取图像文件内容失败:", err)
|
||||
err = errors.New("读取图像文件内容失败")
|
||||
return
|
||||
}
|
||||
|
||||
bounds, format, err := image.DecodeConfig(bytes.NewReader(imgData))
|
||||
if err != nil {
|
||||
fmt.Println("解码图像配置失败:", err)
|
||||
err = errors.New("解码图像配置失败")
|
||||
return
|
||||
}
|
||||
|
||||
if format != "jpeg" && format != "jpg" && format != "png" && format != "bmp" {
|
||||
fmt.Println("不支持的图像格式:", format)
|
||||
//err = errors.New("不支持的图像格式")
|
||||
err = errors.New("支持的图像格式为:jpg、png、gif、bmp")
|
||||
return
|
||||
}
|
||||
|
||||
width := bounds.Width
|
||||
height := bounds.Height
|
||||
shortEdge := width
|
||||
if height < width {
|
||||
shortEdge = height
|
||||
}
|
||||
|
||||
if shortEdge < MinShortEdge {
|
||||
fmt.Println("图像尺寸的最短边小于要求:", shortEdge)
|
||||
str := "图像尺寸的最短边小于要求:" + strconv.Itoa(shortEdge)
|
||||
err = errors.New(str)
|
||||
return
|
||||
}
|
||||
|
||||
if width > MaxLongEdge || height > MaxLongEdge {
|
||||
fmt.Println("图像尺寸的最长边超过限制:", width, height)
|
||||
str := "图像尺寸的最长边超过限制:" + strconv.Itoa(width) + strconv.Itoa(height)
|
||||
err = errors.New(str)
|
||||
return
|
||||
}
|
||||
|
||||
// Base64编码图像数据
|
||||
encodedStr := base64.StdEncoding.EncodeToString(imgData)
|
||||
|
||||
// URL编码
|
||||
//urlEncodedStr := url.QueryEscape(encodedStr)
|
||||
|
||||
//fmt.Println("Base64编码并URL编码后的图像数据:", urlEncodedStr)
|
||||
return err, encodedStr
|
||||
}
|
||||
|
||||
/*
|
||||
EncodeAndUrlEncodeImage 图片文件的绝对路径
|
||||
|
||||
功能:
|
||||
图像数据,base64编码后进行urlencode,需去掉编码头(data:image/jpeg;base64, )要求base64编码和urlencode后大小不超过N兆,最短边至少15px,最长边最大4096px,支持jpg/jpeg/png/bmp格式
|
||||
*/
|
||||
func EncodeAndUrlEncodeImage(filePath string, size int64) (string, error) {
|
||||
// 检查文件是否存在
|
||||
_, err := os.Stat(filePath)
|
||||
if os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("文件 %s 不存在", filePath)
|
||||
}
|
||||
|
||||
// 检查文件大小
|
||||
fileSize := getFileSize(filePath)
|
||||
if fileSize > (size * MaxSize) {
|
||||
return "", fmt.Errorf("文件大小超过限制")
|
||||
}
|
||||
|
||||
// 检查图像尺寸
|
||||
img, err := loadImage(filePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("无法加载图像: %s", err)
|
||||
}
|
||||
|
||||
bounds := img.Bounds()
|
||||
if !isValidSize(bounds) {
|
||||
return "", fmt.Errorf("图像尺寸不符合要求")
|
||||
}
|
||||
|
||||
// 读取文件
|
||||
fileData, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("无法读取文件: %s", err)
|
||||
}
|
||||
|
||||
// 获取文件后缀名
|
||||
ext := filepath.Ext(filePath)
|
||||
|
||||
// 检查是否支持的文件格式
|
||||
if !isSupportedFormat(ext) {
|
||||
return "", fmt.Errorf("不支持的文件格式")
|
||||
}
|
||||
|
||||
// 将图像数据进行 base64 编码
|
||||
encodedData := base64.StdEncoding.EncodeToString(fileData)
|
||||
|
||||
// 去掉编码头(如:"data:image/jpeg;base64,")
|
||||
encodedData = removeEncodingHeader(encodedData, ext)
|
||||
|
||||
//// 对 base64 编码后的数据进行 URL 编码
|
||||
//encodedData = urlEncode(encodedData)
|
||||
|
||||
return encodedData, nil
|
||||
}
|
||||
|
||||
func getFileSize(filePath string) int64 {
|
||||
fileInfo, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return fileInfo.Size()
|
||||
}
|
||||
|
||||
func loadImage(filePath string) (image.Image, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
img, _, err := image.Decode(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func isValidSize(bounds image.Rectangle) bool {
|
||||
width := bounds.Dx()
|
||||
height := bounds.Dy()
|
||||
if width < MinShortEdge || height < MinShortEdge {
|
||||
return false
|
||||
}
|
||||
if width > MaxLongEdge || height > MaxLongEdge {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isSupportedFormat(ext string) bool {
|
||||
ext = strings.ToLower(ext)
|
||||
return strings.Contains(SupportedExts, ext)
|
||||
}
|
||||
|
||||
func removeEncodingHeader(encodedData string, ext string) string {
|
||||
header := fmt.Sprintf("data:image/%s;base64,", ext[1:])
|
||||
|
||||
if strings.HasPrefix(encodedData, header) {
|
||||
encodedData = strings.TrimPrefix(encodedData, header)
|
||||
}
|
||||
|
||||
return encodedData
|
||||
}
|
||||
|
||||
func urlEncode(data string) string {
|
||||
return url.PathEscape(data)
|
||||
}
|
38
api/v1/common/coryCommon/largeNumber.go
Normal file
38
api/v1/common/coryCommon/largeNumber.go
Normal file
@ -0,0 +1,38 @@
|
||||
package coryCommon
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// PercentageFunc 百分比计算(精度很高:比如说总数100,完成100,最终得到结果为99.99999999%,那么会直接操作成100)
|
||||
// precision:设置精度
|
||||
// total:总数据量
|
||||
// finish:完成度
|
||||
func PercentageFunc(precision uint, total, finish float64) (consequence float64, err error) {
|
||||
if total == 0 {
|
||||
err = errors.New("总数据不能为初始值")
|
||||
return 0, err
|
||||
}
|
||||
if precision == 0 {
|
||||
err = errors.New("精度不能为初始值")
|
||||
return 0, err
|
||||
}
|
||||
consequence = 0
|
||||
// 定义大浮点数
|
||||
numerator := big.NewFloat(100)
|
||||
denominator := big.NewFloat(total)
|
||||
result := big.NewFloat(finish)
|
||||
// 设置精度
|
||||
//var precision uint = 100 // 设置为你需要的精度
|
||||
numerator.SetPrec(precision)
|
||||
denominator.SetPrec(precision)
|
||||
result.SetPrec(precision)
|
||||
// 计算结果
|
||||
result.Quo(result, denominator)
|
||||
result.Mul(result, numerator)
|
||||
// 截取到两位小数
|
||||
resultRounded, _ := result.Float64()
|
||||
consequence = float64(int(resultRounded*100)) / 100 // 保留两位小数
|
||||
return
|
||||
}
|
111
api/v1/common/coryCommon/map.go
Normal file
111
api/v1/common/coryCommon/map.go
Normal file
@ -0,0 +1,111 @@
|
||||
package coryCommon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/golang/geo/s2"
|
||||
toolTurf "github.com/tiger1103/gfast/v3/api/v1/common/tool/turf"
|
||||
"github.com/tomchavakis/geojson/geometry"
|
||||
"github.com/tomchavakis/turf-go"
|
||||
)
|
||||
|
||||
// DetailedMap shp文件数据
|
||||
type DetailedMap struct {
|
||||
Positions []struct {
|
||||
Lng float64 `json:"lng"`
|
||||
Lat float64 `json:"lat"`
|
||||
Alt float64 `json:"alt"`
|
||||
} `json:"positions"`
|
||||
Width string `json:"width"`
|
||||
Color string `json:"color"`
|
||||
Alpha string `json:"alpha"`
|
||||
Name string `json:"name"`
|
||||
Property string `json:"property"`
|
||||
TxtMemo string `json:"TxtMemo"`
|
||||
ShapeLeng string `json:"Shape_Leng"`
|
||||
ShapeArea string `json:"Shape_Area"`
|
||||
Range struct {
|
||||
MinX float64 `json:"min_x"`
|
||||
MinY float64 `json:"min_y"`
|
||||
MaxX float64 `json:"max_x"`
|
||||
MaxY float64 `json:"max_y"`
|
||||
} `json:"range"`
|
||||
}
|
||||
|
||||
// RectangularFrameRange 是否在矩形框范围内,是否在打卡范围内
|
||||
func RectangularFrameRange(dataInfo DetailedMap, locationLng float64, locationLat float64) (flag bool) {
|
||||
//1、组装数据 84坐标
|
||||
polygon := [][]float64{}
|
||||
for _, data := range dataInfo.Positions {
|
||||
polygon = append(polygon, []float64{data.Lng, data.Lat})
|
||||
}
|
||||
//3、判断位置
|
||||
distance := toolTurf.BooleanPointInPolygon([]float64{locationLng, locationLat}, polygon)
|
||||
if distance {
|
||||
fmt.Println("点在矩形框内")
|
||||
return true
|
||||
} else {
|
||||
fmt.Println("点在矩形框外")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// FEIQI_RectangularFrameRange 是否在矩形框范围内,是否在打卡范围内 !!!!!!!!!!!!!!!!!有问题
|
||||
func FEIQI_RectangularFrameRange(dataInfo DetailedMap, locationLng float64, locationLat float64) (flag bool) {
|
||||
//1、组装数据
|
||||
var pl geometry.Polygon
|
||||
var ls []geometry.LineString
|
||||
var pt []geometry.Point
|
||||
for _, data := range dataInfo.Positions {
|
||||
wgs84 := LatLng{Latitude: data.Lat, Longitude: data.Lng}
|
||||
t1 := WGS84ToEPSG900913(wgs84)
|
||||
var p geometry.Point
|
||||
p.Lng = t1.Latitude
|
||||
p.Lat = t1.Longitude
|
||||
pt = append(pt, p)
|
||||
}
|
||||
var lsTwo geometry.LineString
|
||||
lsTwo.Coordinates = pt
|
||||
ls = append(ls, lsTwo)
|
||||
pl.Coordinates = append(pl.Coordinates, lsTwo)
|
||||
//2、当前人所在位置
|
||||
locationLng, locationLat = GCJ02toWGS84(locationLng, locationLat)
|
||||
wgs84 := LatLng{Latitude: locationLng, Longitude: locationLat}
|
||||
t1 := WGS84ToEPSG900913(wgs84)
|
||||
myPoint := geometry.Point{
|
||||
Lng: t1.Longitude,
|
||||
Lat: t1.Latitude,
|
||||
}
|
||||
//3、判断myPoint是否在pl框内
|
||||
distance, _ := turf.PointInPolygon(myPoint, pl)
|
||||
if distance {
|
||||
fmt.Println("点在矩形框内")
|
||||
return true
|
||||
} else {
|
||||
fmt.Println("点在矩形框外")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type LatLng struct {
|
||||
Latitude float64
|
||||
Longitude float64
|
||||
}
|
||||
|
||||
// WGS84ToEPSG900913 将WGS84坐标转换为EPSG-900913坐标
|
||||
func WGS84ToEPSG900913(wgs84 LatLng) LatLng {
|
||||
// 将WGS84坐标转换为s2.LatLng
|
||||
ll := s2.LatLngFromDegrees(wgs84.Latitude, wgs84.Longitude)
|
||||
|
||||
// 创建s2.Point
|
||||
p := s2.PointFromLatLng(ll)
|
||||
|
||||
// 计算EPSG-900913坐标
|
||||
//epsgX, epsgY := p.Normalize()
|
||||
normalize := p.Normalize()
|
||||
|
||||
// 将坐标范围映射到EPSG-900913的范围
|
||||
epsgX := normalize.X * 20037508.34 / 180.0
|
||||
epsgY := normalize.Y * 20037508.34 / 180.0
|
||||
|
||||
return LatLng{Latitude: epsgY, Longitude: epsgX}
|
||||
}
|
23
api/v1/common/coryCommon/md5.go
Normal file
23
api/v1/common/coryCommon/md5.go
Normal file
@ -0,0 +1,23 @@
|
||||
package coryCommon
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MD5Hash 将字符串进行 MD5 加密,并返回 32 位长度的 MD5 散列值
|
||||
func MD5Hash(input string) string {
|
||||
// 将输入字符串转换为小写,以确保不区分大小写
|
||||
input = strings.ToLower(input)
|
||||
// 创建 MD5 散列对象
|
||||
md5Hash := md5.New()
|
||||
// 将输入字符串转换为字节数组,并传递给 MD5 散列对象
|
||||
_, _ = md5Hash.Write([]byte(input))
|
||||
// 计算 MD5 散列值
|
||||
hashBytes := md5Hash.Sum(nil)
|
||||
// 将散列值转换为 32 位的十六进制字符串
|
||||
md5HashString := hex.EncodeToString(hashBytes)
|
||||
|
||||
return md5HashString
|
||||
}
|
69
api/v1/common/coryCommon/restapi.go
Normal file
69
api/v1/common/coryCommon/restapi.go
Normal file
@ -0,0 +1,69 @@
|
||||
package coryCommon
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/net/gclient"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
)
|
||||
|
||||
// 高德地图API https://lbs.amap.com/api/webservice/guide/api/georegeo
|
||||
|
||||
// InverseGeocoding 逆地理编码
|
||||
func InverseGeocoding(location string) (we string) {
|
||||
//请求路径
|
||||
key := "3bbede95174c607a1ed4c479d3f637cc"
|
||||
requestURL := "https://restapi.amap.com/v3/geocode/regeo?location=" + location + "&key=" + key
|
||||
|
||||
response, err := gclient.New().ContentJson().ContentType("application/x-www-form-urlencoded").Get(gctx.New(), requestURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var dataInfo = ""
|
||||
dataInfo = response.ReadAllString()
|
||||
return dataInfo
|
||||
}
|
||||
|
||||
type InverseGeocodingRep struct {
|
||||
Status string `json:"status"`
|
||||
Regeocode Regeocode `json:"regeocode"`
|
||||
Info string `json:"info"`
|
||||
Infocode string `json:"infocode"`
|
||||
}
|
||||
|
||||
type Regeocode struct {
|
||||
FormattedAddress string `json:"formatted_address"`
|
||||
AddressComponent AddressComponent `json:"addressComponent"`
|
||||
}
|
||||
|
||||
type AddressComponent struct {
|
||||
//City string `json:"city"`
|
||||
Province string `json:"province"`
|
||||
Adcode string `json:"adcode"`
|
||||
District string `json:"district"`
|
||||
Towncode string `json:"towncode"`
|
||||
//StreetNumber StreetNumber `json:"streetNumber"`
|
||||
Country string `json:"country"`
|
||||
Township string `json:"township"`
|
||||
//BusinessAreas []One `json:"businessAreas"`
|
||||
//Building Two `json:"building"`
|
||||
//Neighborhood Two `json:"neighborhood"`
|
||||
Citycode string `json:"citycode"`
|
||||
}
|
||||
|
||||
type StreetNumber struct {
|
||||
Number string `json:"number"`
|
||||
Location string `json:"location"`
|
||||
Direction string `json:"direction"`
|
||||
Distance string `json:"distance"`
|
||||
Street string `json:"street"`
|
||||
}
|
||||
|
||||
type One struct {
|
||||
Location string `json:"location"`
|
||||
Name string `json:"name"`
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
type Two struct {
|
||||
Name []string `json:"name"`
|
||||
Type []string `json:"type"`
|
||||
}
|
473
api/v1/common/coryCommon/uploadFile.go
Normal file
473
api/v1/common/coryCommon/uploadFile.go
Normal file
@ -0,0 +1,473 @@
|
||||
package coryCommon
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/tiger1103/gfast/v3/library/liberr"
|
||||
"golang.org/x/exp/rand"
|
||||
)
|
||||
|
||||
func UploadFileS(ctx context.Context, fileHeader []*ghttp.UploadFile, fileUrl string, typeStr string) (pathStr string, err error) {
|
||||
for i := range fileHeader {
|
||||
var str string
|
||||
if typeStr == "1" {
|
||||
str, err = UploadFile(ctx, fileHeader[i], fileUrl)
|
||||
}
|
||||
if typeStr == "2" {
|
||||
str, err = UploadFileTwo(ctx, fileHeader[i], fileUrl)
|
||||
}
|
||||
if err != nil {
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
return
|
||||
}
|
||||
pathStr = pathStr + ResourcePublicToFunc("/"+str, 0) + ","
|
||||
}
|
||||
pathStr = pathStr[0 : len(pathStr)-1]
|
||||
return
|
||||
}
|
||||
|
||||
// UploadFile [文件名称为原来的文件名称] 上传文件(流) 也可以将http图片上传 返回:resource/public/upload_file/2023-11-28/tmp_c7858f0fd98d74cbe2c095125871aaf19c749c209ae33f5f.png
|
||||
func UploadFile(ctx context.Context, fileHeader *ghttp.UploadFile, fileUrl string) (str string, err error) {
|
||||
// 获取上传的文件流
|
||||
if fileHeader == nil {
|
||||
log.Println("Failed to get file")
|
||||
liberr.ErrIsNil(ctx, err, "Failed to get file")
|
||||
return "", err
|
||||
}
|
||||
ynr := Ynr(fileUrl)
|
||||
// 在当前目录创建一个新文件用于保存上传的数据
|
||||
lj := "/" + ynr + fileHeader.Filename
|
||||
destFilePath := filepath.Join(".", lj)
|
||||
destFile, err := os.Create(destFilePath)
|
||||
if err != nil {
|
||||
log.Println("Failed to create destination file:", err)
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
return "", err
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
// 创建一个内存缓冲区作为文件数据的临时存储
|
||||
buffer := make([]byte, 4096)
|
||||
|
||||
// 记录已接收的数据量,用于计算上传进度
|
||||
var totalReceived int64
|
||||
|
||||
// 获取文件上传的数据流
|
||||
file, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
log.Println("Failed to open file:", err)
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 循环读取文件流,直到读取完整个文件
|
||||
for {
|
||||
// 从文件流中读取数据到缓冲区
|
||||
n, err := file.Read(buffer)
|
||||
if err != nil && err != io.EOF {
|
||||
log.Println("Failed to read file:", err)
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 如果缓冲区没有数据,则表示已读取完整个文件
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// 将缓冲区的数据写入目标文件
|
||||
_, err = destFile.Write(buffer[:n])
|
||||
if err != nil {
|
||||
log.Println("Failed to write file:", err)
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 更新已接收的数据量
|
||||
totalReceived += int64(n)
|
||||
}
|
||||
|
||||
//// 返回上传文件的路径给客户端
|
||||
//uploadPath, err := filepath.Abs(destFilePath)
|
||||
//if err != nil {
|
||||
// log.Println("Failed to get absolute file path:", err)
|
||||
// liberr.ErrIsNil(ctx, err)
|
||||
// return "", err
|
||||
//}
|
||||
//统一路径斜杠为/
|
||||
str = filepath.ToSlash(lj)
|
||||
return str, err
|
||||
}
|
||||
|
||||
func UploadUniqueFile(ctx context.Context, fileHeader *ghttp.UploadFile, fileUrl string) (str string, err error) {
|
||||
// 获取上传的文件流
|
||||
if fileHeader == nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ynr := Ynr(fileUrl)
|
||||
|
||||
// 在当前目录创建一个新文件用于保存上传的数据
|
||||
// 在文件名中添加一个唯一的标识符,例如当前的时间戳
|
||||
lj := fmt.Sprintf("%s_%d_%s", ynr, time.Now().Unix(), fileHeader.Filename)
|
||||
destFilePath := filepath.Join(".", lj)
|
||||
destFile, err := os.Create(destFilePath)
|
||||
if err != nil {
|
||||
log.Println("Failed to create destination file:", err)
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
return "", err
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
// 创建一个内存缓冲区作为文件数据的临时存储
|
||||
buffer := make([]byte, 4096)
|
||||
|
||||
// 记录已接收的数据量,用于计算上传进度
|
||||
var totalReceived int64
|
||||
|
||||
// 获取文件上传的数据流
|
||||
file, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
log.Println("Failed to open file:", err)
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 循环读取文件流,直到读取完整个文件
|
||||
for {
|
||||
// 从文件流中读取数据到缓冲区
|
||||
n, err := file.Read(buffer)
|
||||
if err != nil && err != io.EOF {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 如果缓冲区没有数据,则表示已读取完整个文件
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// 将缓冲区的数据写入目标文件
|
||||
_, err = destFile.Write(buffer[:n])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 更新已接收的数据量
|
||||
totalReceived += int64(n)
|
||||
}
|
||||
|
||||
// 统一路径斜杠为/
|
||||
str = filepath.ToSlash(lj)
|
||||
return str, err
|
||||
}
|
||||
|
||||
// UploadFileTwo [文件名称为时间戳] 上传文件(流) 也可以将http图片上传 返回:resource/public/upload_file/2023-11-28/tmp_c7858f0fd98d74cbe2c095125871aaf19c749c209ae33f5f.png
|
||||
func UploadFileTwo(ctx context.Context, fileHeader *ghttp.UploadFile, fileUrl string) (str string, err error) {
|
||||
// 获取上传的文件流
|
||||
if fileHeader == nil {
|
||||
log.Println("Failed to get file")
|
||||
liberr.ErrIsNil(ctx, err, "Failed to get file")
|
||||
return "", err
|
||||
}
|
||||
ynr := Ynr(fileUrl)
|
||||
// 在当前目录创建一个新文件用于保存上传的数据
|
||||
lj := /* "/" +*/ ynr + FileName("login") + filepath.Ext(fileHeader.Filename)
|
||||
|
||||
destFilePath := filepath.Join(".", lj)
|
||||
destFile, err := os.Create(destFilePath)
|
||||
if err != nil {
|
||||
log.Println("Failed to create destination file:", err)
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
return "", err
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
// 创建一个内存缓冲区作为文件数据的临时存储
|
||||
buffer := make([]byte, 4096)
|
||||
|
||||
// 记录已接收的数据量,用于计算上传进度
|
||||
var totalReceived int64
|
||||
|
||||
// 获取文件上传的数据流
|
||||
file, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
log.Println("Failed to open file:", err)
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 循环读取文件流,直到读取完整个文件
|
||||
for {
|
||||
// 从文件流中读取数据到缓冲区
|
||||
n, err := file.Read(buffer)
|
||||
if err != nil && err != io.EOF {
|
||||
log.Println("Failed to read file:", err)
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 如果缓冲区没有数据,则表示已读取完整个文件
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// 将缓冲区的数据写入目标文件
|
||||
_, err = destFile.Write(buffer[:n])
|
||||
if err != nil {
|
||||
log.Println("Failed to write file:", err)
|
||||
liberr.ErrIsNil(ctx, err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 更新已接收的数据量
|
||||
totalReceived += int64(n)
|
||||
}
|
||||
|
||||
//// 返回上传文件的路径给客户端
|
||||
//uploadPath, err := filepath.Abs(destFilePath)
|
||||
//if err != nil {
|
||||
// log.Println("Failed to get absolute file path:", err)
|
||||
// liberr.ErrIsNil(ctx, err)
|
||||
// return "", err
|
||||
//}
|
||||
//统一路径斜杠为/
|
||||
str = filepath.ToSlash(lj)
|
||||
return str, err
|
||||
}
|
||||
|
||||
// Ynr 创建时间文件夹 传递相对路径/resource/public/ 返回相对路径resource/public/2023-11-12/
|
||||
func Ynr(baseDir string) (str string) {
|
||||
// 获取当前时间
|
||||
currentTime := time.Now()
|
||||
// 格式化为年月日的字符串格式
|
||||
dateString := currentTime.Format("2006-01-02")
|
||||
dateString = baseDir + dateString
|
||||
// 在相对路径上添加文件夹
|
||||
destFilePath := filepath.Join(".", dateString)
|
||||
dateString = destFilePath
|
||||
// 检查文件夹是否已存在
|
||||
_, err := os.Stat(dateString)
|
||||
if os.IsNotExist(err) {
|
||||
// 文件夹不存在,创建文件夹
|
||||
err := os.MkdirAll(dateString, os.ModePerm)
|
||||
if err != nil {
|
||||
fmt.Println("创建文件夹失败:", err)
|
||||
return
|
||||
}
|
||||
// fmt.Println("文件夹创建成功:", dateString)
|
||||
} else {
|
||||
// fmt.Println("文件夹已存在:", dateString)
|
||||
}
|
||||
return dateString + "/"
|
||||
}
|
||||
|
||||
// FileName 【前缀】+获取当前时间+随机数得到文件名
|
||||
func FileName(prefix string) (uniqueFileName string) {
|
||||
currentTime := time.Now()
|
||||
timestamp := currentTime.UnixNano() / int64(time.Millisecond)
|
||||
randomNum := rand.Intn(1000)
|
||||
uniqueFileName = fmt.Sprintf("%d_%d", timestamp, randomNum)
|
||||
if prefix != "" {
|
||||
uniqueFileName = prefix + uniqueFileName
|
||||
}
|
||||
return uniqueFileName
|
||||
}
|
||||
|
||||
// FileInfo 传参:相对路径 获取文件信息(文件名称、文件后缀、文件大小(字节))
|
||||
func FileInfo(filePath string) (name string, ext string, size int64) {
|
||||
newPath := ""
|
||||
if strings.Contains(filePath, "wxfile") {
|
||||
newPath = strings.ReplaceAll(filePath, "/wxfile", GetCWD()+"/resource/public")
|
||||
} else {
|
||||
newPath = strings.ReplaceAll(filePath, "/file", GetCWD()+"/resource/public")
|
||||
}
|
||||
newPath = strings.ReplaceAll(newPath, "\\", "/")
|
||||
filePath = newPath
|
||||
// filePath = "D:\\Cory\\go\\中煤\\zmkg-back\\resource\\public\\upload_file\\2023-08-31\\cv6kxb89pd984rt5ze.png"
|
||||
// 获取文件的基本名称(包括扩展名)
|
||||
fileName := filepath.Base(filePath)
|
||||
// 分割文件名和扩展名
|
||||
nameParts := strings.Split(fileName, ".")
|
||||
fileNameWithoutExt := nameParts[0]
|
||||
fileExt := nameParts[1]
|
||||
// 获取文件大小
|
||||
fileInfo, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
fmt.Println("无法获取文件信息:", err)
|
||||
return
|
||||
}
|
||||
fileSize := fileInfo.Size()
|
||||
// 文件名称、文件后缀、文件大小(字节)
|
||||
name = fileNameWithoutExt
|
||||
ext = fileExt
|
||||
size = fileSize
|
||||
return
|
||||
}
|
||||
|
||||
// OutJson 创建txt写入数据,jsonData数据、absPath 绝对路径(带文件名和后缀)
|
||||
func OutJson(jsonData []byte, absPath string) (flag bool, err error) {
|
||||
absPath = strings.ReplaceAll(absPath, "\\", "/")
|
||||
absPath = strings.ReplaceAll(absPath, "/file", GetCWD()+"/resource/public")
|
||||
flag = false
|
||||
// 创建要写入的文件
|
||||
file, err := os.Create(absPath)
|
||||
if err != nil {
|
||||
fmt.Println("无法创建文件:", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
// 将 JSON 数据写入文件
|
||||
_, err = file.Write(jsonData)
|
||||
if err != nil {
|
||||
fmt.Println("无法写入文件:", err)
|
||||
return
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
|
||||
// PutJson 读取文件
|
||||
func PutJson(filePath string) (jsonData string, err error) {
|
||||
filePath = strings.ReplaceAll(filePath, "/file", GetCWD()+"/resource/public")
|
||||
filePath = strings.ReplaceAll(filePath, "\\", "/")
|
||||
// filePath := "relative/path/to/file.txt"
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
fmt.Println("打开文件错误:", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
// 设置一个足够大的缓冲区
|
||||
const maxScanTokenSize = 128 * 1024
|
||||
buf := make([]byte, maxScanTokenSize)
|
||||
scanner.Buffer(buf, maxScanTokenSize)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
jsonData = jsonData + line
|
||||
}
|
||||
if scanner.Err() != nil {
|
||||
fmt.Println("读取文件错误:", scanner.Err())
|
||||
err = scanner.Err()
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
BatchFile 批量刪除文件
|
||||
filePath 相对路径
|
||||
*/
|
||||
func BatchFile(filePath []string) {
|
||||
for _, data := range filePath {
|
||||
// if strings.Contains(data, "file") || strings.Contains(data, "wxfile"){
|
||||
newPath := ""
|
||||
if strings.Contains(data, "/file") {
|
||||
newPath = strings.Replace(data, "/file", GetCWD()+"/resource/public", 1)
|
||||
} else if strings.Contains(data, "/wxfile") {
|
||||
newPath = strings.Replace(data, "/wxfile", GetCWD()+"/resource/public", 1)
|
||||
}
|
||||
os.Remove(strings.ReplaceAll(newPath, "\\", "/"))
|
||||
}
|
||||
}
|
||||
|
||||
// FlagImg 判斷是否是圖片
|
||||
func FlagImg(filePath string) (flag bool) {
|
||||
filePathLower := strings.ToLower(filePath)
|
||||
isImage := strings.HasSuffix(filePathLower, ".png") || strings.HasSuffix(filePathLower, ".jpg") || strings.HasSuffix(filePathLower, ".jpeg") || strings.HasSuffix(filePathLower, ".gif") || strings.HasSuffix(filePathLower, ".bmp")
|
||||
if isImage {
|
||||
flag = true
|
||||
} else {
|
||||
flag = false
|
||||
}
|
||||
return flag
|
||||
}
|
||||
|
||||
// CreateDirectory 判断文件夹是否存在,不存在则创建文件夹
|
||||
func CreateDirectory(folderName string) error {
|
||||
// 检查文件夹是否已经存在
|
||||
_, err := os.Stat(folderName)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
// 创建文件夹
|
||||
err = os.Mkdir(folderName, 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FileToFunc file转resource/public
|
||||
func FileToFunc(path string, num int) (rpath string) {
|
||||
if num == 1 {
|
||||
rpath = strings.Replace(path, GetCWD()+"/file/", "/resource/public/", 1)
|
||||
} else if num == 2 {
|
||||
rpath = strings.Replace(path, "/file/", GetCWD()+"/resource/public/", 1)
|
||||
} else if num == 3 {
|
||||
rpath = strings.Replace(path, "/file/", "/wxfile/", 1)
|
||||
} else if num == 4 {
|
||||
rpath = strings.Replace(path, "/wxfile/", GetCWD()+"/resource/public/", 1)
|
||||
} else if num == 5 {
|
||||
rpath = strings.Replace(path, "/wxfile/", "/file/", 1)
|
||||
} else {
|
||||
rpath = strings.Replace(path, "/file/", "/resource/public/", 1)
|
||||
}
|
||||
rpath = filepath.ToSlash(rpath)
|
||||
return
|
||||
}
|
||||
|
||||
// ResourcePublicToFunc resource/public转file
|
||||
func ResourcePublicToFunc(path string, num int) (rpath string) {
|
||||
if num == 1 {
|
||||
rpath = strings.Replace(path, GetCWD()+"/resource/public/", "/file/", 1)
|
||||
} else if num == 2 {
|
||||
rpath = strings.Replace(path, "/resource/public/", GetCWD()+"/resource/public/", 1)
|
||||
} else if num == 3 {
|
||||
rpath = strings.Replace(path, "/resource/public/", "/wxfile/", 1)
|
||||
} else {
|
||||
rpath = strings.Replace(path, "/resource/public/", "/file/", 1)
|
||||
}
|
||||
rpath = filepath.ToSlash(rpath)
|
||||
return
|
||||
}
|
||||
|
||||
// FormatRestrictionFunc 判断文件后缀是否匹配
|
||||
func FormatRestrictionFunc(path, suffix string) (flag bool) {
|
||||
split := strings.Split(suffix, ",")
|
||||
for _, data := range split {
|
||||
extension := filepath.Ext(path)
|
||||
if extension == data {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// URLCoding 对url的特俗符号进行编码,不对/进行编码(定制编码,将"/file/networkDisk/completion/admin37/2.jpg"的"networkDisk/completion/admin37/2.jpg"进行编码)
|
||||
func URLCoding(path string) (filenPathCoding string) {
|
||||
s2 := path[6 : len(path)-1]
|
||||
split := strings.Split(s2, "/")
|
||||
p := ""
|
||||
for i2 := range split {
|
||||
p = p + url.PathEscape(split[i2]) + "/"
|
||||
}
|
||||
p = p[0 : len(p)-1]
|
||||
filenPathCoding = strings.ReplaceAll(path, s2, p)
|
||||
return
|
||||
}
|
199
api/v1/common/coryCommon/watermark.go
Normal file
199
api/v1/common/coryCommon/watermark.go
Normal file
@ -0,0 +1,199 @@
|
||||
package coryCommon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fogleman/gg"
|
||||
"github.com/nfnt/resize"
|
||||
"image/color"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetCWD() string {
|
||||
cwd, _ := os.Getwd()
|
||||
return filepath.ToSlash(cwd)
|
||||
}
|
||||
|
||||
func MultiPicture(pictureList string, compere string, meetingDate string, site string, teamName string, labourserviceName string) {
|
||||
//1、分割字符串
|
||||
split := strings.Split(pictureList, ",")
|
||||
//2、循环添加水印
|
||||
for i := range split {
|
||||
filePath := split[i]
|
||||
newPath := strings.ReplaceAll(filePath, "/wxfile", GetCWD()+"/resource/public")
|
||||
newPath = strings.ReplaceAll(newPath, "\\", "/")
|
||||
WatermarkFunc(newPath, compere, meetingDate, site, teamName, labourserviceName)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// WatermarkFunc 给图片设置水印和logo
|
||||
func WatermarkFunc(filePath string, compere string, meetingDate string, site string, teamName string, labourserviceName string) {
|
||||
|
||||
// 检查文件是否存在
|
||||
_, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
fmt.Println("文件不存在")
|
||||
} else {
|
||||
fmt.Println("发生错误:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
//// 获取文件名和后缀
|
||||
//fileName := filepath.Base(filePath)
|
||||
//fileExt := filepath.Ext(filePath)
|
||||
|
||||
//1、加载图片
|
||||
srcImage, err := gg.LoadImage(filePath)
|
||||
if err != nil {
|
||||
log.Fatalf("打开图片失败: %v", err)
|
||||
}
|
||||
//2、加载logo水印
|
||||
dc := gg.NewContextForImage(srcImage)
|
||||
dc.SetRGB(1, 1, 1)
|
||||
logoImage, err := gg.LoadImage(GetCWD() + "/resource/cory/zmlogo.jpg")
|
||||
if err != nil {
|
||||
log.Fatalf("打开 logo 图片失败: %v", err)
|
||||
}
|
||||
logoWidth := 80.0
|
||||
logoHeight := float64(logoImage.Bounds().Dy()) * (logoWidth / float64(logoImage.Bounds().Dx()))
|
||||
logoImage = resize.Resize(uint(logoWidth), uint(logoHeight), logoImage, resize.Lanczos3)
|
||||
x := float64(logoWidth) + 10.0
|
||||
y := float64(dc.Height()/2) + 96.0
|
||||
dc.DrawImageAnchored(logoImage, int(x), int(y), 1.0, 1.0)
|
||||
//3、设置字体
|
||||
fontPath := GetCWD() + "/resource/cory/msyh.ttc"
|
||||
if err != nil {
|
||||
log.Fatalf("加载字体失败: %v", err)
|
||||
}
|
||||
dc.SetRGB(0, 0, 0)
|
||||
//4、创建矩形框 背景透明
|
||||
boxText := teamName
|
||||
dc.SetRGBA255(0, 99, 175, 100)
|
||||
rectangleX := x
|
||||
rectangleY := y - logoHeight + 1
|
||||
rectangleWidth := len(boxText) * 8
|
||||
rectangleHeight := logoHeight - 1
|
||||
dc.DrawRectangle(rectangleX, rectangleY, float64(rectangleWidth), rectangleHeight)
|
||||
dc.Fill()
|
||||
textFunc(dc, boxText, fontPath, 18.0, rectangleX, rectangleY, float64(rectangleWidth), rectangleHeight, 1)
|
||||
//5、添加文字水印
|
||||
text := "开会宣讲人:" + compere + "\n \n" +
|
||||
"开 会 时 间:" + meetingDate + "\n \n" +
|
||||
//"天 气:多云转晴\n \n" +
|
||||
"地 点:" + site + "\n \n"
|
||||
textX := x - logoWidth
|
||||
textY := y + 10
|
||||
err = dc.LoadFontFace(fontPath, 12)
|
||||
dc.DrawStringWrapped(text, textX, textY, 0.0, 0.0, float64(dc.Width())-textX, 1.2, gg.AlignLeft)
|
||||
//6、创建矩形框 渐变透明
|
||||
boxText = labourserviceName
|
||||
width := len(boxText) * 8
|
||||
height := 30
|
||||
fromAlpha := 0.6
|
||||
toAlpha := 0
|
||||
for x := 0; x <= width; x++ {
|
||||
alpha := fromAlpha - (fromAlpha-float64(toAlpha))*(float64(x)/float64(width))
|
||||
rgba := color.RGBA{G: 99, B: 175, A: uint8(alpha * 255)}
|
||||
dc.SetRGBA255(int(rgba.R), int(rgba.G), int(rgba.B), int(rgba.A))
|
||||
dc.DrawLine(float64(x)+10, y+96, float64(x)+10, y+96+float64(height))
|
||||
dc.StrokePreserve()
|
||||
dc.Fill()
|
||||
}
|
||||
textFunc(dc, boxText, fontPath, 16.0, 10.0, y+96, float64(width), float64(height), 2)
|
||||
//7、保存图片
|
||||
err = dc.SavePNG(filePath)
|
||||
if err != nil {
|
||||
log.Fatalf("保存带水印的图片失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func textFunc(dc *gg.Context, boxText string, fontPath string, boxTextSize, rectangleX, rectangleY, rectangleWidth, rectangleHeight float64, num int) {
|
||||
err := dc.LoadFontFace(fontPath, boxTextSize)
|
||||
if err != nil {
|
||||
log.Fatalf("加载字体失败: %v", err)
|
||||
}
|
||||
dc.SetRGB(1, 1, 1)
|
||||
boxTextWidth, boxTextHeight := dc.MeasureString(boxText)
|
||||
boxTextX := 0.00
|
||||
if num == 1 {
|
||||
boxTextX = rectangleX + (rectangleWidth-boxTextWidth)/2
|
||||
} else if num == 2 {
|
||||
boxTextX = 10
|
||||
} else {
|
||||
log.Fatalf("对齐方式错误")
|
||||
}
|
||||
boxTextY := rectangleY + (rectangleHeight-boxTextHeight)/2 + boxTextHeight
|
||||
dc.DrawStringAnchored(boxText, boxTextX, boxTextY, 0.0, 0.0)
|
||||
}
|
||||
|
||||
// 绘制矩形框
|
||||
func Test_draw_rect_text(im_path string, x, y, w, h float64) {
|
||||
// Load image
|
||||
//font_path := GetCWD() + "/resource/cory/msyh.ttc"
|
||||
im, err := gg.LoadImage(im_path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// 2 method
|
||||
dc := gg.NewContextForImage(im)
|
||||
|
||||
// Set color and line width
|
||||
dc.SetHexColor("#FF0000")
|
||||
dc.SetLineWidth(4)
|
||||
|
||||
// DrawRoundedRectangle 使用 DrawRoundedRectangle 方法在图像上绘制一个带有圆角的矩形。这里 x, y 是矩形左上角的坐标,w, h 是矩形的宽度和高度,最后的 0 表示圆角的半径为0。
|
||||
dc.DrawRoundedRectangle(x, y, w, h, 0)
|
||||
// Store set
|
||||
dc.Stroke()
|
||||
|
||||
dc.DrawRectangle(x, y, w, h)
|
||||
dc.Clip()
|
||||
|
||||
// Save png image
|
||||
dc.SavePNG(im_path)
|
||||
}
|
||||
|
||||
type TestDrawRectTextEntity struct {
|
||||
ImPath string `json:"im_path"`
|
||||
Coordinates []*CoordinatesListEntity `json:"coordinates"`
|
||||
}
|
||||
|
||||
type CoordinatesListEntity struct {
|
||||
X float64 `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
W float64 `json:"w"`
|
||||
H float64 `json:"h"`
|
||||
}
|
||||
|
||||
// TestDrawRectTextFunc 同一文件多次绘制矩形框
|
||||
func TestDrawRectTextFunc(entity *TestDrawRectTextEntity) {
|
||||
if entity == nil {
|
||||
return
|
||||
}
|
||||
if len(entity.Coordinates) == 0 {
|
||||
return
|
||||
}
|
||||
// 加载图像
|
||||
im, err := gg.LoadImage(entity.ImPath) // 加载图像一次
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// 创建上下文
|
||||
dc := gg.NewContextForImage(im)
|
||||
// 设置颜色和线宽
|
||||
dc.SetHexColor("#FF0000")
|
||||
dc.SetLineWidth(4)
|
||||
//绘制和保存矩形
|
||||
for _, zuobiao := range entity.Coordinates {
|
||||
// 绘制矩形
|
||||
dc.DrawRoundedRectangle(zuobiao.X, zuobiao.Y, zuobiao.W, zuobiao.H, 0)
|
||||
}
|
||||
dc.Stroke()
|
||||
// 保存图像
|
||||
dc.SavePNG(entity.ImPath)
|
||||
}
|
166
api/v1/common/coryCommon/weChatNewsFeeds.go
Normal file
166
api/v1/common/coryCommon/weChatNewsFeeds.go
Normal file
@ -0,0 +1,166 @@
|
||||
package coryCommon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
commonService "github.com/tiger1103/gfast/v3/internal/app/common/service"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/dao"
|
||||
"github.com/tiger1103/gfast/v3/internal/app/system/model"
|
||||
wxModel "github.com/tiger1103/gfast/v3/internal/app/wxApplet/model"
|
||||
tool "github.com/tiger1103/gfast/v3/utility/coryUtils"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 微信小程序的消息订阅 https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscribe-message/sendMessage.html
|
||||
|
||||
type TokenEntity struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
ExpiresIn string `json:"expiresIn"`
|
||||
}
|
||||
|
||||
func GetAccessToken() (token string, err error) {
|
||||
var te *TokenEntity
|
||||
appId, _ := g.Cfg().Get(gctx.New(), "wx.appId")
|
||||
appSecret, _ := g.Cfg().Get(gctx.New(), "wx.appSecret")
|
||||
key := "weChatAccessToken"
|
||||
//从缓存捞取key
|
||||
ctx := gctx.New()
|
||||
get := commonService.Cache().Get(ctx, key)
|
||||
if get != nil && get.String() != "" {
|
||||
token = get.String()
|
||||
return "", err
|
||||
} else {
|
||||
uri := "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId.String() + "&secret=" + appSecret.String()
|
||||
response, err := g.Client().Get(gctx.New(), uri)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
allString := response.ReadAllString()
|
||||
err := json.Unmarshal([]byte(allString), &te)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
//将token存储到redis中,tiken默认时间为秒,实际计算为2小时,(这里少100秒,防止token过期还存在redis中)
|
||||
num, err := strconv.Atoi(te.ExpiresIn)
|
||||
if err != nil {
|
||||
err = errors.New("过期时间转换失败!")
|
||||
return "", err
|
||||
}
|
||||
commonService.Cache().Set(ctx, key, te.AccessToken, time.Duration(num-100)*time.Second)
|
||||
token = te.AccessToken
|
||||
return token, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type AppletSubscription struct {
|
||||
Touser string `json:"touser"`
|
||||
TemplateId string `json:"template_id"`
|
||||
MiniprogramState string `json:"MiniprogramState"`
|
||||
Data DataEntity `json:"data"`
|
||||
}
|
||||
|
||||
type DataEntity struct {
|
||||
Date1 ValueEntity `json:"date1"`
|
||||
Thing2 ValueEntity `json:"thing2"`
|
||||
Thing3 ValueEntity `json:"thing3"`
|
||||
}
|
||||
|
||||
type ValueEntity struct {
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type MsgEntity struct {
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg int64 `json:"errmsg"`
|
||||
MsgId string `json:"msgid"`
|
||||
}
|
||||
|
||||
// Subscription 微信服务通知(消息订阅)
|
||||
func Subscription(openid string) (msgEntity *MsgEntity, err error) {
|
||||
//1、获取token
|
||||
token, err := GetAccessToken()
|
||||
if err != nil {
|
||||
fmt.Println("获取微信凭证错误!")
|
||||
return
|
||||
}
|
||||
//2、组装数据
|
||||
//now := time.Now()
|
||||
var entity = new(AppletSubscription)
|
||||
entity.Touser = openid
|
||||
entity.TemplateId = "EyBO6gWizF5HwUThYSSm_HuQWgfMrwEkVHPXeEq1Me8"
|
||||
entity.MiniprogramState = "trial"
|
||||
var dataEntity = DataEntity{}
|
||||
dataEntity.Date1 = ValueEntity{Value: time.Now().Format("2006-01-02 15:04:05")}
|
||||
dataEntity.Thing2 = ValueEntity{Value: "您今日还有打卡未完成!"}
|
||||
dataEntity.Thing3 = ValueEntity{Value: "请登录中煤小程序进行打卡操作!"}
|
||||
entity.Data = dataEntity
|
||||
marshal, _ := json.Marshal(entity)
|
||||
|
||||
//3、发起请求
|
||||
msgEntity = new(MsgEntity)
|
||||
uri := "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + token
|
||||
post, err := g.Client().Post(gctx.New(), uri, marshal)
|
||||
|
||||
postBytes, err := json.Marshal(post)
|
||||
if err != nil {
|
||||
fmt.Println("JSON marshaling error:", err)
|
||||
return
|
||||
}
|
||||
if err := json.Unmarshal(postBytes, &msgEntity); err != nil {
|
||||
fmt.Println("解析JSON错误:", err)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ServiceNoticeFunc(ctx context.Context) {
|
||||
//1、获取所有项目的打卡范围
|
||||
var projectEntity []*model.SysProjectListRes
|
||||
err := dao.SysProject.Ctx(ctx).
|
||||
Where("status", 0).
|
||||
Where("show_hidden", 1).
|
||||
Fields("id,project_name,punch_range").Scan(&projectEntity)
|
||||
if err != nil {
|
||||
fmt.Println("获取项目失败!")
|
||||
}
|
||||
//2、遍历项目获取打卡范围
|
||||
for _, pData := range projectEntity {
|
||||
// 3、每个项目都有两个临时触发器
|
||||
punchRange := pData.PunchRange
|
||||
split := strings.Split(punchRange, ",")
|
||||
for _, cfq := range split {
|
||||
dn, err := tool.TimeStr(cfq)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
//创建临时定时器
|
||||
if dn > 0 {
|
||||
tempTimer := time.NewTimer(dn)
|
||||
// 在新的 goroutine 中等待临时定时器触发
|
||||
go func() {
|
||||
<-tempTimer.C
|
||||
//4、就获取当前项目下面的所有成员(条件为subscription为1的数据) 下发数据,并存储下发数据的状态
|
||||
var openidList []*wxModel.BusConstructionUserListRes
|
||||
dao.BusConstructionUser.Ctx(ctx).
|
||||
Fields("openid").
|
||||
Where("subscription", "1").
|
||||
Where("status = 0").
|
||||
Where("entry_date is not null and entry_date!='' and (leave_date is null or leave_date = '')").
|
||||
Where("project_id", pData.Id).
|
||||
Scan(&openidList)
|
||||
for _, oi := range openidList {
|
||||
Subscription(oi.Openid)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
110
api/v1/common/coryCommon/weather.go
Normal file
110
api/v1/common/coryCommon/weather.go
Normal file
@ -0,0 +1,110 @@
|
||||
package coryCommon
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/net/gclient"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
)
|
||||
|
||||
var key = "00b60ebda96849e694cb570e3d4f5c89"
|
||||
|
||||
// WeatherRep 返回参数 免费天气查询 https://dev.qweather.com/docs/api/weather/weather-daily-forecast/
|
||||
type WeatherRep struct {
|
||||
Code string `json:"code"`
|
||||
UpdateTime string `json:"updateTime"`
|
||||
FxLink string `json:"fxLink"`
|
||||
Daily []struct {
|
||||
FxDate string `json:"fxDate"`
|
||||
Sunrise string `json:"sunrise"`
|
||||
Sunset string `json:"sunset"`
|
||||
Moonrise string `json:"moonrise"`
|
||||
Moonset string `json:"moonset"`
|
||||
MoonPhase string `json:"moonPhase"`
|
||||
MoonPhaseIcon string `json:"moonPhaseIcon"`
|
||||
TempMax string `json:"tempMax"`
|
||||
TempMin string `json:"tempMin"`
|
||||
IconDay string `json:"iconDay"`
|
||||
TextDay string `json:"textDay"`
|
||||
IconNight string `json:"iconNight"`
|
||||
TextNight string `json:"textNight"`
|
||||
Wind360Day string `json:"wind360Day"`
|
||||
WindDirDay string `json:"windDirDay"`
|
||||
WindScaleDay string `json:"windScaleDay"`
|
||||
WindSpeedDay string `json:"windSpeedDay"`
|
||||
Wind360Night string `json:"wind360Night"`
|
||||
WindDirNight string `json:"windDirNight"`
|
||||
WindScaleNight string `json:"windScaleNight"`
|
||||
WindSpeedNight string `json:"windSpeedNight"`
|
||||
Humidity string `json:"humidity"`
|
||||
Precip string `json:"precip"`
|
||||
Pressure string `json:"pressure"`
|
||||
Vis string `json:"vis"`
|
||||
Cloud string `json:"cloud"`
|
||||
UvIndex string `json:"uvIndex"`
|
||||
} `json:"daily"`
|
||||
Refer struct {
|
||||
Sources []string `json:"sources"`
|
||||
License []string `json:"license"`
|
||||
} `json:"refer"`
|
||||
}
|
||||
|
||||
// Weather 传递经纬度 location := "116.41,39.92"
|
||||
func Weather(location string) (we string) {
|
||||
//请求路径
|
||||
//key := ""
|
||||
requestURL := "https://devapi.qweather.com/v7/weather/3d?location=" + location + "&key=" + key
|
||||
|
||||
response, err := gclient.New().ContentJson().ContentType("application/x-www-form-urlencoded").Get(gctx.New(), requestURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var dataInfo = ""
|
||||
dataInfo = response.ReadAllString()
|
||||
return dataInfo
|
||||
}
|
||||
|
||||
type GridPointRes struct {
|
||||
Code string `json:"code"`
|
||||
UpdateTime string `json:"updateTime"`
|
||||
FxLink string `json:"fxLink"`
|
||||
Now struct {
|
||||
ObsTime string `json:"obsTime"`
|
||||
Temp string `json:"temp"`
|
||||
Icon string `json:"icon"`
|
||||
Text string `json:"text"`
|
||||
Wind360 string `json:"wind360"`
|
||||
WindDir string `json:"windDir"`
|
||||
WindScale string `json:"windScale"`
|
||||
WindSpeed string `json:"windSpeed"`
|
||||
Humidity string `json:"humidity"`
|
||||
Precip string `json:"precip"`
|
||||
Pressure string `json:"pressure"`
|
||||
Cloud string `json:"cloud"`
|
||||
Dew string `json:"dew"`
|
||||
} `json:"now"`
|
||||
Refer struct {
|
||||
Sources []string `json:"sources"`
|
||||
License []string `json:"license"`
|
||||
} `json:"refer"`
|
||||
}
|
||||
|
||||
// 格点天气 GridPoint
|
||||
func GridPoint(location string) (gp *GridPointRes, err error) {
|
||||
//请求路径
|
||||
//key := "00b60ebda96849e694cb570e3d4f5c89"
|
||||
requestURL := "https://devapi.qweather.com/v7/grid-weather/now?location=" + location + "&key=" + key
|
||||
|
||||
response, err := gclient.New().ContentJson().ContentType("application/x-www-form-urlencoded").Get(gctx.New(), requestURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var dataInfo = ""
|
||||
dataInfo = response.ReadAllString()
|
||||
|
||||
gp = new(GridPointRes)
|
||||
err = json.Unmarshal([]byte(dataInfo), &gp)
|
||||
|
||||
fmt.Println(gp.Now.Icon)
|
||||
return
|
||||
}
|
621
api/v1/common/coryCommon/zip.go
Normal file
621
api/v1/common/coryCommon/zip.go
Normal file
@ -0,0 +1,621 @@
|
||||
package coryCommon
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/transform"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//解压文件、文件夹复制
|
||||
|
||||
// FileZipFunc 对文件进行解压
|
||||
func FileZipFunc(relativePath string, filenPath string, template string) (fileName string, err error) {
|
||||
// 打开压缩文件
|
||||
zipFile, err := zip.OpenReader(relativePath)
|
||||
if err != nil {
|
||||
fmt.Println("无法打开压缩文件(只支持ZIP):", err)
|
||||
return
|
||||
}
|
||||
var i = 0
|
||||
var name = ""
|
||||
//进入的压缩文件,判断压缩文件中的第一层是否包含文件,如果有就在当前随机产生一个文件夹,返回就返回到随机文件夹的位置
|
||||
randomFolder := ""
|
||||
// 遍历压缩文件中的文件头信息
|
||||
for _, f := range zipFile.File {
|
||||
//path, _ := gbkDecode(f.Name)
|
||||
fmt.Println("头? ", f.Name)
|
||||
path, _ := IsGB18030(f.Name)
|
||||
// 判断是否为顶层文件夹
|
||||
if !hasSlash(path) {
|
||||
randomFolder = FileName("/randomFolder")
|
||||
template = template + randomFolder
|
||||
filenPath = filenPath + randomFolder
|
||||
break
|
||||
}
|
||||
}
|
||||
// 遍历压缩文件中的文件
|
||||
for _, file := range zipFile.File {
|
||||
fmt.Println("ti ? ", file.Name)
|
||||
// 解决文件名编码问题
|
||||
if i == 0 {
|
||||
gb18030, _ := IsGB18030(file.Name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name = gb18030
|
||||
}
|
||||
file.Name, err = IsGB18030(file.Name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
//对所有文件的名称进行空格替换
|
||||
file.Name = strings.Replace(file.Name, " ", "", -1)
|
||||
if err != nil {
|
||||
return file.Name, err
|
||||
}
|
||||
// 获取文件的相对路径,根据原本路径来操作还是根据新路径来(filenPath)
|
||||
extractedFilePath := ""
|
||||
extractedFilePathTwo := ""
|
||||
if filenPath != "" {
|
||||
extractedFilePath = filepath.Join(FileToFunc(filenPath, 2), "/"+file.Name)
|
||||
split := strings.Split(filenPath, "/")
|
||||
extractedFilePathTwo = extractedFilePath + "/" + split[len(split)-1]
|
||||
} else {
|
||||
extractedFilePath = filepath.ToSlash(filepath.Join(GetCWD()+template+"/"+".", file.Name))
|
||||
extractedFilePathTwo = extractedFilePath + "/"
|
||||
}
|
||||
//判断文件夹是否存在,存在就退出(i==0 是为了只判断最外层那一文件夹路径)
|
||||
_, err := os.Stat(filepath.Dir(extractedFilePathTwo))
|
||||
if err == nil && i == 0 {
|
||||
zipFile.Close()
|
||||
// 删除压缩文件
|
||||
err = os.Remove(relativePath)
|
||||
err = errors.New("当前文件夹已经存在,导入无效!")
|
||||
return "", err
|
||||
}
|
||||
i = i + 1
|
||||
// 检查是否为文件
|
||||
if !file.FileInfo().IsDir() {
|
||||
// 创建文件的目录结构
|
||||
err = os.MkdirAll(filepath.Dir(extractedFilePath), os.ModePerm)
|
||||
if err != nil {
|
||||
fmt.Println("无法创建目录:", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 打开压缩文件中的文件
|
||||
zippedFile, err := file.Open()
|
||||
if err != nil {
|
||||
fmt.Println("无法打开压缩文件中的文件:", err)
|
||||
return "", err
|
||||
}
|
||||
defer zippedFile.Close()
|
||||
|
||||
// 创建目标文件
|
||||
extractedFile, err := os.Create(extractedFilePath)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("无法创建目标文件:", err)
|
||||
return "", err
|
||||
}
|
||||
defer extractedFile.Close()
|
||||
|
||||
// 将压缩文件中的内容复制到目标文件
|
||||
_, err = io.Copy(extractedFile, zippedFile)
|
||||
if err != nil {
|
||||
fmt.Println("无法解压缩文件:", err)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zipFile.Close()
|
||||
|
||||
// 删除压缩文件
|
||||
err = os.Remove(relativePath)
|
||||
if err != nil {
|
||||
fmt.Println("无法删除压缩文件:", err)
|
||||
return
|
||||
}
|
||||
fileName = strings.Split(name, "/")[0]
|
||||
if randomFolder != "" {
|
||||
fileName = ""
|
||||
} else {
|
||||
fileName = "/" + fileName
|
||||
}
|
||||
if filenPath != "" {
|
||||
return FileToFunc(filenPath, 2) + fileName, err
|
||||
} else {
|
||||
return GetCWD() + template + fileName, err
|
||||
}
|
||||
}
|
||||
|
||||
// IsGB18030 判断字符串是否是 GB18030 编码
|
||||
func IsGB18030(name string) (string, error) {
|
||||
// 创建 GB18030 解码器
|
||||
decoder := simplifiedchinese.GB18030.NewDecoder()
|
||||
// 使用 transform 解码数据
|
||||
_, err := io.ReadAll(transform.NewReader(bytes.NewReader([]byte(name)), decoder))
|
||||
if err == nil {
|
||||
return name, nil
|
||||
} else {
|
||||
fileName, errName := simplifiedchinese.GB18030.NewDecoder().String(name)
|
||||
return fileName, errName
|
||||
}
|
||||
}
|
||||
|
||||
// gbkDecode 解决文件名乱码
|
||||
func gbkDecode(s string) (string, error) {
|
||||
gbkDecoder := simplifiedchinese.GBK.NewDecoder()
|
||||
decodedName, _, err := transform.String(gbkDecoder, s)
|
||||
return decodedName, err
|
||||
}
|
||||
|
||||
type DocumentListPublicRes struct {
|
||||
Id int64 `json:"id"`
|
||||
IdStr string `json:"idStr"`
|
||||
Pid string `json:"pid"`
|
||||
Name string `json:"name"`
|
||||
FilenPath string `json:"filenPath"`
|
||||
FilenPathCoding string `json:"filenPathCoding"`
|
||||
Suffix string `json:"suffix"`
|
||||
Type string `json:"type"`
|
||||
CreateBy string `json:"createBy"`
|
||||
CreatedAt *gtime.Time `json:"createdAt"`
|
||||
IsDuplicate bool `json:"isDuplicate"`
|
||||
ProjectId int64 `json:"projectId"`
|
||||
}
|
||||
|
||||
// Traversal 遍历文件夹 ctx,绝对路径,上级文件夹(可有可无),表名,存储位置,项目id,模板1or资料2
|
||||
//
|
||||
// one, err := coryCommon.Traversal(ctx, path, req.Pid, dao.DocumentCompletion.Table(), dataFolder, req.ProjectId, "2") //遍历解压后的文件,插入数据
|
||||
// if err != nil {
|
||||
// liberr.ErrIsNil(ctx, err)
|
||||
// return
|
||||
// }
|
||||
// _, err = g.DB().Model(dao.DocumentCompletion.Table()).Ctx(ctx).Insert(one)
|
||||
// liberr.ErrIsNil(ctx, err, "新增失败!")
|
||||
func Traversal(ctx context.Context, root string, pidstr string, tableName string, templatePath string, projectId int64, num string) (dataOne []*DocumentListPublicRes, err error) {
|
||||
|
||||
template := strings.Replace(templatePath, "/resource/public", "/file", 1)
|
||||
err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 获取相对路径
|
||||
relativePath, err := filepath.Rel(root, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 根目录下创建,还是指定文件夹下面创建?
|
||||
p := ""
|
||||
if pidstr != "" {
|
||||
value, _ := g.DB().Model(tableName).Ctx(ctx).Where("id_str", pidstr).Fields("filen_path").Value()
|
||||
split := strings.Split(root, "/")
|
||||
p = value.String() + "/" + split[len(split)-1] + "/" + relativePath
|
||||
} else {
|
||||
p = template + "/" + relativePath
|
||||
}
|
||||
p = strings.ReplaceAll(p, "\\", "/")
|
||||
// 获取当前项的深度
|
||||
depth := strings.Count(relativePath, string(filepath.Separator))
|
||||
// 判断父子关系并打印结果
|
||||
if depth == 0 && info.IsDir() {
|
||||
if relativePath == "." {
|
||||
split := strings.Split(root, "/")
|
||||
n := split[len(split)-1]
|
||||
// 根目录下创建,还是指定文件夹下面创建?
|
||||
p := ""
|
||||
if pidstr != "" {
|
||||
value, _ := g.DB().Model(tableName).Ctx(ctx).Where("id_str", pidstr).Fields("filen_path").Value()
|
||||
p = value.String() + "/" + n
|
||||
} else {
|
||||
p = template + "/" + n
|
||||
}
|
||||
p = strings.ReplaceAll(p, "\\", "/")
|
||||
template = template + "/" + n
|
||||
var dataTwo = new(DocumentListPublicRes)
|
||||
dataTwo.IdStr = SHA256(p)
|
||||
if pidstr != "" {
|
||||
dataTwo.Pid = pidstr
|
||||
} else {
|
||||
dataTwo.Pid = "0"
|
||||
}
|
||||
dataTwo.Name = n
|
||||
dataTwo.FilenPath = p
|
||||
////如果文件夹路径重复,就提示 解压文件夹的时候就已经判断了,这里就不需要了
|
||||
//err := IsFolderExist(ctx, p)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
dataTwo.Type = "2"
|
||||
dataOne = append(dataOne, dataTwo)
|
||||
} else {
|
||||
dir, n := filepath.Split(p)
|
||||
dir = strings.TrimSuffix(dir, "/")
|
||||
var dataTwo = new(DocumentListPublicRes)
|
||||
dataTwo.IdStr = SHA256(p)
|
||||
dataTwo.Pid = SHA256(dir)
|
||||
dataTwo.Name = n
|
||||
dataTwo.FilenPath = p
|
||||
////如果文件夹路径重复,就提示
|
||||
//err := IsFolderExist(ctx, p)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
dataTwo.Type = "2"
|
||||
dataOne = append(dataOne, dataTwo)
|
||||
}
|
||||
} else if info.IsDir() {
|
||||
// 子文件夹
|
||||
dir, n := filepath.Split(p)
|
||||
dir = strings.TrimSuffix(dir, "/")
|
||||
var dataTwo = new(DocumentListPublicRes)
|
||||
dataTwo.IdStr = SHA256(p)
|
||||
dataTwo.Pid = SHA256(dir)
|
||||
dataTwo.Name = n
|
||||
dataTwo.FilenPath = p
|
||||
dataTwo.Type = "2"
|
||||
dataOne = append(dataOne, dataTwo)
|
||||
} else {
|
||||
dir, n := filepath.Split(p)
|
||||
dir = strings.TrimSuffix(dir, "/")
|
||||
var dataTwo = new(DocumentListPublicRes)
|
||||
dataTwo.Pid = SHA256(dir)
|
||||
lastDotIndex := strings.LastIndex(n, ".")
|
||||
if lastDotIndex == -1 || lastDotIndex == 0 {
|
||||
dataTwo.Name = strings.Split(n, ".")[0]
|
||||
} else {
|
||||
dataTwo.Name = n[:lastDotIndex]
|
||||
}
|
||||
dataTwo.Suffix = n[lastDotIndex:]
|
||||
dataTwo.FilenPath = p
|
||||
dataTwo.Type = "1"
|
||||
//文件只能是这三种类型,其他类型进不来
|
||||
s := n[lastDotIndex:]
|
||||
if num == "1" { //资料有格式限制
|
||||
if strings.EqualFold(s, ".xls") || strings.EqualFold(s, ".xlsx") || strings.EqualFold(s, ".docx") || strings.EqualFold(s, ".doc") || strings.EqualFold(s, ".pptx") || strings.EqualFold(s, ".ppt") {
|
||||
dataOne = append(dataOne, dataTwo)
|
||||
}
|
||||
} else {
|
||||
dataOne = append(dataOne, dataTwo)
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
//fmt.Println("遍历文件夹时发生错误:", err)
|
||||
return nil, err
|
||||
}
|
||||
// 有项目id表示资料 无表模板
|
||||
if projectId > 0 {
|
||||
for i := range dataOne {
|
||||
dataOne[i].ProjectId = projectId
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SHA256(str string) (hx string) {
|
||||
// 创建 SHA-256 哈希对象
|
||||
hash := sha256.New()
|
||||
// 将字符串转换为字节数组并进行哈希计算
|
||||
hash.Write([]byte(str))
|
||||
// 计算 SHA-256 哈希值
|
||||
hashedBytes := hash.Sum(nil)
|
||||
// 将哈希值转换为十六进制字符串
|
||||
hashStr := hex.EncodeToString(hashedBytes)
|
||||
return hashStr
|
||||
}
|
||||
|
||||
// CopyFile 文件复制
|
||||
func CopyFile(src, dst string) error {
|
||||
// 打开源文件
|
||||
inputFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer inputFile.Close()
|
||||
|
||||
// 创建目标文件
|
||||
outputFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outputFile.Close()
|
||||
|
||||
// 通过 Copy 函数实现拷贝功能
|
||||
_, err = io.Copy(outputFile, inputFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 确保文件内容被刷新到磁盘上
|
||||
err = outputFile.Sync()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyDirectory 文件夹复制
|
||||
func CopyDirectory(src string, dest string) error {
|
||||
// 检查源文件夹是否存在
|
||||
_, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 检查目标文件夹是否存在,不存在则创建
|
||||
err = os.MkdirAll(dest, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 遍历源文件夹
|
||||
files, err := os.ReadDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, file := range files {
|
||||
srcPath := src + "/" + file.Name()
|
||||
destPath := dest + "/" + file.Name()
|
||||
|
||||
// 判断文件类型
|
||||
if file.IsDir() {
|
||||
// 如果是文件夹,则递归调用 copyDirectory 函数复制文件夹及其子文件
|
||||
err = CopyDirectory(srcPath, destPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// 如果是文件,则复制文件到目标文件夹
|
||||
inputFile, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer inputFile.Close()
|
||||
|
||||
outputFile, err := os.Create(destPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outputFile.Close()
|
||||
|
||||
_, err = io.Copy(outputFile, inputFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MoveFile 文件移动
|
||||
func MoveFile(source, destination string) (err error) {
|
||||
// 执行移动操作
|
||||
err = os.Rename(source, destination)
|
||||
if err != nil {
|
||||
fmt.Println("Error:", err)
|
||||
} else {
|
||||
fmt.Println("File moved successfully!")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// MoveFolder 文件夹下面的文件及子文件全部移动到新文件夹下
|
||||
func MoveFolder(srcPath, destPath string) error {
|
||||
// 获取源文件夹下的所有文件和子文件夹
|
||||
fileList := []string{}
|
||||
err := filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error {
|
||||
fileList = append(fileList, path)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 移动每个文件和子文件夹
|
||||
for _, file := range fileList {
|
||||
// 获取相对路径
|
||||
relPath, err := filepath.Rel(srcPath, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 构建目标路径
|
||||
destFile := filepath.Join(destPath, relPath)
|
||||
// 判断是文件还是文件夹
|
||||
if fileInfo, err := os.Stat(file); err == nil && fileInfo.IsDir() {
|
||||
// 如果是文件夹,创建目标文件夹
|
||||
err := os.MkdirAll(destFile, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// 如果是文件,复制文件
|
||||
err := copyFile(file, destFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移动完成后删除源文件夹
|
||||
return os.RemoveAll(srcPath)
|
||||
}
|
||||
|
||||
func copyFile(src, dest string) error {
|
||||
srcFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
destFile, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
_, err = io.Copy(destFile, srcFile)
|
||||
return err
|
||||
}
|
||||
|
||||
// FolderToZip 将给定文件夹压缩成压缩包存储到另外一个路径(参数:源数据、目标路径)
|
||||
func FolderToZip(folderToZip, zipFile string) (err error) {
|
||||
// 创建一个新的压缩包文件
|
||||
newZipFile, err := os.Create(zipFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer newZipFile.Close()
|
||||
// 创建一个 zip.Writer 来向压缩包中写入内容
|
||||
zipWriter := zip.NewWriter(newZipFile)
|
||||
defer zipWriter.Close()
|
||||
|
||||
// 递归遍历文件夹并将其中的文件和目录添加到压缩包中
|
||||
err = filepath.Walk(folderToZip, func(filePath string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 获取当前文件的相对路径
|
||||
relativePath, err := filepath.Rel(folderToZip, filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 如果是目录,则创建一个目录项
|
||||
if info.IsDir() {
|
||||
_, err = zipWriter.Create(relativePath + "/")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// 如果是文件,则创建一个文件项并写入文件内容
|
||||
fileData, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := zipWriter.Create(relativePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = file.Write(fileData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// zipDirFiles 压缩文件
|
||||
func ZipDirFiles(src_dir string, zip_file_name string) error {
|
||||
// 检查并创建目标目录
|
||||
err := os.MkdirAll(filepath.Dir(zip_file_name), os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 删除空的zip,而不是直接使用os.RemoveAll,以提高安全性
|
||||
err = os.Remove(zip_file_name)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建新的zip文件
|
||||
zipfile, err := os.Create(zip_file_name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer zipfile.Close()
|
||||
|
||||
// 初始化zip写入器
|
||||
archive := zip.NewWriter(zipfile)
|
||||
defer archive.Close()
|
||||
|
||||
// 遍历源目录下的文件和子目录
|
||||
return filepath.Walk(src_dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 计算相对于源目录的路径
|
||||
relPath, _ := filepath.Rel(src_dir, path)
|
||||
if path == src_dir {
|
||||
// 如果是源目录本身,跳过
|
||||
return nil
|
||||
}
|
||||
|
||||
// 创建zip文件头
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header.Name = filepath.ToSlash(relPath)
|
||||
|
||||
// 标记目录
|
||||
if info.IsDir() {
|
||||
header.Name += "/"
|
||||
return nil
|
||||
}
|
||||
|
||||
// 处理文件
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
writer, err := archive.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(writer, file)
|
||||
return err
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// 压缩后删除源文件
|
||||
func DeleteFolderToZip(srcpath, destpathzip string) (err error) {
|
||||
//srcpath := filepath.ToSlash(`D:\GiteeProject\gsproject\zmkg-back\resource\public\temporary\del`)
|
||||
//destpathzip := filepath.ToSlash(`D:\GiteeProject\gsproject\zmkg-back\resource\public\temporary\yy.zip`)
|
||||
err = ZipDirFiles(srcpath, destpathzip) // 文件压缩 压缩文件目录 和压缩文件zip
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 删除原文件
|
||||
if err := os.RemoveAll(srcpath); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
fmt.Printf("Error removing existing file %s: %v\n", srcpath, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 判断字符串中是否包含斜杠
|
||||
func hasSlash(s string) bool {
|
||||
for _, c := range s {
|
||||
if c == '/' || c == '\\' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
278
api/v1/common/fileUpload/upload.go
Normal file
278
api/v1/common/fileUpload/upload.go
Normal 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")
|
||||
}
|
96
api/v1/common/globe/globe.go
Normal file
96
api/v1/common/globe/globe.go
Normal 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
25
api/v1/common/req.go
Normal 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
21
api/v1/common/res.go
Normal 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
254
api/v1/common/shp/shp.go
Normal 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)
|
||||
}
|
133
api/v1/common/source/clt/clt.go
Normal file
133
api/v1/common/source/clt/clt.go
Normal 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
|
||||
|
||||
}
|
123
api/v1/common/source/mbt/mbt.go
Normal file
123
api/v1/common/source/mbt/mbt.go
Normal 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
|
||||
}
|
148
api/v1/common/source/pak/pak.go
Normal file
148
api/v1/common/source/pak/pak.go
Normal 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
|
||||
}
|
295
api/v1/common/source/shp/shp.go
Normal file
295
api/v1/common/source/shp/shp.go
Normal 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
|
||||
})
|
||||
}
|
85
api/v1/common/source/source.go
Normal file
85
api/v1/common/source/source.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
49
api/v1/common/tool/excel/excel.go
Normal file
49
api/v1/common/tool/excel/excel.go
Normal 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
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user