docs:初始化go-view-server

This commit is contained in:
黄明 2024-07-23 13:50:17 +08:00
commit 92f5e57614
113 changed files with 5389 additions and 0 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* linguist-language=GO

22
.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
.buildpath
.hgignore.swp
.project
.orig
.swp
.idea/
.settings/
.vscode/
bin/
**/.DS_Store
gf
main
main.exe
main.exe~
output/
manifest/output/
temp/
temp.yaml
bin
resource/public/attachment
storage/cache
logs/

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021-present HotGo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

16
Makefile Normal file
View File

@ -0,0 +1,16 @@
ROOT_DIR = $(shell pwd)
NAMESPACE = "default"
DEPLOY_NAME = "template-single"
DOCKER_NAME = "template-single"
include ./hack/hack.mk
# 通过热编译启动所有服务
.PHONY: all
all:
gf run main.go --args "all"
.PHONY: http
http:
gf run main.go

215
README.MD Normal file
View File

@ -0,0 +1,215 @@
## 项目介绍
- 本项目是由 `GoLang` 实现的 `GoView` 后端接口基于 `GoFrame` 开发
- `GoView` 是一个高效的拖拽式低代码数据可视化开发平台将图表或页面元素封装为基础组件无需编写代码即可制作数据大屏减少心智负担当然低代码也不是 银弹希望所有人员都能理智看待此技术
- 如果在使用中遇到问题请提交 [issues](https://gitee.com/bufanyun/go-view-server/issues/new) 或联系作者
- 作者QQ133814250
### 主要技术栈
| 名称 | 版本 |
| ------------------- | ------ |
| goframe | 2.6.3 |
| golang-jwt | 5.2.0 |
| 详见 `go.mod` | 😁 |
### 运行环境
1. 下载golang安装 版本号需>=1.19
2. 国际: https://golang.org/dl/
3. 国内: https://golang.google.cn/dl/
4. 命令行运行 go 若控制台输出各类提示命令 则安装成功 输入 `go version` 确认版本大于1.19
5. 开发工具推荐 [Goland](https://www.jetbrains.com/go/)
### 使用说明
> 需要本地具有 git node golang mysql 环境
- node版本 16.14.x
- golang版本 >= v1.19
- mysql版本 >=5.7
- IDE推荐Goland
### 安装go-view-server
克隆项目
```
git clone https://gitee.com/bufanyun/go-view-server.git
```
初始化数据库
- 项目数据库文件 [storage/data/goview.sql](storage/data/goview.sql) 创建数据库并导入
- `manifest/config/config.yaml`中的数据库配置改为你自己的
```yaml
database:
logger:
path: "logs/database" # 日志文件路径默认为空表示关闭仅输出到终端
<<: *defaultLogger
stdout: true
default:
link: "mysql:goview:nWkG43Xxnbi2Y44C@tcp(127.0.0.1:3306)/goview?loc=Local&parseTime=true&charset=utf8mb4"
debug: true
Prefix: "hg_"
```
启动服务
```shell script
# 设置国内代理如果已经设置好了代理可以跳过
go env -w GOPROXY=https://goproxy.io,direct
# 更新包
go mod tidy
# 启动所有服务
go run main.go # 热编译启动 gf run main.go
```
## 安装go-view
克隆项目
- 注意`master-fetch` 分支是带有后端接口请求的分支
```
git clone --branch master-fetch https://gitee.com/dromara/go-view.git
```
修改和`go-view-server`一致的端口号
- 接口地址修改.env
```env
# port
VITE_DEV_PORT = '8080'
# development path
VITE_DEV_PATH = 'http://127.0.0.1:8090'
# production path
VITE_PRO_PATH = 'http://127.0.0.1:8090'
```
启动服务
- 安装
- 推荐使用 pnpm 管理项目并使用 nrm 切换到阿里镜像整体安装步骤如下
```shell script
# 1. 安装 pnpm
npm install -g pnpm
# 2. 安装 nrm
npm install -g nrm
# 3. 使用 nrm 添加阿里镜像
nrm add taobao https://registry.npmmirror.com/
# 4. nrm 查看镜像列表
nrm ls
# 5. nrm 应用对应镜像
nrm use taobao
```
- 安装项目依赖
```shell script
# 推荐使用 pnpm
pnpm install
# yarn
yarn install
```
- 启动
```shell script
# 推荐使用 pnpm
pnpm dev
# yarn
yarn dev
# Makefile需要自行配置系统环境谷歌 make 命令环境搭建
make dev
```
- 编译
```shell script
# 推荐使用 pnpm
pnpm run build
# yarn
yarn run build
# Makefile
make dist
```
登录go-view
- 如果一切正常启动 `go-view` 后可以通过浏览器打开下方地址进入 `go-view` 登录页面
```
登录地址http://localhost:3000/
账号admin
密码admin
```
## 测试项目
- 在满足 `go-view` 后台接口使用的基础上我们内置了一个测试项目和测试设备数据接口方便日常配置大屏和报表的数据调试需求
- 测试接口获取测试数据
```http request
GET http://127.0.0.1:8090/api/goview/sys/testData
```
- 响应数据内容
- `data.deviceInfo` 为模拟的设备基本信息数据固定
- `data.deviceData` 为模拟的设备动态上报数据数据和数值类型每次请求都会随机生成
```json
{
"code": 200,
"data": {
"deviceInfo": {
"GPSLatitude": "-42.12345",
"GPSLongitude": "73.56789",
"deviceId": "IoTDevice001",
"deviceStatus": true,
"deviceTime": "2024-03-04 10:38:20",
"location": "河南郑州高新区科学大道001号"
},
"deviceData": {
"airQuality": 66,
"ambientLight": 22.13,
"current": 66.3,
"deviceCurrent": 87.85,
"deviceTemperature": 21,
"deviceVibration": 62.04,
"deviceVoltage": 70,
"factoryData": "WQLZbKQf",
"humidity": "QMEHBqmT",
"noiseLevel": 14,
"powerConsumption": 5.18,
"pressure": "mOiEPEiZ",
"temperature": 8,
"vibration": "DPemPsNi",
"voltage": "YLkSKFXm",
"waterLevel": 30,
"windSpeed": 62.51
}
},
"msg": "获取设备数据成功"
}
```
## 相关开源地址
- [go-view](https://gitee.com/dromara/go-view)
- [goframe](https://gitee.com/johng/gf)
- [go-view-server](https://gitee.com/bufanyun/go-view-server)
## 相关文档
- [go-view](https://www.mtruning.club/)
- [goframe](https://goframe.org/)

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

@ -0,0 +1,22 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package project
import (
"context"
"hotgo/api/project/v1"
)
type IProjectV1 interface {
List(ctx context.Context, req *v1.ListReq) (res *v1.ListRes, err error)
GetData(ctx context.Context, req *v1.GetDataReq) (res *v1.GetDataRes, err error)
Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error)
Edit(ctx context.Context, req *v1.EditReq) (res *v1.EditRes, err error)
SaveData(ctx context.Context, req *v1.SaveDataReq) (res *v1.SaveDataRes, err error)
Delete(ctx context.Context, req *v1.DeleteReq) (res *v1.DeleteRes, err error)
Publish(ctx context.Context, req *v1.PublishReq) (res *v1.PublishRes, err error)
Upload(ctx context.Context, req *v1.UploadReq) (res *v1.UploadRes, err error)
}

84
api/project/v1/project.go Normal file
View File

@ -0,0 +1,84 @@
// Package v1
// @Description
// @Author Ms <133814250@qq.com>
package v1
import (
"github.com/gogf/gf/v2/frame/g"
"hotgo/internal/model/input/adminin"
"hotgo/internal/model/input/form"
)
// ListReq 查询列表
type ListReq struct {
g.Meta `path:"/project/list" method:"get" tags:"项目" summary:"获取项目列表"`
adminin.ProjectListInp
}
type ListRes struct {
g.Meta `mime:"custom"`
List []*adminin.ProjectListModel `json:"list" dc:"数据列表"`
form.PageRes
}
// GetDataReq 获取信息
type GetDataReq struct {
g.Meta `path:"/project/getData" method:"get" tags:"项目" summary:"获取指定信息"`
adminin.ProjectGetDataInp
}
type GetDataRes struct {
*adminin.ProjectGetDataModel
}
// CreateReq 新增项目
type CreateReq struct {
g.Meta `path:"/project/create" method:"post" tags:"项目" summary:"新增项目"`
adminin.ProjectCreateInp
}
type CreateRes struct {
*adminin.ProjectCreateModel
}
// EditReq 修改/新增
type EditReq struct {
g.Meta `path:"/project/edit" method:"post" tags:"项目" summary:"修改/新增项目"`
adminin.ProjectEditInp
}
type EditRes struct{}
// SaveDataReq 保存项目数据
type SaveDataReq struct {
g.Meta `path:"/project/save/data" method:"post" tags:"项目" summary:"保存项目数据"`
adminin.ProjectSaveDataInp
}
type SaveDataRes struct{}
// DeleteReq 删除
type DeleteReq struct {
g.Meta `path:"/project/delete" method:"post" tags:"项目" summary:"删除项目"`
adminin.ProjectDeleteInp
}
type DeleteRes struct{}
// PublishReq 修改发布状态
type PublishReq struct {
g.Meta `path:"/project/publish" method:"put" tags:"项目" summary:"修改发布状态"`
adminin.ProjectPublishInp
}
type PublishRes struct{}
// UploadReq 文件上传
type UploadReq struct {
g.Meta `path:"/project/upload" method:"post" tags:"项目" summary:"文件上传"`
adminin.ProjectUploadInp
}
type UploadRes struct {
FileUrl string `json:"fileurl"`
}

19
api/site/site.go Normal file
View File

@ -0,0 +1,19 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package site
import (
"context"
"hotgo/api/site/v1"
)
type ISiteV1 interface {
Register(ctx context.Context, req *v1.RegisterReq) (res *v1.RegisterRes, err error)
AccountLogin(ctx context.Context, req *v1.AccountLoginReq) (res *v1.AccountLoginRes, err error)
LoginLogout(ctx context.Context, req *v1.LoginLogoutReq) (res *v1.LoginLogoutRes, err error)
GetOssInfo(ctx context.Context, req *v1.GetOssInfoReq) (res *v1.GetOssInfoRes, err error)
TestData(ctx context.Context, req *v1.TestDataReq) (res *v1.TestDataRes, err error)
}

56
api/site/v1/site.go Normal file
View File

@ -0,0 +1,56 @@
// Package v1
// @Description
// @Author Ms <133814250@qq.com>
package v1
import (
"github.com/gogf/gf/v2/frame/g"
"hotgo/internal/model/input/adminin"
)
// RegisterReq 提交账号注册
type RegisterReq struct {
g.Meta `path:"/sys/register" method:"post" tags:"后台基础" summary:"账号注册"`
adminin.RegisterInp
}
type RegisterRes struct {
*adminin.LoginModel
}
// AccountLoginReq 提交账号登录
type AccountLoginReq struct {
g.Meta `path:"/sys/login" method:"post" tags:"后台基础" summary:"账号登录"`
adminin.AccountLoginInp
}
type AccountLoginRes struct {
*adminin.LoginModel
}
// LoginLogoutReq 注销登录
type LoginLogoutReq struct {
g.Meta `path:"/sys/logout" method:"get" tags:"后台基础" summary:"注销登录"`
}
type LoginLogoutRes struct{}
// GetOssInfoReq 获取文件上传oss信息
type GetOssInfoReq struct {
g.Meta `path:"/sys/getOssInfo" method:"get" tags:"后台基础" summary:"获取文件上传oss信息"`
}
type GetOssInfoRes struct {
BucketURL string `json:"bucketUrl"`
}
// TestDataReq 模拟测试数据
type TestDataReq struct {
g.Meta `path:"/sys/testData" method:"all" tags:"后台基础" summary:"模拟测试数据"`
}
type TestDataRes struct {
g.Meta `mime:"custom"`
DeviceInfo map[string]interface{} `json:"deviceInfo" dc:"设备信息"`
DeviceData map[string]interface{} `json:"deviceData" dc:"随机设备数值"`
}

43
go.mod Normal file
View File

@ -0,0 +1,43 @@
module hotgo
go 1.21
require (
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.6.3
github.com/gogf/gf/contrib/nosql/redis/v2 v2.7.2
github.com/gogf/gf/v2 v2.7.2
github.com/golang-jwt/jwt/v5 v5.2.0
)
require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/redis/go-redis/v9 v9.5.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.8.0 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

98
go.sum Normal file
View File

@ -0,0 +1,98 @@
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.6.3 h1:dRGGKKiT9FEnxhfHFerojy34uCKHgReKgpMbAOtqhsY=
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.6.3/go.mod h1:sGdaCPgN1AY0tho+WYAgYdUHJkXwuDf76M3ASgHXWRQ=
github.com/gogf/gf/contrib/nosql/redis/v2 v2.7.2 h1:V1hdGnyjU9kT0I3DDDFDl6Ll8yC6aAIFJa/lMQwB8V4=
github.com/gogf/gf/contrib/nosql/redis/v2 v2.7.2/go.mod h1:XzkPv3G8TdKczqoB/ydR3bxvBRdQLQNCOCEgxso/c3o=
github.com/gogf/gf/v2 v2.7.2 h1:uZDfyblasI12lZUtFd1mfxsIr8b14cd/F88DJUTCSDM=
github.com/gogf/gf/v2 v2.7.2/go.mod h1:EBXneAg/wes86rfeh68XC0a2JBNQylmT7Sp6/8Axk88=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

30
hack/config.yaml Normal file
View File

@ -0,0 +1,30 @@
# CLI tool, only in development environment.
# https://goframe.org/pages/viewpage.action?pageId=3673173
gfcli:
build:
name: "goview" # 编译后的可执行文件名称
# arch: "all" #不填默认当前系统架构可选386,amd64,arm,all
# system: "all" #不填默认当前系统平台可选linux,darwin,windows,all
mod: "none"
cgo: 0
# packSrc: "resource" # 将resource目录打包进可执行文件静态资源无需单独部署
# packDst: "internal/packed/packed.go" # 打包后生成的Go文件路径一般使用相对路径指定到本项目目录中
version: ""
output: "./temp/goview" # 可执行文件生成路径
extra: ""
docker:
build: "-a amd64 -s linux -p temp -ew"
tagPrefixes:
- my.image.pub/my-app
gen:
dao:
- link: "mysql:goview:nWkG43Xxnbi2Y44C@tcp(127.0.0.1:3306)/goview?loc=Local&parseTime=true"
removePrefix: "hg_"
descriptionTag: true
noModelComment: true
jsonCase: "CamelLower"
gJsonSupport: true
clear: false

19
hack/hack-cli.mk Normal file
View File

@ -0,0 +1,19 @@
# Install/Update to the latest CLI tool.
.PHONY: cli
cli:
@set -e; \
wget -O gf https://github.com/gogf/gf/releases/latest/download/gf_$(shell go env GOOS)_$(shell go env GOARCH) && \
chmod +x gf && \
./gf install -y && \
rm ./gf
# Check and install CLI tool.
.PHONY: cli.install
cli.install:
@set -e; \
gf -v > /dev/null 2>&1 || if [[ "$?" -ne "0" ]]; then \
echo "GoFame CLI is not installed, start proceeding auto installation..."; \
make cli; \
fi;

75
hack/hack.mk Normal file
View File

@ -0,0 +1,75 @@
include ./hack/hack-cli.mk
# Update GoFrame and its CLI to latest stable version.
.PHONY: up
up: cli.install
@gf up -a
# Build binary using configuration from hack/config.yaml.
.PHONY: build
build: cli.install
@gf build -ew
# Parse api and generate controller/sdk.
.PHONY: ctrl
ctrl: cli.install
@gf gen ctrl
# Generate Go files for DAO/DO/Entity.
.PHONY: dao
dao: cli.install
@gf gen dao
# Parse current project go files and generate enums go file.
.PHONY: enums
enums: cli.install
@gf gen enums
# Generate Go files for Service.
.PHONY: service
service: cli.install
@gf gen service
# Build docker image.
.PHONY: image
image: cli.install
$(eval _TAG = $(shell git describe --dirty --always --tags --abbrev=8 --match 'v*' | sed 's/-/./2' | sed 's/-/./2'))
ifneq (, $(shell git status --porcelain 2>/dev/null))
$(eval _TAG = $(_TAG).dirty)
endif
$(eval _TAG = $(if ${TAG}, ${TAG}, $(_TAG)))
$(eval _PUSH = $(if ${PUSH}, ${PUSH}, ))
@gf docker ${_PUSH} -tn $(DOCKER_NAME):${_TAG};
# Build docker image and automatically push to docker repo.
.PHONY: image.push
image.push:
@make image PUSH=-p;
# Deploy image and yaml to current kubectl environment.
.PHONY: deploy
deploy:
$(eval _TAG = $(if ${TAG}, ${TAG}, develop))
@set -e; \
mkdir -p $(ROOT_DIR)/temp/kustomize;\
cd $(ROOT_DIR)/manifest/deploy/kustomize/overlays/${_ENV};\
kustomize build > $(ROOT_DIR)/temp/kustomize.yaml;\
kubectl apply -f $(ROOT_DIR)/temp/kustomize.yaml; \
if [ $(DEPLOY_NAME) != "" ]; then \
kubectl patch -n $(NAMESPACE) deployment/$(DEPLOY_NAME) -p "{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"date\":\"$(shell date +%s)\"}}}}}"; \
fi;
# Parsing protobuf files and generating go files.
.PHONY: pb
pb: cli.install
@gf gen pb
# Generate protobuf files for database tables.
.PHONY: pbentity
pbentity: cli.install
@gf gen pbentity

54
internal/cmd/cmd.go Normal file
View File

@ -0,0 +1,54 @@
package cmd
import (
"context"
"hotgo/internal/controller/project"
"hotgo/internal/controller/site"
"hotgo/internal/library/cache"
"hotgo/internal/library/storager"
"hotgo/internal/library/token"
"hotgo/internal/service"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gcmd"
)
var (
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
// 设置缓存适配器
cache.SetAdapter(ctx)
token.InitConfig(ctx)
storager.InitConfig(ctx)
s := g.Server()
// 注册全局中间件
s.BindMiddleware("/*any", []ghttp.HandlerFunc{
service.Middleware().Ctx, // 初始化请求上下文,一般需要第一个进行加载,后续中间件存在依赖关系
service.Middleware().CORS, // 跨域中间件,自动处理跨域问题
service.Middleware().PreFilter, // 请求输入预处理api使用gf规范路由并且XxxReq结构体实现了validate.Filter接口即可隐式预处理
service.Middleware().ResponseHandler, // HTTP响应预处理在业务处理完成后对响应结果进行格式化和错误过滤将处理后的数据发送给请求方
}...)
s.Group("/api/goview", func(group *ghttp.RouterGroup) {
group.Bind(
site.NewV1(),
)
group.Middleware(service.Middleware().AdminAuth)
group.Bind(
project.NewV1(),
)
})
s.Run()
return nil
},
}
)

12
internal/consts/app.go Normal file
View File

@ -0,0 +1,12 @@
// Package consts
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package consts
// 应用类型
const (
AppAdmin = "admin"
AppDefault = "default"
)

12
internal/consts/cache.go Normal file
View File

@ -0,0 +1,12 @@
// Package consts
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package consts
// cache
const (
CacheToken = "token" // 登录token
CacheTokenBind = "token_bind" // 登录用户身份绑定
)

View File

@ -0,0 +1,14 @@
// Package consts
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package consts
type CtxKey string
// ContextKey 上下文
const (
ContextHTTPKey CtxKey = "httpContext" // http上下文变量名称
)

14
internal/consts/debris.go Normal file
View File

@ -0,0 +1,14 @@
// Package consts
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package consts
// 碎片
// curd.
const (
DefaultPage = 10 // 默认列表分页加载数量
DefaultPageSize = 1 // 默认列表分页加载页码
)

35
internal/consts/error.go Normal file
View File

@ -0,0 +1,35 @@
// Package consts
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package consts
import (
"github.com/gogf/gf/v2/text/gstr"
)
// 错误解释
const (
ErrorORM = "sql执行异常"
ErrorNotData = "数据不存在"
ErrorRotaPointer = "指针转换异常"
)
// 需要隐藏真实错误的Wrap开启访问日志后仍然会将真实错误记录
var concealErrorSlice = []string{ErrorORM, ErrorRotaPointer}
// ErrorMessage 错误显示信息非debug模式有效
func ErrorMessage(err error) (message string) {
if err == nil {
return "操作失败!~"
}
message = err.Error()
for _, e := range concealErrorSlice {
if gstr.Contains(message, e) {
return "操作失败,请稍后重试!~"
}
}
return
}

19
internal/consts/http.go Normal file
View File

@ -0,0 +1,19 @@
// Package consts
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package consts
const (
HTTPContentTypeXml = "text/xml"
HTTPContentTypeHtml = "text/html"
HTTPContentTypeStream = "text/event-stream"
HTTPContentTypeJson = "application/json"
HTTPContentTypeCustom = "custom"
)
const (
ApiStatusOk = 200
ApiStatusTokenOverdue = 886
)

14
internal/consts/status.go Normal file
View File

@ -0,0 +1,14 @@
// Package consts
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package consts
// 状态码
const (
StatusALL int = -1 // 全部状态
StatusEnabled int = 1 // 启用
StatusDisable int = 2 // 禁用
StatusDelete int = 3 // 已删除
)

12
internal/consts/upload.go Normal file
View File

@ -0,0 +1,12 @@
// Package consts
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package consts
// 上传存储驱动
const (
UploadDriveLocal = "local" // 本地驱动
)

View File

@ -0,0 +1,5 @@
// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================
package project

View File

@ -0,0 +1,15 @@
// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================
package project
import (
"hotgo/api/project"
)
type ControllerV1 struct{}
func NewV1() project.IProjectV1 {
return &ControllerV1{}
}

View File

@ -0,0 +1,14 @@
package project
import (
"context"
"hotgo/internal/service"
"hotgo/api/project/v1"
)
func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) {
res = new(v1.CreateRes)
res.ProjectCreateModel, err = service.AdminProject().Create(ctx, &req.ProjectCreateInp)
return
}

View File

@ -0,0 +1,14 @@
package project
import (
"context"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"hotgo/api/project/v1"
)
func (c *ControllerV1) Delete(ctx context.Context, req *v1.DeleteReq) (res *v1.DeleteRes, err error) {
return nil, gerror.NewCode(gcode.CodeNotImplemented)
}

View File

@ -0,0 +1,13 @@
package project
import (
"context"
"hotgo/internal/service"
"hotgo/api/project/v1"
)
func (c *ControllerV1) Edit(ctx context.Context, req *v1.EditReq) (res *v1.EditRes, err error) {
err = service.AdminProject().Edit(ctx, &req.ProjectEditInp)
return
}

View File

@ -0,0 +1,14 @@
package project
import (
"context"
"hotgo/internal/service"
"hotgo/api/project/v1"
)
func (c *ControllerV1) GetData(ctx context.Context, req *v1.GetDataReq) (res *v1.GetDataRes, err error) {
res = new(v1.GetDataRes)
res.ProjectGetDataModel, err = service.AdminProject().GetData(ctx, &req.ProjectGetDataInp)
return
}

View File

@ -0,0 +1,26 @@
package project
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"hotgo/internal/library/response"
"hotgo/internal/service"
"hotgo/api/project/v1"
)
func (c *ControllerV1) List(ctx context.Context, req *v1.ListReq) (res *v1.ListRes, err error) {
list, count, err := service.AdminProject().List(ctx, &req.ProjectListInp)
if err != nil {
return nil, err
}
response.CustomJson(ghttp.RequestFromCtx(ctx), g.Map{
"code": 200,
"count": count,
"data": list,
"msg": "获取成功",
})
return
}

View File

@ -0,0 +1,13 @@
package project
import (
"context"
"hotgo/internal/service"
"hotgo/api/project/v1"
)
func (c *ControllerV1) Publish(ctx context.Context, req *v1.PublishReq) (res *v1.PublishRes, err error) {
err = service.AdminProject().Publish(ctx, &req.ProjectPublishInp)
return
}

View File

@ -0,0 +1,13 @@
package project
import (
"context"
"hotgo/internal/service"
"hotgo/api/project/v1"
)
func (c *ControllerV1) SaveData(ctx context.Context, req *v1.SaveDataReq) (res *v1.SaveDataRes, err error) {
err = service.AdminProject().SaveData(ctx, &req.ProjectSaveDataInp)
return
}

View File

@ -0,0 +1,18 @@
package project
import (
"context"
"hotgo/api/project/v1"
"hotgo/internal/library/storager"
)
func (c *ControllerV1) Upload(ctx context.Context, req *v1.UploadReq) (res *v1.UploadRes, err error) {
attachment, err := storager.DoUpload(ctx, "default", req.File)
if err != nil {
return
}
res = new(v1.UploadRes)
res.FileUrl = storager.LastUrl(ctx, attachment.FileUrl, attachment.Drive)
return
}

View File

@ -0,0 +1,5 @@
// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================
package site

View File

@ -0,0 +1,15 @@
// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================
package site
import (
"hotgo/api/site"
)
type ControllerV1 struct{}
func NewV1() site.ISiteV1 {
return &ControllerV1{}
}

View File

@ -0,0 +1,19 @@
package site
import (
"context"
"github.com/gogf/gf/v2/util/gconv"
"hotgo/internal/service"
"hotgo/api/site/v1"
)
func (c *ControllerV1) AccountLogin(ctx context.Context, req *v1.AccountLoginReq) (res *v1.AccountLoginRes, err error) {
model, err := service.AdminSite().AccountLogin(ctx, &req.AccountLoginInp)
if err != nil {
return
}
err = gconv.Scan(model, &res)
return
}

View File

@ -0,0 +1,14 @@
package site
import (
"context"
"hotgo/utility/url"
"hotgo/api/site/v1"
)
func (c *ControllerV1) GetOssInfo(ctx context.Context, req *v1.GetOssInfoReq) (res *v1.GetOssInfoRes, err error) {
res = new(v1.GetOssInfoRes)
res.BucketURL = url.GetAddr(ctx)
return
}

View File

@ -0,0 +1,14 @@
package site
import (
"context"
"github.com/gogf/gf/v2/net/ghttp"
"hotgo/internal/library/token"
"hotgo/api/site/v1"
)
func (c *ControllerV1) LoginLogout(ctx context.Context, req *v1.LoginLogoutReq) (res *v1.LoginLogoutRes, err error) {
err = token.Logout(ghttp.RequestFromCtx(ctx))
return
}

View File

@ -0,0 +1,13 @@
package site
import (
"context"
"hotgo/internal/service"
"hotgo/api/site/v1"
)
func (c *ControllerV1) Register(ctx context.Context, req *v1.RegisterReq) (res *v1.RegisterRes, err error) {
err = service.AdminSite().Register(ctx, &req.RegisterInp)
return
}

View File

@ -0,0 +1,48 @@
package site
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gtime"
"hotgo/internal/library/response"
"hotgo/utility/format"
"math/rand"
"hotgo/api/site/v1"
)
func (c *ControllerV1) TestData(ctx context.Context, req *v1.TestDataReq) (res *v1.TestDataRes, err error) {
res = new(v1.TestDataRes)
// 模拟设备信息
res.DeviceInfo = map[string]interface{}{
"deviceId": "IoTDevice001",
"GPSLongitude": "73.56789",
"GPSLatitude": "-42.12345",
"location": "河南郑州高新区科学大道001号",
"deviceStatus": true,
"deviceTime": gtime.Now(),
}
fields := []string{
"temperature", "humidity", "pressure", "flow",
"factoryData", "voltage", "current", "waterLevel",
"airQuality", "vibration", "noiseLevel", "ambientLight",
"windSpeed", "deviceTemperature", "deviceVibration",
"deviceVoltage", "deviceCurrent", "powerConsumption",
}
res.DeviceData = make(map[string]interface{}, len(fields))
for _, field := range fields {
// 模拟设备上报随机数值
res.DeviceData[field] = format.Round2Float64(rand.Float64() * 100)
}
// 自定义返回字段格式
response.CustomJson(ghttp.RequestFromCtx(ctx), g.Map{
"code": 200,
"data": res,
"msg": "获取设备数据成功",
})
return
}

View File

@ -0,0 +1,27 @@
// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================
package dao
import (
"hotgo/internal/dao/internal"
)
// internalAdminAttachmentDao is internal type for wrapping internal DAO implements.
type internalAdminAttachmentDao = *internal.AdminAttachmentDao
// adminAttachmentDao is the data access object for table hg_admin_attachment.
// You can define custom methods on it to extend its functionality as you wish.
type adminAttachmentDao struct {
internalAdminAttachmentDao
}
var (
// AdminAttachment is globally public accessible object for table hg_admin_attachment operations.
AdminAttachment = adminAttachmentDao{
internal.NewAdminAttachmentDao(),
}
)
// Fill with you ideas below.

View File

@ -0,0 +1,27 @@
// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================
package dao
import (
"hotgo/internal/dao/internal"
)
// internalAdminMemberDao is internal type for wrapping internal DAO implements.
type internalAdminMemberDao = *internal.AdminMemberDao
// adminMemberDao is the data access object for table hg_admin_member.
// You can define custom methods on it to extend its functionality as you wish.
type adminMemberDao struct {
internalAdminMemberDao
}
var (
// AdminMember is globally public accessible object for table hg_admin_member operations.
AdminMember = adminMemberDao{
internal.NewAdminMemberDao(),
}
)
// Fill with you ideas below.

View File

@ -0,0 +1,27 @@
// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================
package dao
import (
"hotgo/internal/dao/internal"
)
// internalAdminProjectDao is internal type for wrapping internal DAO implements.
type internalAdminProjectDao = *internal.AdminProjectDao
// adminProjectDao is the data access object for table hg_admin_project.
// You can define custom methods on it to extend its functionality as you wish.
type adminProjectDao struct {
internalAdminProjectDao
}
var (
// AdminProject is globally public accessible object for table hg_admin_project operations.
AdminProject = adminProjectDao{
internal.NewAdminProjectDao(),
}
)
// Fill with you ideas below.

View File

@ -0,0 +1,105 @@
// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package internal
import (
"context"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// AdminAttachmentDao is the data access object for table hg_admin_attachment.
type AdminAttachmentDao struct {
table string // table is the underlying table name of the DAO.
group string // group is the database configuration group name of current DAO.
columns AdminAttachmentColumns // columns contains all the column names of Table for convenient usage.
}
// AdminAttachmentColumns defines and stores column names for table hg_admin_attachment.
type AdminAttachmentColumns struct {
Id string // 文件ID
AppId string // 应用ID
MemberId string // 管理员ID
CateId string // 上传分类
Drive string // 上传驱动
Name string // 文件原始名
Kind string // 上传类型
MimeType string // 扩展类型
NaiveType string // NaiveUI类型
Path string // 本地路径
FileUrl string // url
Size string // 文件大小
Ext string // 扩展名
Md5 string // md5校验码
Status string // 状态
CreatedAt string // 创建时间
UpdatedAt string // 修改时间
}
// adminAttachmentColumns holds the columns for table hg_admin_attachment.
var adminAttachmentColumns = AdminAttachmentColumns{
Id: "id",
AppId: "app_id",
MemberId: "member_id",
CateId: "cate_id",
Drive: "drive",
Name: "name",
Kind: "kind",
MimeType: "mime_type",
NaiveType: "naive_type",
Path: "path",
FileUrl: "file_url",
Size: "size",
Ext: "ext",
Md5: "md5",
Status: "status",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
}
// NewAdminAttachmentDao creates and returns a new DAO object for table data access.
func NewAdminAttachmentDao() *AdminAttachmentDao {
return &AdminAttachmentDao{
group: "default",
table: "hg_admin_attachment",
columns: adminAttachmentColumns,
}
}
// DB retrieves and returns the underlying raw database management object of current DAO.
func (dao *AdminAttachmentDao) DB() gdb.DB {
return g.DB(dao.group)
}
// Table returns the table name of current dao.
func (dao *AdminAttachmentDao) Table() string {
return dao.table
}
// Columns returns all column names of current dao.
func (dao *AdminAttachmentDao) Columns() AdminAttachmentColumns {
return dao.columns
}
// Group returns the configuration group name of database of current dao.
func (dao *AdminAttachmentDao) Group() string {
return dao.group
}
// Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation.
func (dao *AdminAttachmentDao) Ctx(ctx context.Context) *gdb.Model {
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
}
// Transaction wraps the transaction logic using function f.
// It rollbacks the transaction and returns the error from function f if it returns non-nil error.
// It commits the transaction and returns nil if function f returns nil.
//
// Note that, you should not Commit or Rollback the transaction in function f
// as it is automatically handled by this function.
func (dao *AdminAttachmentDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
return dao.Ctx(ctx).Transaction(ctx, f)
}

View File

@ -0,0 +1,93 @@
// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package internal
import (
"context"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// AdminMemberDao is the data access object for table hg_admin_member.
type AdminMemberDao struct {
table string // table is the underlying table name of the DAO.
group string // group is the database configuration group name of current DAO.
columns AdminMemberColumns // columns contains all the column names of Table for convenient usage.
}
// AdminMemberColumns defines and stores column names for table hg_admin_member.
type AdminMemberColumns struct {
Id string // 管理员ID
Nickname string // 昵称
Username string // 帐号
PasswordHash string // 密码
Salt string // 密码盐
Avatar string // 头像
LastActiveAt string // 最后活跃时间
Remark string // 备注
Status string // 状态
CreatedAt string // 创建时间
UpdatedAt string // 修改时间
}
// adminMemberColumns holds the columns for table hg_admin_member.
var adminMemberColumns = AdminMemberColumns{
Id: "id",
Nickname: "nickname",
Username: "username",
PasswordHash: "password_hash",
Salt: "salt",
Avatar: "avatar",
LastActiveAt: "last_active_at",
Remark: "remark",
Status: "status",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
}
// NewAdminMemberDao creates and returns a new DAO object for table data access.
func NewAdminMemberDao() *AdminMemberDao {
return &AdminMemberDao{
group: "default",
table: "hg_admin_member",
columns: adminMemberColumns,
}
}
// DB retrieves and returns the underlying raw database management object of current DAO.
func (dao *AdminMemberDao) DB() gdb.DB {
return g.DB(dao.group)
}
// Table returns the table name of current dao.
func (dao *AdminMemberDao) Table() string {
return dao.table
}
// Columns returns all column names of current dao.
func (dao *AdminMemberDao) Columns() AdminMemberColumns {
return dao.columns
}
// Group returns the configuration group name of database of current dao.
func (dao *AdminMemberDao) Group() string {
return dao.group
}
// Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation.
func (dao *AdminMemberDao) Ctx(ctx context.Context) *gdb.Model {
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
}
// Transaction wraps the transaction logic using function f.
// It rollbacks the transaction and returns the error from function f if it returns non-nil error.
// It commits the transaction and returns nil if function f returns nil.
//
// Note that, you should not Commit or Rollback the transaction in function f
// as it is automatically handled by this function.
func (dao *AdminMemberDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
return dao.Ctx(ctx).Transaction(ctx, f)
}

View File

@ -0,0 +1,91 @@
// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package internal
import (
"context"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// AdminProjectDao is the data access object for table hg_admin_project.
type AdminProjectDao struct {
table string // table is the underlying table name of the DAO.
group string // group is the database configuration group name of current DAO.
columns AdminProjectColumns // columns contains all the column names of Table for convenient usage.
}
// AdminProjectColumns defines and stores column names for table hg_admin_project.
type AdminProjectColumns struct {
Id string // 项目id
ProjectName string // 项目名称
IndexImage string // 预览图片url
Content string // 项目数据
Status string // 项目状态,-1: 未发布'1: 已发布
Remarks string // 项目备注
CreatedBy string // 创建人
UpdatedAt string // 更新时间
CreatedAt string // 创建时间
DeletedAt string // 删除时间
}
// adminProjectColumns holds the columns for table hg_admin_project.
var adminProjectColumns = AdminProjectColumns{
Id: "id",
ProjectName: "project_name",
IndexImage: "index_image",
Content: "content",
Status: "status",
Remarks: "remarks",
CreatedBy: "created_by",
UpdatedAt: "updated_at",
CreatedAt: "created_at",
DeletedAt: "deleted_at",
}
// NewAdminProjectDao creates and returns a new DAO object for table data access.
func NewAdminProjectDao() *AdminProjectDao {
return &AdminProjectDao{
group: "default",
table: "hg_admin_project",
columns: adminProjectColumns,
}
}
// DB retrieves and returns the underlying raw database management object of current DAO.
func (dao *AdminProjectDao) DB() gdb.DB {
return g.DB(dao.group)
}
// Table returns the table name of current dao.
func (dao *AdminProjectDao) Table() string {
return dao.table
}
// Columns returns all column names of current dao.
func (dao *AdminProjectDao) Columns() AdminProjectColumns {
return dao.columns
}
// Group returns the configuration group name of database of current dao.
func (dao *AdminProjectDao) Group() string {
return dao.group
}
// Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation.
func (dao *AdminProjectDao) Ctx(ctx context.Context) *gdb.Model {
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
}
// Transaction wraps the transaction logic using function f.
// It rollbacks the transaction and returns the error from function f if it returns non-nil error.
// It commits the transaction and returns nil if function f returns nil.
//
// Note that, you should not Commit or Rollback the transaction in function f
// as it is automatically handled by this function.
func (dao *AdminProjectDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
return dao.Ctx(ctx).Transaction(ctx, f)
}

58
internal/library/cache/cache.go vendored Normal file
View File

@ -0,0 +1,58 @@
// Package cache
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package cache
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/gfile"
"hotgo/internal/library/cache/file"
)
// cache 缓存驱动
var cache *gcache.Cache
// Instance 缓存实例
func Instance() *gcache.Cache {
if cache == nil {
panic("cache uninitialized.")
}
return cache
}
// SetAdapter 设置缓存适配器
func SetAdapter(ctx context.Context) {
var adapter gcache.Adapter
switch g.Cfg().MustGet(ctx, "cache.adapter").String() {
case "redis":
adapter = gcache.NewAdapterRedis(g.Redis())
case "file":
fileDir := g.Cfg().MustGet(ctx, "cache.fileDir").String()
if fileDir == "" {
g.Log().Fatal(ctx, "file path must be configured for file caching.")
return
}
if !gfile.Exists(fileDir) {
if err := gfile.Mkdir(fileDir); err != nil {
g.Log().Fatalf(ctx, "failed to create the cache directory. procedure, err:%+v", err)
return
}
}
adapter = file.NewAdapterFile(fileDir)
default:
adapter = gcache.NewAdapterMemory()
}
// 数据库缓存,默认和通用缓冲驱动一致,如果你不想使用默认的,可以自行调整
//g.DB().GetCache().SetAdapter(adapter)
// 通用缓存
cache = gcache.New()
cache.SetAdapter(adapter)
}

322
internal/library/cache/file/file.go vendored Normal file
View File

@ -0,0 +1,322 @@
// Package file
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package file
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/gogf/gf/v2/container/gvar"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/util/gconv"
"os"
"path/filepath"
"time"
)
type (
// AdapterFile is the gcache adapter implements using file server.
AdapterFile struct {
dir string
}
fileContent struct {
Duration int64 `json:"duration"`
Data interface{} `json:"data,omitempty"`
}
)
const perm = 0o666
var (
CacheExpiredErr = errors.New("cache expired")
)
// NewAdapterFile creates and returns a new memory cache object.
func NewAdapterFile(dir string) gcache.Adapter {
return &AdapterFile{
dir: dir,
}
}
func (c *AdapterFile) Set(ctx context.Context, key interface{}, value interface{}, lifeTime time.Duration) (err error) {
fileKey := gconv.String(key)
if value == nil || lifeTime < 0 {
return c.Delete(fileKey)
}
return c.Save(fileKey, gconv.String(value), lifeTime)
}
func (c *AdapterFile) SetMap(ctx context.Context, data map[interface{}]interface{}, duration time.Duration) (err error) {
return gerror.New("implement me")
}
func (c *AdapterFile) SetIfNotExist(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (ok bool, err error) {
return false, gerror.New("implement me")
}
func (c *AdapterFile) SetIfNotExistFunc(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (ok bool, err error) {
return false, gerror.New("implement me")
}
func (c *AdapterFile) SetIfNotExistFuncLock(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (ok bool, err error) {
return false, gerror.New("implement me")
}
func (c *AdapterFile) Get(ctx context.Context, key interface{}) (*gvar.Var, error) {
fetch, err := c.Fetch(gconv.String(key))
if err != nil {
return nil, err
}
return gvar.New(fetch), nil
}
func (c *AdapterFile) GetOrSet(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (result *gvar.Var, err error) {
result, err = c.Get(ctx, key)
if err != nil && !errors.Is(err, CacheExpiredErr) {
return nil, err
}
if result.IsNil() {
return gvar.New(value), c.Set(ctx, key, value, duration)
}
return
}
func (c *AdapterFile) GetOrSetFunc(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (result *gvar.Var, err error) {
v, err := c.Get(ctx, key)
if err != nil && !errors.Is(err, CacheExpiredErr) {
return nil, err
}
if v.IsNil() {
value, err := f(ctx)
if err != nil {
return nil, err
}
if value == nil {
return nil, nil
}
return gvar.New(value), c.Set(ctx, key, value, duration)
} else {
return v, nil
}
}
func (c *AdapterFile) GetOrSetFuncLock(ctx context.Context, key interface{}, f gcache.Func, duration time.Duration) (result *gvar.Var, err error) {
return c.GetOrSetFunc(ctx, key, f, duration)
}
func (c *AdapterFile) Contains(ctx context.Context, key interface{}) (bool, error) {
return c.Has(gconv.String(key)), nil
}
func (c *AdapterFile) Size(ctx context.Context) (size int, err error) {
return 0, nil
}
func (c *AdapterFile) Data(ctx context.Context) (data map[interface{}]interface{}, err error) {
return nil, gerror.New("implement me")
}
func (c *AdapterFile) Keys(ctx context.Context) (keys []interface{}, err error) {
return nil, gerror.New("implement me")
}
func (c *AdapterFile) Values(ctx context.Context) (values []interface{}, err error) {
return nil, gerror.New("implement me")
}
func (c *AdapterFile) Update(ctx context.Context, key interface{}, value interface{}) (oldValue *gvar.Var, exist bool, err error) {
return nil, false, gerror.New("implement me")
}
func (c *AdapterFile) UpdateExpire(ctx context.Context, key interface{}, duration time.Duration) (oldDuration time.Duration, err error) {
var (
v *gvar.Var
oldTTL int64
fileKey = gconv.String(key)
)
// TTL.
expire, err := c.GetExpire(ctx, fileKey)
if err != nil {
return
}
oldTTL = int64(expire)
if oldTTL == -2 {
// It does not exist.
return
}
oldDuration = time.Duration(oldTTL) * time.Second
// DEL.
if duration < 0 {
err = c.Delete(fileKey)
return
}
v, err = c.Get(ctx, fileKey)
if err != nil {
return
}
err = c.Set(ctx, fileKey, v.Val(), duration)
return
}
func (c *AdapterFile) GetExpire(ctx context.Context, key interface{}) (time.Duration, error) {
content, err := c.read(gconv.String(key))
if err != nil {
return -1, nil
}
if content.Duration <= time.Now().Unix() {
return -1, nil
}
return time.Duration(time.Now().Unix()-content.Duration) * time.Second, nil
}
func (c *AdapterFile) Remove(ctx context.Context, keys ...interface{}) (lastValue *gvar.Var, err error) {
if len(keys) == 0 {
return nil, nil
}
// Retrieves the last key value.
if lastValue, err = c.Get(ctx, gconv.String(keys[len(keys)-1])); err != nil {
return nil, err
}
// Deletes all given keys.
err = c.DeleteMulti(gconv.Strings(keys)...)
return
}
func (c *AdapterFile) Clear(ctx context.Context) error {
return c.Flush()
}
func (c *AdapterFile) Close(ctx context.Context) error {
return nil
}
func (c *AdapterFile) createName(key string) string {
h := sha256.New()
_, _ = h.Write([]byte(key))
hash := hex.EncodeToString(h.Sum(nil))
return filepath.Join(c.dir, fmt.Sprintf("%s.cache", hash))
}
func (c *AdapterFile) read(key string) (*fileContent, error) {
rp := gfile.RealPath(c.createName(key))
if rp == "" {
return nil, nil
}
value, err := os.ReadFile(rp)
if err != nil {
return nil, err
}
content := &fileContent{}
if err := json.Unmarshal(value, content); err != nil {
return nil, err
}
if content.Duration == 0 {
return content, nil
}
if content.Duration <= time.Now().Unix() {
_ = c.Delete(key)
return nil, CacheExpiredErr
}
return content, nil
}
// Has checks if the cached key exists into the File storage
func (c *AdapterFile) Has(key string) bool {
fc, err := c.read(key)
return err == nil && fc != nil
}
// Delete the cached key from File storage
func (c *AdapterFile) Delete(key string) error {
_, err := os.Stat(c.createName(key))
if err != nil && os.IsNotExist(err) {
return nil
}
return os.Remove(c.createName(key))
}
// DeleteMulti the cached key from File storage
func (c *AdapterFile) DeleteMulti(keys ...string) (err error) {
for _, key := range keys {
if err = c.Delete(key); err != nil {
return
}
}
return
}
// Fetch retrieves the cached value from key of the File storage
func (c *AdapterFile) Fetch(key string) (interface{}, error) {
content, err := c.read(key)
if err != nil {
return nil, err
}
if content == nil {
return nil, nil
}
return content.Data, nil
}
// FetchMulti retrieve multiple cached values from keys of the File storage
func (c *AdapterFile) FetchMulti(keys []string) map[string]interface{} {
result := make(map[string]interface{})
for _, key := range keys {
if value, err := c.Fetch(key); err == nil {
result[key] = value
}
}
return result
}
// Flush removes all cached keys of the File storage
func (c *AdapterFile) Flush() error {
dir, err := os.Open(c.dir)
if err != nil {
return err
}
defer func() {
_ = dir.Close()
}()
names, _ := dir.Readdirnames(-1)
for _, name := range names {
_ = os.Remove(filepath.Join(c.dir, name))
}
return nil
}
// Save a value in File storage by key
func (c *AdapterFile) Save(key string, value string, lifeTime time.Duration) error {
duration := int64(0)
if lifeTime > 0 {
duration = time.Now().Unix() + int64(lifeTime.Seconds())
}
content := &fileContent{duration, value}
data, err := json.Marshal(content)
if err != nil {
return err
}
err = os.WriteFile(c.createName(key), data, perm)
return err
}

View File

@ -0,0 +1,120 @@
// Package contexts
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package contexts
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"hotgo/internal/consts"
"hotgo/internal/model"
)
// Init 初始化上下文对象指针到上下文对象中,以便后续的请求流程中可以修改
func Init(r *ghttp.Request, customCtx *model.Context) {
r.SetCtxVar(consts.ContextHTTPKey, customCtx)
}
// Get 获得上下文变量如果没有设置那么返回nil
func Get(ctx context.Context) *model.Context {
value := ctx.Value(consts.ContextHTTPKey)
if value == nil {
return nil
}
if localCtx, ok := value.(*model.Context); ok {
return localCtx
}
return nil
}
// SetUser 将上下文信息设置到上下文请求中,注意是完整覆盖
func SetUser(ctx context.Context, user *model.Identity) {
c := Get(ctx)
if c == nil {
g.Log().Warning(ctx, "contexts.SetUser, c == nil ")
return
}
c.User = user
}
// SetResponse 设置组件响应 用于访问日志使用
func SetResponse(ctx context.Context, response *model.Response) {
c := Get(ctx)
if c == nil {
g.Log().Warning(ctx, "contexts.SetResponse, c == nil ")
return
}
c.Response = response
}
// SetModule 设置应用模块
func SetModule(ctx context.Context, module string) {
c := Get(ctx)
if c == nil {
g.Log().Warning(ctx, "contexts.SetModule, c == nil ")
return
}
c.Module = module
}
// GetUser 获取用户信息
func GetUser(ctx context.Context) *model.Identity {
c := Get(ctx)
if c == nil {
return nil
}
return c.User
}
// GetUserId 获取用户ID
func GetUserId(ctx context.Context) int64 {
user := GetUser(ctx)
if user == nil {
return 0
}
return user.Id
}
// GetModule 获取应用模块
func GetModule(ctx context.Context) string {
c := Get(ctx)
if c == nil {
return ""
}
return c.Module
}
// SetData 设置额外数据
func SetData(ctx context.Context, k string, v interface{}) {
c := Get(ctx)
if c == nil {
g.Log().Warning(ctx, "contexts.SetData, c == nil ")
return
}
Get(ctx).Data[k] = v
}
// SetDataMap 设置额外数据
func SetDataMap(ctx context.Context, vs g.Map) {
c := Get(ctx)
if c == nil {
g.Log().Warning(ctx, "contexts.SetData, c == nil ")
return
}
for k, v := range vs {
Get(ctx).Data[k] = v
}
}
// GetData 获取额外数据
func GetData(ctx context.Context) g.Map {
c := Get(ctx)
if c == nil {
return nil
}
return c.Data
}

View File

@ -0,0 +1,35 @@
// Package contexts
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package contexts
import (
"context"
"time"
)
type detached struct {
ctx context.Context
}
func (detached) Deadline() (time.Time, bool) {
return time.Time{}, false
}
func (detached) Done() <-chan struct{} {
return nil
}
func (detached) Err() error {
return nil
}
func (d detached) Value(key interface{}) interface{} {
return d.ctx.Value(key)
}
func Detach(ctx context.Context) context.Context {
return detached{ctx: ctx}
}

View File

@ -0,0 +1,116 @@
// Package response
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package response
import (
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
"hotgo/internal/consts"
"hotgo/internal/library/contexts"
"hotgo/internal/model"
)
// JsonExit 返回JSON数据并退出当前HTTP执行函数
func JsonExit(r *ghttp.Request, code int, message string, data ...interface{}) {
RJson(r, code, message, data...)
r.Exit()
}
// RXml xml
func RXml(r *ghttp.Request, code int, message string, data ...interface{}) {
responseData := interface{}(nil)
if len(data) > 0 {
responseData = data[0]
}
res := &model.Response{
Code: code,
Message: message,
Timestamp: gtime.Timestamp(),
TraceID: gctx.CtxId(r.Context()),
}
// 如果不是正常的返回则将data转为error
if gcode.CodeOK.Code() == code {
res.Data = responseData
} else {
res.Error = responseData
}
// 清空响应
r.Response.ClearBuffer()
// 写入响应
r.Response.WriteXml(gconv.Map(res))
// 加入到上下文
contexts.SetResponse(r.Context(), res)
}
// RJson 标准返回结果数据结构封装
func RJson(r *ghttp.Request, code int, message string, data ...interface{}) {
responseData := interface{}(nil)
if len(data) > 0 {
responseData = data[0]
}
res := &model.Response{
Code: code,
Message: message,
Timestamp: gtime.Timestamp(),
TraceID: gctx.CtxId(r.Context()),
}
// 如果不是正常的返回则将data转为error
if consts.ApiStatusOk == code {
res.Data = responseData
} else {
res.Error = responseData
}
// 清空响应
r.Response.ClearBuffer()
// 写入响应
r.Response.WriteJson(res)
// 加入到上下文
contexts.SetResponse(r.Context(), res)
}
// CustomJson 自定义JSON
func CustomJson(r *ghttp.Request, content interface{}) {
// 清空响应
r.Response.ClearBuffer()
// 写入响应
r.Response.WriteJson(content)
// 加入到上下文
contexts.SetResponse(r.Context(), &model.Response{
Code: consts.ApiStatusOk,
Message: "",
Data: content,
Error: nil,
Timestamp: gtime.Timestamp(),
TraceID: gctx.CtxId(r.Context()),
})
}
// Redirect 重定向
func Redirect(r *ghttp.Request, location string, code ...int) {
r.Response.RedirectTo(location, code...)
}
// RText 返回成功文本
func RText(r *ghttp.Request, message string) {
// 清空响应
r.Response.ClearBuffer()
// 写入响应
r.Response.Write(message)
}

View File

@ -0,0 +1,30 @@
// Package storager
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package storager
import (
"context"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"hotgo/internal/model"
)
var config *model.UploadConfig
func InitConfig(ctx context.Context) {
err := g.Cfg().MustGet(ctx, "storager").Scan(&config)
if err != nil {
panic(err)
}
if config == nil {
panic("没有找到storager配置")
}
}
func GetModel(ctx context.Context) *gdb.Model {
return g.Model("admin_attachment").Ctx(ctx)
}

View File

@ -0,0 +1,232 @@
// Package storager
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package storager
import (
"crypto/md5"
"fmt"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/text/gstr"
"io"
"path"
)
// 文件归属分类
const (
KindImg = "image" // 图片
KindDoc = "doc" // 文档
KindAudio = "audio" // 音频
KindVideo = "video" // 视频
KindZip = "zip" // 压缩包
KindOther = "other" // 其他
)
var KindSlice = []string{KindImg, KindDoc, KindAudio, KindVideo, KindZip, KindOther}
var (
// 图片类型
imgType = g.MapStrStr{
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"png": "image/png",
"gif": "image/gif",
"webp": "image/webp",
"cr2": "image/x-canon-cr2",
"tif": "image/tiff",
"bmp": "image/bmp",
"heif": "image/heif",
"jxr": "image/vnd.ms-photo",
"psd": "image/vnd.adobe.photoshop",
"ico": "image/vnd.microsoft.icon",
"dwg": "image/vnd.dwg",
}
// 文档类型
docType = g.MapStrStr{
"doc": "application/msword",
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"dot": "application/msword",
"xls": "application/vnd.ms-excel",
"xlt": "application/vnd.ms-excel",
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"xltx": "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
"ppt": "application/vnd.ms-powerpoint",
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
"pdf": "application/pdf",
"txt": "text/plain",
"csv": "text/csv",
"html": "text/html",
"xml": "text/xml",
"pptm": "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
"pot": "application/vnd.ms-powerpoint",
"wpd": "application/wordperfect",
"md": "text/markdown",
"json": "application/json",
"yaml": "application/x-yaml",
"markdown": "text/markdown",
"asciidoc": "text/asciidoc",
"xsl": "application/xml",
"wps": "application/vnd.ms-works",
"sxi": "application/vnd.sun.xml.impress",
"sti": "application/vnd.sun.xml.impress.template",
"odp": "application/vnd.oasis.opendocument.presentation-template",
}
// 音频类型
audioType = g.MapStrStr{
"mid": "audio/midi",
"mp3": "audio/mpeg",
"m4a": "audio/mp4",
"ogg": "audio/ogg",
"flac": "audio/x-flac",
"wav": "audio/x-wav",
"amr": "audio/amr",
"aac": "audio/aac",
"aiff": "audio/x-aiff",
}
// 视频类型
videoType = g.MapStrStr{
"mp4": "video/mp4",
"m4v": "video/x-m4v",
"mkv": "video/x-matroska",
"webm": "video/webm",
"mov": "video/quicktime",
"avi": "video/x-msvideo",
"wmv": "video/x-ms-wmv",
"mpg": "video/mpeg",
"flv": "video/x-flv",
"3gp": "video/3gpp",
}
// 压缩包
zipType = g.MapStrStr{
"zip": "application/zip",
"rar": "application/x-rar-compressed",
"tar": "application/x-tar",
"gz": "application/gzip",
"7z": "application/octet-stream",
"tar.gz": "application/octet-stream",
}
// 其他
otherType = g.MapStrStr{
"dwf": "model/vnd.dwf",
"ics": "text/calendar",
"vcard": "text/vcard",
"apk": "application/vnd.android.package-archive",
"ipa": "application/octet-stream",
}
)
// IsImgType 判断是否为图片
func IsImgType(ext string) bool {
_, ok := imgType[ext]
return ok
}
// IsDocType 判断是否为文档
func IsDocType(ext string) bool {
_, ok := docType[ext]
return ok
}
// IsAudioType 判断是否为音频
func IsAudioType(ext string) bool {
_, ok := audioType[ext]
return ok
}
// IsVideoType 判断是否为视频
func IsVideoType(ext string) bool {
_, ok := videoType[ext]
return ok
}
// IsZipType 判断是否为压缩包
func IsZipType(ext string) bool {
_, ok := zipType[ext]
return ok
}
// GetFileMimeType 获取文件扩展类型
// 如果文件类型没有加入系统映射类型,默认认为不是合法的文件类型。建议将常用的上传文件类型加入映射关系。
// 当然你也可以不做限制,可以上传任意文件。但需要谨慎处理和设置相应的安全措施。
// 获取任意扩展名的扩展类型mime.TypeByExtension(".xls")
func GetFileMimeType(ext string) (string, error) {
if mime, ok := imgType[ext]; ok {
return mime, nil
}
if mime, ok := docType[ext]; ok {
return mime, nil
}
if mime, ok := audioType[ext]; ok {
return mime, nil
}
if mime, ok := videoType[ext]; ok {
return mime, nil
}
if mime, ok := zipType[ext]; ok {
return mime, nil
}
if mime, ok := otherType[ext]; ok {
return mime, nil
}
return "", gerror.Newf("Invalid file type:%v", ext)
}
// GetFileKind 获取文件上传类型
func GetFileKind(ext string) string {
if _, ok := imgType[ext]; ok {
return KindImg
}
if _, ok := docType[ext]; ok {
return KindDoc
}
if _, ok := audioType[ext]; ok {
return KindAudio
}
if _, ok := videoType[ext]; ok {
return KindVideo
}
if _, ok := zipType[ext]; ok {
return KindZip
}
return KindOther
}
// Ext 获取文件后缀
func Ext(baseName string) string {
return gstr.ToLower(gstr.StrEx(path.Ext(baseName), "."))
}
// UploadFileByte 获取上传文件的byte
func UploadFileByte(file *ghttp.UploadFile) ([]byte, error) {
open, err := file.Open()
if err != nil {
return nil, err
}
return io.ReadAll(open)
}
// CalcFileMd5 计算文件md5值
func CalcFileMd5(file *ghttp.UploadFile) (string, error) {
f, err := file.Open()
if err != nil {
err = gerror.Wrapf(err, `os.Open failed for name "%s"`, file.Filename)
return "", err
}
defer f.Close()
h := md5.New()
_, err = io.Copy(h, f)
if err != nil {
err = gerror.Wrap(err, `io.Copy failed`)
return "", err
}
return fmt.Sprintf("%x", h.Sum(nil)), nil
}

View File

@ -0,0 +1,17 @@
// Package storager
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package storager
// FileMeta 文件元数据
type FileMeta struct {
Filename string `json:"filename"` // 文件名称
Size int64 `json:"size"` // 文件大小
Kind string `json:"kind"` // 文件上传类型
MimeType string `json:"mimeType"` // 文件扩展类型
NaiveType string `json:"naiveType"` // NaiveUI类型
Ext string `json:"ext"` // 文件扩展名
Md5 string `json:"md5"` // 文件hash
}

View File

@ -0,0 +1,236 @@
// Package storager
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package storager
import (
"context"
"fmt"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/grand"
"hotgo/internal/consts"
"hotgo/internal/library/contexts"
"hotgo/internal/model/entity"
"hotgo/utility/url"
"hotgo/utility/validate"
"strconv"
"strings"
)
// UploadDrive 存储驱动
type UploadDrive interface {
// Upload 上传
Upload(ctx context.Context, file *ghttp.UploadFile) (fullPath string, err error)
}
// New 初始化存储驱动
func New(name ...string) UploadDrive {
var (
driveType = consts.UploadDriveLocal
drive UploadDrive
)
if len(name) > 0 && name[0] != "" {
driveType = name[0]
}
switch driveType {
case consts.UploadDriveLocal:
drive = &LocalDrive{}
default:
panic(fmt.Sprintf("暂不支持的存储驱动:%v", driveType))
}
return drive
}
// DoUpload 上传入口
func DoUpload(ctx context.Context, typ string, file *ghttp.UploadFile) (result *entity.AdminAttachment, err error) {
if file == nil {
err = gerror.New("文件必须!")
return
}
meta, err := GetFileMeta(file)
if err != nil {
return
}
if err = ValidateFileMeta(typ, meta); err != nil {
return
}
result, err = HasFile(ctx, meta.Md5)
if err != nil {
return
}
// 相同存储相同身份才复用
if result != nil && result.Drive == config.Drive && result.MemberId == contexts.GetUserId(ctx) && result.AppId == contexts.GetModule(ctx) {
return
}
// 上传到驱动
fullPath, err := New(config.Drive).Upload(ctx, file)
if err != nil {
return
}
// 写入附件记录
return write(ctx, meta, fullPath)
}
// ValidateFileMeta 验证文件元数据
func ValidateFileMeta(typ string, meta *FileMeta) (err error) {
if _, err = GetFileMimeType(meta.Ext); err != nil {
return
}
switch typ {
case KindImg:
if !IsImgType(meta.Ext) {
err = gerror.New("上传的文件不是图片")
return
}
if config.ImageSize > 0 && meta.Size > config.ImageSize*1024*1024 {
err = gerror.Newf("图片大小不能超过%vMB", config.ImageSize)
return
}
if len(config.ImageType) > 0 && !validate.InSlice(strings.Split(config.ImageType, `,`), meta.Ext) {
err = gerror.New("上传图片类型未经允许")
return
}
case KindDoc:
if !IsDocType(meta.Ext) {
err = gerror.New("上传的文件不是文档")
return
}
case KindAudio:
if !IsAudioType(meta.Ext) {
err = gerror.New("上传的文件不是音频")
return
}
case KindVideo:
if !IsVideoType(meta.Ext) {
err = gerror.New("上传的文件不是视频")
return
}
case KindZip:
if !IsZipType(meta.Ext) {
err = gerror.New("上传的文件不是压缩文件")
return
}
case KindOther:
fallthrough
default:
// 默认为通用的文件上传
if config.FileSize > 0 && meta.Size > config.FileSize*1024*1024 {
err = gerror.Newf("文件大小不能超过%vMB", config.FileSize)
return
}
if len(config.FileType) > 0 && !validate.InSlice(strings.Split(config.FileType, `,`), meta.Ext) {
err = gerror.New("上传文件类型未经允许")
return
}
}
return
}
// LastUrl 根据驱动获取最终文件访问地址
func LastUrl(ctx context.Context, fullPath, drive string) string {
if validate.IsURL(fullPath) {
return fullPath
}
switch drive {
case consts.UploadDriveLocal:
return url.GetAddr(ctx) + "/" + fullPath
default:
return fullPath
}
}
// GetFileMeta 获取上传文件元数据
func GetFileMeta(file *ghttp.UploadFile) (meta *FileMeta, err error) {
meta = new(FileMeta)
meta.Filename = file.Filename
meta.Size = file.Size
meta.Ext = Ext(file.Filename)
meta.Kind = GetFileKind(meta.Ext)
meta.MimeType, err = GetFileMimeType(meta.Ext)
if err != nil {
return
}
// 兼容naiveUI
naiveType := "text/plain"
if IsImgType(Ext(file.Filename)) {
naiveType = ""
}
meta.NaiveType = naiveType
// 计算md5值
meta.Md5, err = CalcFileMd5(file)
return
}
// GenFullPath 根据目录和文件类型生成一个绝对地址
func GenFullPath(basePath, ext string) string {
fileName := strconv.FormatInt(gtime.TimestampNano(), 36) + grand.S(6)
fileName = fileName + ext
return basePath + gtime.Date() + "/" + strings.ToLower(fileName)
}
// write 写入附件记录
func write(ctx context.Context, meta *FileMeta, fullPath string) (models *entity.AdminAttachment, err error) {
models = &entity.AdminAttachment{
Id: 0,
AppId: contexts.GetModule(ctx),
MemberId: contexts.GetUserId(ctx),
Drive: config.Drive,
Size: meta.Size,
Path: fullPath,
FileUrl: fullPath,
Name: meta.Filename,
Kind: meta.Kind,
MimeType: meta.MimeType,
NaiveType: meta.NaiveType,
Ext: meta.Ext,
Md5: meta.Md5,
Status: consts.StatusEnabled,
}
id, err := GetModel(ctx).Data(models).InsertAndGetId()
if err != nil {
return
}
models.Id = id
return
}
// HasFile 检查附件是否存在
func HasFile(ctx context.Context, md5 string) (res *entity.AdminAttachment, err error) {
if err = GetModel(ctx).Where("md5", md5).Scan(&res); err != nil {
err = gerror.Wrap(err, "检查文件hash时出现错误")
return
}
if res == nil {
return
}
// 只有在上传时才会检查md5值如果附件存在则更新最后上传时间保证上传列表更新显示在最前面
if res.Id > 0 {
update := g.Map{
"status": consts.StatusEnabled,
"updated_at": gtime.Now(),
}
_, _ = GetModel(ctx).WherePri(res.Id).Data(update).Update()
}
return
}

View File

@ -0,0 +1,47 @@
// Package storager
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package storager
import (
"context"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gtime"
"strings"
)
// LocalDrive 本地驱动
type LocalDrive struct {
}
// Upload 上传到本地
func (d *LocalDrive) Upload(ctx context.Context, file *ghttp.UploadFile) (fullPath string, err error) {
var (
sp = g.Cfg().MustGet(ctx, "server.serverRoot")
nowDate = gtime.Date()
)
if sp.IsEmpty() {
err = gerror.New("本地上传驱动必须配置静态路径!")
return
}
if config.LocalPath == "" {
err = gerror.New("本地上传驱动必须配置本地存储路径!")
return
}
// 包含静态文件夹的路径
fullDirPath := strings.Trim(sp.String(), "/") + "/" + config.LocalPath + nowDate
fileName, err := file.Save(fullDirPath, true)
if err != nil {
return
}
// 不含静态文件夹的路径
fullPath = config.LocalPath + nowDate + "/" + fileName
return
}

View File

@ -0,0 +1,313 @@
// Package token
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package token
import (
"context"
"fmt"
"github.com/gogf/gf/v2/crypto/gmd5"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/golang-jwt/jwt/v5"
"hotgo/internal/consts"
"hotgo/internal/library/cache"
"hotgo/internal/library/contexts"
"hotgo/internal/model"
"hotgo/utility/simple"
"time"
)
type Claims struct {
*model.Identity
jwt.RegisteredClaims
}
type Token struct {
ExpireAt int64 `json:"exp"` // token过期时间
RefreshAt int64 `json:"ra"` // 刷新时间
RefreshCount int64 `json:"rc"` // 刷新次数
}
var (
config *model.TokenConfig
errorLogin = gerror.New("登录身份已失效,请重新登录!")
errorMultiLogin = gerror.New("账号已在其他地方登录,如非本人操作请及时修改登录密码!")
)
func InitConfig(ctx context.Context) {
err := g.Cfg().MustGet(ctx, "token").Scan(&config)
if err != nil {
panic(err)
}
if config == nil {
panic("没有找到token配置")
}
}
// Login 登录
func Login(ctx context.Context, user *model.Identity) (string, int64, error) {
claims := Claims{
user,
jwt.RegisteredClaims{},
}
header, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(config.SecretKey))
if err != nil {
return "", 0, err
}
var (
now = gtime.Now()
// 认证key
authKey = GetAuthKey(header)
// 登录token
tokenKey = GetTokenKey(user.App, authKey)
// 身份绑定
bindKey = GetBindKey(user.App, user.Id)
// 有效时长
duration = time.Second * gconv.Duration(config.Expires)
)
token := &Token{
ExpireAt: now.Unix() + config.Expires,
RefreshAt: now.Unix(),
RefreshCount: 0,
}
if err = cache.Instance().Set(ctx, tokenKey, token, duration); err != nil {
return "", 0, err
}
if err = cache.Instance().Set(ctx, bindKey, tokenKey, duration); err != nil {
return "", 0, err
}
return header, config.Expires, nil
}
// Logout 注销登录
func Logout(r *ghttp.Request) (err error) {
var (
ctx = r.Context()
header = GetAuthorization(r)
)
if header == "" {
err = errorLogin
return
}
claims, err := parseToken(ctx, header)
if err != nil {
g.Log().Debugf(ctx, "logout parseToken err:%+v", err)
err = errorLogin
return
}
var (
// 认证key
authKey = GetAuthKey(header)
// 登录token
tokenKey = GetTokenKey(contexts.GetModule(ctx), authKey)
// 身份绑定
bindKey = GetBindKey(contexts.GetModule(ctx), claims.Id)
)
// 删除token
if _, err = cache.Instance().Remove(ctx, tokenKey); err != nil {
return
}
if !config.MultiLogin {
if _, err = cache.Instance().Remove(ctx, bindKey); err != nil {
return
}
}
return
}
// ParseLoginUser 解析登录用户信息
func ParseLoginUser(r *ghttp.Request) (user *model.Identity, err error) {
var (
ctx = r.Context()
header = GetAuthorization(r)
)
if header == "" {
g.Log().Debugf(ctx, "ParseLoginUser header:%v", header)
err = errorLogin
return
}
claims, err := parseToken(ctx, header)
if err != nil {
g.Log().Debugf(ctx, "parseToken err:%+v", err)
err = errorLogin
return
}
var (
// 认证key
authKey = GetAuthKey(header)
// 登录token
tokenKey = GetTokenKey(claims.App, authKey)
// 身份绑定
bindKey = GetBindKey(claims.App, claims.Id)
)
// 检查token是否存在
tk, err := cache.Instance().Get(ctx, tokenKey)
if err != nil {
g.Log().Debugf(ctx, "get tokenKey err:%+v", err)
err = errorLogin
return
}
if tk.IsEmpty() {
g.Log().Debug(ctx, "token isEmpty")
err = errorLogin
return
}
var token *Token
if err = tk.Scan(&token); err != nil {
g.Log().Debugf(ctx, "token scan err:%+v", err)
err = errorLogin
return
}
if token == nil {
g.Log().Debugf(ctx, "token = nil")
err = errorLogin
return
}
now := gtime.Now()
if token.ExpireAt < now.Unix() {
g.Log().Debugf(ctx, "token expired.")
err = errorLogin
return
}
// 是否允许多端登录
if !config.MultiLogin {
origin, err := cache.Instance().Get(ctx, bindKey)
if err != nil {
g.Log().Debugf(ctx, "bindKey get err:%+v", err)
err = errorLogin
return nil, err
}
if origin == nil || origin.IsEmpty() {
g.Log().Debug(ctx, "bindKey isEmpty")
err = errorLogin
return nil, err
}
if tokenKey != origin.String() {
g.Log().Debugf(ctx, "bindKey offsite login tokenKey:%v, origin:%v", tokenKey, origin.String())
err = errorMultiLogin
return nil, err
}
}
// 自动刷新token有效期
refreshToken := func() {
// 未开启自动刷新
if !config.AutoRefresh {
return
}
// 刷新次数已达上限
if config.MaxRefreshTimes != -1 && token.RefreshCount >= config.MaxRefreshTimes {
return
}
// 未达到刷新间隔
if gtime.New(token.RefreshAt).Unix()+config.RefreshInterval > now.Unix() {
return
}
// 刷新有效期
token.ExpireAt = now.Unix() + config.Expires
token.RefreshAt = now.Unix()
token.RefreshCount += 1
duration := time.Second * gconv.Duration(config.Expires)
if err = cache.Instance().Set(ctx, tokenKey, token, duration); err != nil {
return
}
if err = cache.Instance().Set(ctx, bindKey, tokenKey, duration); err != nil {
return
}
}
simple.SafeGo(ctx, func(ctx context.Context) {
refreshToken()
})
user = claims.Identity
return
}
// parseToken 解析jwt令牌
func parseToken(ctx context.Context, header string) (*Claims, error) {
token, err := jwt.ParseWithClaims(header, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(config.SecretKey), nil
})
if err != nil {
g.Log().Debugf(ctx, "parseToken err:%+v", err)
return nil, err
}
if !token.Valid {
return nil, errorLogin
}
claims, ok := token.Claims.(*Claims)
if !ok {
return nil, errorLogin
}
return claims, nil
}
// GetAuthorization 获取authorization
func GetAuthorization(r *ghttp.Request) string {
// 默认从请求头获取
var authorization = r.Header.Get("Authorization")
if authorization == "" {
authorization = r.Cookie.Get("Authorization").String()
}
// 如果请求头不存在则从get参数获取
if authorization == "" {
return r.Get("authorization").String()
}
return gstr.Replace(authorization, "Bearer ", "")
}
// GetAuthKey 认证key
func GetAuthKey(token string) string {
return gmd5.MustEncryptString("hotgo" + token)
}
// GetTokenKey 令牌缓存key
func GetTokenKey(appName, authKey string) string {
return fmt.Sprintf("%v:%v:%v", consts.CacheToken, appName, authKey)
}
// GetBindKey 令牌身份绑定key
func GetBindKey(appName string, userId int64) string {
return fmt.Sprintf("%v:%v:%v", consts.CacheTokenBind, appName, userId)
}

0
internal/logic/.gitkeep Normal file
View File

View File

@ -0,0 +1,167 @@
// Package admin
// @Description
// @Author Ms <133814250@qq.com>
package admin
import (
"context"
"fmt"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/grand"
"hotgo/internal/dao"
"hotgo/internal/library/contexts"
"hotgo/internal/model/entity"
"hotgo/internal/model/input/adminin"
"hotgo/internal/service"
)
type sAdminProject struct{}
func NewAdminProject() *sAdminProject {
return &sAdminProject{}
}
func init() {
service.RegisterAdminProject(NewAdminProject())
}
// Model ORM模型
func (s *sAdminProject) Model(ctx context.Context) *gdb.Model {
return dao.AdminProject.Ctx(ctx).Handler(func(m *gdb.Model) *gdb.Model {
return m.Where(dao.AdminProject.Columns().CreatedBy, contexts.GetUserId(ctx))
})
}
// List 查询列表
func (s *sAdminProject) List(ctx context.Context, in *adminin.ProjectListInp) (list []*adminin.ProjectListModel, totalCount int, err error) {
mod := s.Model(ctx)
totalCount, err = mod.Count()
if err != nil {
return
}
if totalCount == 0 {
return
}
if err = mod.Page(in.Page, in.PerPage).OrderDesc(dao.AdminProject.Columns().Id).Scan(&list); err != nil {
return
}
for _, v := range list {
v.State = v.Status
v.CreateUserId = v.CreatedBy
v.CreateTime = v.CreatedAt
}
return
}
// Delete 删除
func (s *sAdminProject) Delete(ctx context.Context, in *adminin.ProjectDeleteInp) (err error) {
if exists, err := s.ExistById(ctx, in.Id); err != nil || !exists {
err = gerror.New("没有找到项目!")
return err
}
_, err = s.Model(ctx).WherePri(in.Id).Delete()
return
}
// Create 新增
func (s *sAdminProject) Create(ctx context.Context, in *adminin.ProjectCreateInp) (res *adminin.ProjectCreateModel, err error) {
data := &entity.AdminProject{
Id: gconv.Int64(fmt.Sprintf("%v%v", gtime.Now().Format("YmdH"), grand.N(10000, 99999))),
ProjectName: in.ProjectName,
IndexImage: in.IndexImage,
Status: -1,
Remarks: in.Remarks,
CreatedBy: contexts.GetUserId(ctx),
}
if _, err = dao.AdminProject.Ctx(ctx).Data(data).Insert(); err != nil {
return nil, err
}
res = &adminin.ProjectCreateModel{
Id: data.Id,
ProjectName: data.ProjectName,
State: data.Status,
CreateTime: gtime.Now(),
CreateUserId: contexts.GetUserId(ctx),
IndexImage: data.IndexImage,
Remarks: data.Remarks,
}
return
}
// Edit 修改
func (s *sAdminProject) Edit(ctx context.Context, in *adminin.ProjectEditInp) (err error) {
if exists, err := s.ExistById(ctx, in.Id); err != nil || !exists {
err = gerror.New("没有找到项目!")
return err
}
data := make(g.Map)
if len(in.IndexImage) > 0 {
data["index_image"] = in.IndexImage
}
if len(in.ProjectName) > 0 {
data["project_name"] = in.ProjectName
}
if len(in.Remarks) > 0 {
data["remarks"] = in.Remarks
}
if len(data) > 0 {
_, err = s.Model(ctx).WherePri(in.Id).Data(data).Update()
}
return
}
// SaveData 保存数据
func (s *sAdminProject) SaveData(ctx context.Context, in *adminin.ProjectSaveDataInp) (err error) {
if exists, err := s.ExistById(ctx, in.Id); err != nil || !exists {
err = gerror.New("没有找到项目!")
return err
}
_, err = s.Model(ctx).WherePri(in.Id).Data("content", in.Content).Update()
return
}
// Publish 修改发布状态
func (s *sAdminProject) Publish(ctx context.Context, in *adminin.ProjectPublishInp) (err error) {
if exists, err := s.ExistById(ctx, in.Id); err != nil || !exists {
err = gerror.New("没有找到项目!")
return err
}
_, err = s.Model(ctx).WherePri(in.Id).Data("status", in.State).Update()
return
}
// GetData 获取指定信息
func (s *sAdminProject) GetData(ctx context.Context, in *adminin.ProjectGetDataInp) (res *adminin.ProjectGetDataModel, err error) {
if err = dao.AdminProject.Ctx(ctx).WherePri(in.Id).Scan(&res); err != nil {
return nil, err
}
if res == nil {
err = gerror.New("项目不存在")
return
}
res.State = res.Status
return
}
// ExistById 判断指定项目是否存在
func (s *sAdminProject) ExistById(ctx context.Context, id int64) (exists bool, err error) {
count, err := s.Model(ctx).WherePri(id).Count()
if err != nil {
return
}
exists = count > 0
return
}

View File

@ -0,0 +1,125 @@
package admin
import (
"context"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gtime"
"hotgo/internal/consts"
"hotgo/internal/dao"
"hotgo/internal/library/contexts"
"hotgo/internal/library/token"
"hotgo/internal/model"
"hotgo/internal/model/entity"
"hotgo/internal/model/input/adminin"
"hotgo/internal/service"
"hotgo/utility/simple"
)
type sAdminSite struct{}
func NewAdminSite() *sAdminSite {
return &sAdminSite{}
}
func init() {
service.RegisterAdminSite(NewAdminSite())
}
// Register 账号注册
func (s *sAdminSite) Register(ctx context.Context, in *adminin.RegisterInp) (err error) {
err = gerror.New("暂未开放注册")
return
}
// AccountLogin 账号登录
func (s *sAdminSite) AccountLogin(ctx context.Context, in *adminin.AccountLoginInp) (res *adminin.LoginModel, err error) {
var mb *entity.AdminMember
if err = dao.AdminMember.Ctx(ctx).Where("username", in.Username).Scan(&mb); err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return
}
if mb == nil {
err = gerror.New("账号不存在")
return
}
if mb.Salt == "" {
err = gerror.New("用户信息错误")
return
}
if err = simple.CheckPassword(in.Password, mb.Salt, mb.PasswordHash); err != nil {
return
}
if mb.Status != consts.StatusEnabled {
err = gerror.New("账号已被禁用")
return
}
res, err = s.handleLogin(ctx, mb)
return
}
// handleLogin .
func (s *sAdminSite) handleLogin(ctx context.Context, mb *entity.AdminMember) (res *adminin.LoginModel, err error) {
user := &model.Identity{
Id: mb.Id,
Username: mb.Username,
Nickname: mb.Nickname,
Avatar: mb.Avatar,
App: consts.AppAdmin,
LoginAt: gtime.Now(),
}
tk, expires, err := token.Login(ctx, user)
if err != nil {
return nil, err
}
res = &adminin.LoginModel{
UserInfo: &adminin.LoginUserInfo{
Id: user.Id,
Username: user.Username,
Nickname: user.Nickname,
},
Token: &adminin.LoginToken{
TokenName: "Authorization",
TokenValue: tk,
Expires: expires,
},
}
return
}
// BindUserContext 绑定用户上下文
func (s *sAdminSite) BindUserContext(ctx context.Context, claims *model.Identity) (err error) {
var mb *entity.AdminMember
if err = dao.AdminMember.Ctx(ctx).Where("id", claims.Id).Scan(&mb); err != nil {
err = gerror.Wrap(err, "获取用户信息失败,请稍后重试!")
return
}
if mb == nil {
err = gerror.Wrap(err, "账号不存在或已被删除!")
return
}
if mb.Status != consts.StatusEnabled {
err = gerror.New("账号已被禁用,如有疑问请联系管理员")
return
}
user := &model.Identity{
Id: mb.Id,
Username: mb.Username,
Nickname: mb.Nickname,
Avatar: mb.Avatar,
App: claims.App,
LoginAt: claims.LoginAt,
}
contexts.SetUser(ctx, user)
return
}

10
internal/logic/logic.go Normal file
View File

@ -0,0 +1,10 @@
// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================
package logic
import (
_ "hotgo/internal/logic/admin"
_ "hotgo/internal/logic/middleware"
)

View File

@ -0,0 +1,35 @@
// Package middleware
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package middleware
import (
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/text/gstr"
"hotgo/internal/consts"
"hotgo/internal/library/response"
"hotgo/utility/simple"
)
// AdminAuth 后台鉴权中间件
func (s *sMiddleware) AdminAuth(r *ghttp.Request) {
var (
ctx = r.Context()
path = gstr.Replace(r.URL.Path, simple.RouterPrefix(ctx, consts.AppAdmin), "", 1)
)
// 不需要验证登录的路由地址
if s.IsExceptLogin(ctx, consts.AppAdmin, path) {
r.Middleware.Next()
return
}
// 将用户信息传递到上下文中
if err := s.DeliverUserContext(r); err != nil {
response.JsonExit(r, consts.ApiStatusTokenOverdue, err.Error())
return
}
r.Middleware.Next()
}

View File

@ -0,0 +1,123 @@
// Package middleware
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package middleware
import (
"context"
"fmt"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gctx"
"hotgo/internal/consts"
"hotgo/internal/library/contexts"
"hotgo/internal/library/token"
"hotgo/internal/model"
"hotgo/internal/service"
"hotgo/utility/validate"
"strings"
"sync"
)
type sMiddleware struct {
LoginUrl string // 登录路由地址
NotRecordRequest g.Map // 不记录请求数据的路由(当前请求数据过大时会影响响应效率,可以将路径放到该选项中改善)
FilterRoutes map[string]ghttp.RouterItem // 支持预处理的web路由
routeMutex sync.Mutex
}
func init() {
service.RegisterMiddleware(NewMiddleware())
}
func NewMiddleware() *sMiddleware {
return &sMiddleware{
NotRecordRequest: g.Map{
"/api/goview/project/upload": struct{}{}, // 上传文件
},
}
}
// Ctx 初始化请求上下文
func (s *sMiddleware) Ctx(r *ghttp.Request) {
data := make(g.Map)
if _, ok := s.NotRecordRequest[r.URL.Path]; ok {
data["request.body"] = gjson.New(nil)
} else {
data["request.body"] = gjson.New(r.GetBodyString())
}
contexts.Init(r, &model.Context{
Data: data,
Module: getModule(r.URL.Path),
})
if len(r.Cookie.GetSessionId()) == 0 {
r.Cookie.SetSessionId(gctx.CtxId(r.Context()))
}
r.Middleware.Next()
}
func getModule(path string) (module string) {
slice := strings.Split(path, "/")
if len(slice) < 2 {
module = consts.AppDefault
return
}
if slice[1] == "" {
module = consts.AppDefault
return
}
return slice[1]
}
// CORS allows Cross-origin resource sharing.
func (s *sMiddleware) CORS(r *ghttp.Request) {
r.Response.CORSDefault()
r.Middleware.Next()
}
// DeliverUserContext 将用户信息传递到上下文中
func (s *sMiddleware) DeliverUserContext(r *ghttp.Request) (err error) {
user, err := token.ParseLoginUser(r)
if err != nil {
return
}
switch user.App {
case consts.AppAdmin:
if err = service.AdminSite().BindUserContext(r.Context(), user); err != nil {
return
}
default:
contexts.SetUser(r.Context(), user)
}
return
}
// IsExceptAuth 是否是不需要验证权限的路由地址
func (s *sMiddleware) IsExceptAuth(ctx context.Context, appName, path string) bool {
pathList := g.Cfg().MustGet(ctx, fmt.Sprintf("router.%v.exceptAuth", appName)).Strings()
for i := 0; i < len(pathList); i++ {
if validate.InSliceExistStr(pathList[i], path) {
return true
}
}
return false
}
// IsExceptLogin 是否是不需要登录的路由地址
func (s *sMiddleware) IsExceptLogin(ctx context.Context, appName, path string) bool {
pathList := g.Cfg().MustGet(ctx, fmt.Sprintf("router.%v.exceptLogin", appName)).Strings()
for i := 0; i < len(pathList); i++ {
if validate.InSliceExistStr(pathList[i], path) {
return true
}
}
return false
}

View File

@ -0,0 +1,102 @@
package middleware
import (
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/util/gconv"
"hotgo/internal/library/response"
"hotgo/utility/validate"
"reflect"
)
// GetFilterRoutes 获取支持预处理的web路由
func (s *sMiddleware) GetFilterRoutes(r *ghttp.Request) map[string]ghttp.RouterItem {
// 首次访问时加载
if s.FilterRoutes == nil {
s.routeMutex.Lock()
defer s.routeMutex.Unlock()
if s.FilterRoutes != nil {
return s.FilterRoutes
}
s.FilterRoutes = make(map[string]ghttp.RouterItem)
for _, v := range r.Server.GetRoutes() {
// 非规范路由不加载
if v.Handler.Info.Type.NumIn() != 2 {
continue
}
key := s.GenFilterRouteKey(v.Handler.Router)
if _, ok := s.FilterRoutes[key]; !ok {
s.FilterRoutes[key] = v
}
}
}
return s.FilterRoutes
}
// GenFilterRequestKey 根据请求生成唯一key
func (s *sMiddleware) GenFilterRequestKey(r *ghttp.Request) string {
return s.GenRouteKey(r.Method, r.Request.URL.Path)
}
// GenFilterRouteKey 根据路由生成唯一key
func (s *sMiddleware) GenFilterRouteKey(r *ghttp.Router) string {
return s.GenRouteKey(r.Method, r.Uri)
}
// GenRouteKey 生成唯一key
func (s *sMiddleware) GenRouteKey(method, path string) string {
return method + " " + path
}
// PreFilter 请求输入预处理
// api使用gf规范路由并且XxxReq结构体实现了validate.Filter接口即可
func (s *sMiddleware) PreFilter(r *ghttp.Request) {
router, ok := s.GetFilterRoutes(r)[s.GenFilterRequestKey(r)]
if !ok {
r.Middleware.Next()
return
}
funcInfo := router.Handler.Info
// 非规范路由不处理
if funcInfo.Type.NumIn() != 2 {
r.Middleware.Next()
return
}
inputType := funcInfo.Type.In(1)
var inputObject reflect.Value
if inputType.Kind() == reflect.Ptr {
inputObject = reflect.New(inputType.Elem())
} else {
inputObject = reflect.New(inputType.Elem()).Elem()
}
// 先验证基本校验规则
if err := r.Parse(inputObject.Interface()); err != nil {
resp := gerror.Code(err)
response.JsonExit(r, resp.Code(), gerror.Current(err).Error(), resp.Detail())
return
}
// 没有实现预处理
if _, ok = inputObject.Interface().(validate.Filter); !ok {
r.Middleware.Next()
return
}
// 执行预处理
if err := validate.PreFilter(r.Context(), inputObject.Interface()); err != nil {
resp := gerror.Code(err)
response.JsonExit(r, resp.Code(), gerror.Current(err).Error(), resp.Detail())
return
}
// 过滤后的参数
r.SetParamMap(gconv.Map(inputObject.Interface()))
r.Middleware.Next()
}

View File

@ -0,0 +1,123 @@
// Package middleware
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package middleware
import (
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/util/gmeta"
"hotgo/internal/consts"
"hotgo/internal/library/contexts"
"hotgo/internal/library/response"
"hotgo/utility/charset"
"hotgo/utility/simple"
)
// ResponseHandler HTTP响应预处理
func (s *sMiddleware) ResponseHandler(r *ghttp.Request) {
r.Middleware.Next()
// 错误状态码接管
switch r.Response.Status {
case 403:
r.Response.Writeln("403 - 网站拒绝显示此网页")
return
case 404:
r.Response.Writeln("404 - 你似乎来到了没有知识存在的荒原…")
return
}
contentType := getContentType(r)
// 已存在响应
if contentType != consts.HTTPContentTypeStream && r.Response.BufferLength() > 0 && contexts.Get(r.Context()).Response != nil {
return
}
switch contentType {
case consts.HTTPContentTypeHtml:
s.responseHtml(r)
return
case consts.HTTPContentTypeXml:
s.responseXml(r)
return
case consts.HTTPContentTypeStream:
default:
responseJson(r)
}
}
// responseHtml html模板响应
func (s *sMiddleware) responseHtml(r *ghttp.Request) {
code, message, resp := parseResponse(r)
if code == gcode.CodeOK.Code() {
return
}
r.Response.ClearBuffer()
_ = r.Response.WriteTplContent(simple.DefaultErrorTplContent(r.Context()), g.Map{"code": code, "message": message, "stack": resp})
}
// responseXml xml响应
func (s *sMiddleware) responseXml(r *ghttp.Request) {
code, message, data := parseResponse(r)
response.RXml(r, code, message, data)
}
// responseJson json响应
func responseJson(r *ghttp.Request) {
code, message, data := parseResponse(r)
response.RJson(r, code, message, data)
}
// parseResponse 解析响应数据
func parseResponse(r *ghttp.Request) (code int, message string, resp interface{}) {
ctx := r.Context()
err := r.GetError()
if err == nil {
return consts.ApiStatusOk, "操作成功", r.GetHandlerResponse()
}
// 是否输出错误堆栈到页面
if g.Cfg().MustGet(ctx, "hotgo.debug", true).Bool() {
message = gerror.Current(err).Error()
if getContentType(r) == consts.HTTPContentTypeHtml {
resp = charset.SerializeStack(err)
} else {
resp = charset.ParseErrStack(err)
}
} else {
message = consts.ErrorMessage(gerror.Current(err))
}
code = gerror.Code(err).Code()
// 记录异常日志
// 如果你想对错误做不同的处理,可以通过定义不同的错误码来区分
// 默认-1为安全可控错误码只记录文件日志非-1为不可控错误记录文件日志+服务日志并打印堆栈
if code == gcode.CodeNil.Code() {
g.Log().Stdout(false).Infof(ctx, "exception:%v", err)
} else {
g.Log().Errorf(ctx, "exception:%v", err)
}
return
}
func getContentType(r *ghttp.Request) (contentType string) {
contentType = r.Response.Header().Get("Content-Type")
if contentType != "" {
return
}
mime := gmeta.Get(r.GetHandlerResponse(), "mime").String()
if mime == "" {
contentType = consts.HTTPContentTypeJson
} else {
contentType = mime
}
return
}

0
internal/model/.gitkeep Normal file
View File

View File

@ -0,0 +1,28 @@
// Package model
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package model
// 本地配置.
// TokenConfig 登录令牌配置
type TokenConfig struct {
SecretKey string `json:"secretKey"`
Expires int64 `json:"expires"`
AutoRefresh bool `json:"autoRefresh"`
RefreshInterval int64 `json:"refreshInterval"`
MaxRefreshTimes int64 `json:"maxRefreshTimes"`
MultiLogin bool `json:"multiLogin"`
}
// UploadConfig 上传配置
type UploadConfig struct {
Drive string `json:"drive"`
FileSize int64 `json:"fileSize"`
FileType string `json:"fileType"`
ImageSize int64 `json:"imageSize"`
ImageType string `json:"imageType"`
LocalPath string `json:"localPath"`
}

30
internal/model/context.go Normal file
View File

@ -0,0 +1,30 @@
// Package model
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package model
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// Context 请求上下文结构
type Context struct {
Module string // 应用模块 adminapihomewebsocket
AddonName string // 插件名称 如果不是插件模块请求,可能为空
User *Identity // 上下文用户信息
Response *Response // 请求响应
Data g.Map // 自定kv变量 业务模块根据需要设置,不固定
}
// Identity 通用身份模型
type Identity struct {
Id int64 `json:"id" description:"用户ID"`
Username string `json:"username" description:"用户名"`
Nickname string `json:"nickname" description:"昵称"`
Avatar string `json:"avatar" description:"头像"`
App string `json:"app" description:"登录应用"`
LoginAt *gtime.Time `json:"loginAt" description:"登录时间"`
}

View File

View File

@ -0,0 +1,32 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package do
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// AdminAttachment is the golang structure of table hg_admin_attachment for DAO operations like Where/Data.
type AdminAttachment struct {
g.Meta `orm:"table:hg_admin_attachment, do:true"`
Id interface{} // 文件ID
AppId interface{} // 应用ID
MemberId interface{} // 管理员ID
CateId interface{} // 上传分类
Drive interface{} // 上传驱动
Name interface{} // 文件原始名
Kind interface{} // 上传类型
MimeType interface{} // 扩展类型
NaiveType interface{} // NaiveUI类型
Path interface{} // 本地路径
FileUrl interface{} // url
Size interface{} // 文件大小
Ext interface{} // 扩展名
Md5 interface{} // md5校验码
Status interface{} // 状态
CreatedAt *gtime.Time // 创建时间
UpdatedAt *gtime.Time // 修改时间
}

View File

@ -0,0 +1,26 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package do
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// AdminMember is the golang structure of table hg_admin_member for DAO operations like Where/Data.
type AdminMember struct {
g.Meta `orm:"table:hg_admin_member, do:true"`
Id interface{} // 管理员ID
Nickname interface{} // 昵称
Username interface{} // 帐号
PasswordHash interface{} // 密码
Salt interface{} // 密码盐
Avatar interface{} // 头像
LastActiveAt *gtime.Time // 最后活跃时间
Remark interface{} // 备注
Status interface{} // 状态
CreatedAt *gtime.Time // 创建时间
UpdatedAt *gtime.Time // 修改时间
}

View File

@ -0,0 +1,25 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package do
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// AdminProject is the golang structure of table hg_admin_project for DAO operations like Where/Data.
type AdminProject struct {
g.Meta `orm:"table:hg_admin_project, do:true"`
Id interface{} // 项目id
ProjectName interface{} // 项目名称
IndexImage interface{} // 预览图片url
Content interface{} // 项目数据
Status interface{} // 项目状态,-1: 未发布'1: 已发布
Remarks interface{} // 项目备注
CreatedBy interface{} // 创建人
UpdatedAt *gtime.Time // 更新时间
CreatedAt *gtime.Time // 创建时间
DeletedAt *gtime.Time // 删除时间
}

View File

View File

@ -0,0 +1,30 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package entity
import (
"github.com/gogf/gf/v2/os/gtime"
)
// AdminAttachment is the golang structure for table admin_attachment.
type AdminAttachment struct {
Id int64 `json:"id" description:"文件ID"`
AppId string `json:"appId" description:"应用ID"`
MemberId int64 `json:"memberId" description:"管理员ID"`
CateId uint64 `json:"cateId" description:"上传分类"`
Drive string `json:"drive" description:"上传驱动"`
Name string `json:"name" description:"文件原始名"`
Kind string `json:"kind" description:"上传类型"`
MimeType string `json:"mimeType" description:"扩展类型"`
NaiveType string `json:"naiveType" description:"NaiveUI类型"`
Path string `json:"path" description:"本地路径"`
FileUrl string `json:"fileUrl" description:"url"`
Size int64 `json:"size" description:"文件大小"`
Ext string `json:"ext" description:"扩展名"`
Md5 string `json:"md5" description:"md5校验码"`
Status int `json:"status" description:"状态"`
CreatedAt *gtime.Time `json:"createdAt" description:"创建时间"`
UpdatedAt *gtime.Time `json:"updatedAt" description:"修改时间"`
}

View File

@ -0,0 +1,24 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package entity
import (
"github.com/gogf/gf/v2/os/gtime"
)
// AdminMember is the golang structure for table admin_member.
type AdminMember struct {
Id int64 `json:"id" description:"管理员ID"`
Nickname string `json:"nickname" description:"昵称"`
Username string `json:"username" description:"帐号"`
PasswordHash string `json:"passwordHash" description:"密码"`
Salt string `json:"salt" description:"密码盐"`
Avatar string `json:"avatar" description:"头像"`
LastActiveAt *gtime.Time `json:"lastActiveAt" description:"最后活跃时间"`
Remark string `json:"remark" description:"备注"`
Status int `json:"status" description:"状态"`
CreatedAt *gtime.Time `json:"createdAt" description:"创建时间"`
UpdatedAt *gtime.Time `json:"updatedAt" description:"修改时间"`
}

View File

@ -0,0 +1,23 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package entity
import (
"github.com/gogf/gf/v2/os/gtime"
)
// AdminProject is the golang structure for table admin_project.
type AdminProject struct {
Id int64 `json:"id" description:"项目id"`
ProjectName string `json:"projectName" description:"项目名称"`
IndexImage string `json:"indexImage" description:"预览图片url"`
Content string `json:"content" description:"项目数据"`
Status int `json:"status" description:"项目状态,-1: 未发布'1: 已发布"`
Remarks string `json:"remarks" description:"项目备注"`
CreatedBy int64 `json:"createdBy" description:"创建人"`
UpdatedAt *gtime.Time `json:"updatedAt" description:"更新时间"`
CreatedAt *gtime.Time `json:"createdAt" description:"创建时间"`
DeletedAt *gtime.Time `json:"deletedAt" description:"删除时间"`
}

View File

@ -0,0 +1,127 @@
// Package adminin
// @Description
// @Author Ms <133814250@qq.com>
package adminin
import (
"context"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gtime"
"hotgo/internal/model/entity"
"hotgo/internal/model/input/form"
)
// ProjectListInp 查询列表
type ProjectListInp struct {
form.PageReq
}
type ProjectListModel struct {
entity.AdminProject
State int `json:"state"`
CreateTime *gtime.Time `json:"createTime"`
CreateUserId int64 `json:"createUserId"`
}
// ProjectCreateInp 新增项目
type ProjectCreateInp struct {
ProjectName string `json:"projectName"`
Remarks string `json:"remarks"`
IndexImage string `json:"indexImage"`
}
type ProjectCreateModel struct {
Id int64 `json:"id"`
ProjectName string `json:"projectName"`
State int `json:"state"`
CreateTime *gtime.Time `json:"createTime"`
CreateUserId int64 `json:"createUserId"`
IndexImage string `json:"indexImage"`
Remarks string `json:"remarks"`
}
// ProjectEditInp 修改项目数据
type ProjectEditInp struct {
entity.AdminProject
}
type ProjectEditModel struct{}
func (in *ProjectEditInp) Filter(ctx context.Context) (err error) {
if in.Id <= 0 {
err = gerror.New("ID不能为空")
return
}
return
}
// ProjectSaveDataInp 保存项目数据
type ProjectSaveDataInp struct {
Id int64 `json:"projectId"`
Content string `json:"content" description:"项目数据"`
}
type ProjectSaveDataModel struct{}
func (in *ProjectSaveDataInp) Filter(ctx context.Context) (err error) {
if in.Id <= 0 {
err = gerror.New("ID不能为空")
return
}
return
}
// ProjectDeleteInp 删除项目类型
type ProjectDeleteInp struct {
Id int64 `json:"id" v:"required#项目ID不能为空" dc:"项目ID"`
}
type ProjectDeleteModel struct{}
// ProjectGetDataInp 获取信息
type ProjectGetDataInp struct {
Id int64 `json:"projectId" dc:"项目ID"`
}
func (in *ProjectGetDataInp) Filter(ctx context.Context) (err error) {
if in.Id <= 0 {
err = gerror.New("ID不能为空")
return
}
return
}
type ProjectGetDataModel struct {
Id int64 `json:"id"`
ProjectName string `json:"projectName"`
State int `json:"state"`
CreateTime *gtime.Time `json:"createTime"`
CreateUserId int64 `json:"createUserId"`
IndexImage string `json:"indexImage"`
Remarks string `json:"remarks"`
Content string `json:"content" description:"项目数据"`
Status int `json:"status"`
}
// ProjectPublishInp 修改发布状态
type ProjectPublishInp struct {
Id int64 `json:"id"`
ProjectName string `json:"projectName"`
State int `json:"state"`
}
func (in *ProjectPublishInp) Filter(ctx context.Context) (err error) {
if in.Id <= 0 {
err = gerror.New("ID不能为空")
return
}
return
}
type ProjectPublishModel struct{}
// ProjectUploadInp 文件上传
type ProjectUploadInp struct {
File *ghttp.UploadFile `json:"object" type:"file" dc:"文件"`
}

View File

@ -0,0 +1,61 @@
package adminin
import (
"context"
"github.com/gogf/gf/v2/os/gtime"
)
// RegisterInp 账号注册
type RegisterInp struct {
Username string `json:"username" v:"required#用户名不能为空" dc:"用户名"`
Password string `json:"password" v:"required#密码不能为空" dc:"密码"`
Nickname string `json:"nickname" dc:"昵称"`
}
func (in *RegisterInp) Filter(ctx context.Context) (err error) {
return
}
type LoginToken struct {
TokenValue string `json:"tokenValue"`
TokenName string `json:"tokenName"`
Expires int64 `json:"expires"`
}
type LoginUserInfo struct {
Id int64 `json:"id" dc:"用户ID"`
Username string `json:"username" dc:"用户名"`
Nickname string `json:"nickname" dc:"昵称"`
}
// LoginModel 统一登录响应
type LoginModel struct {
UserInfo *LoginUserInfo `json:"userinfo" dc:"用户信息"`
Token *LoginToken `json:"token" dc:"登录token"`
}
// AccountLoginInp 账号登录
type AccountLoginInp struct {
Username string `json:"username" v:"required#用户名不能为空" dc:"用户名"`
Password string `json:"password" v:"required#密码不能为空" dc:"密码"`
}
// MobileLoginInp 手机号登录
type MobileLoginInp struct {
Mobile string `json:"mobile" v:"required|phone-loose#手机号不能为空|手机号格式不正确" dc:"手机号"`
Code string `json:"code" v:"required#验证码不能为空" dc:"验证码"`
}
// MemberLoginPermissions 登录用户角色信息
type MemberLoginPermissions []string
// MemberLoginStatInp 用户登录统计
type MemberLoginStatInp struct {
MemberId int64
}
type MemberLoginStatModel struct {
LoginCount int `json:"loginCount" dc:"登录次数"`
LastLoginAt *gtime.Time `json:"lastLoginAt" dc:"最后登录时间"`
LastLoginIp string `json:"lastLoginIp" dc:"最后登录IP"`
}

View File

@ -0,0 +1,67 @@
// Package form
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package form
import (
"hotgo/internal/consts"
)
type ReqPageFunc interface {
GetPage() int
GetPerPage() int
}
// PageReq 分页请求
type PageReq struct {
Page int `json:"page" example:"10" d:"1" v:"min:1#页码最小值不能低于1" dc:"当前页码"`
PerPage int `json:"limit" example:"1" d:"10" v:"min:1|max:200#每页数量最小值不能低于1|最大值不能大于200" dc:"每页数量"`
}
// GetPage 获取当前页码
func (req *PageReq) GetPage() int {
return req.Page
}
// GetPerPage 获取每页数量
func (req *PageReq) GetPerPage() int {
return req.PerPage
}
// PageRes 分页响应
type PageRes struct {
PageReq
PageCount int `json:"pageCount" example:"0" dc:"分页个数"`
TotalCount int `json:"count" example:"0" dc:"数据总行数"`
}
// Pack 打包分页数据
func (res *PageRes) Pack(req ReqPageFunc, totalCount int) {
res.TotalCount = totalCount
res.PageCount = CalPageCount(totalCount, req.GetPerPage())
res.Page = req.GetPage()
res.PerPage = req.GetPerPage()
}
func CalPageCount(totalCount int, perPage int) int {
return (totalCount + perPage - 1) / perPage
}
// CalPage 计算分页偏移量
func CalPage(page, perPage int) (newPage, newPerPage int, offset int) {
if page <= 0 {
newPage = consts.DefaultPage
} else {
newPage = page
}
if perPage <= 0 {
newPerPage = consts.DefaultPageSize
} else {
newPerPage = perPage
}
offset = (newPage - 1) * newPerPage
return
}

View File

@ -0,0 +1,16 @@
// Package model
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package model
// Response HTTP响应
type Response struct {
Code int `json:"code" example:"0" description:"状态码"`
Message string `json:"msg,omitempty" example:"操作成功" description:"提示消息"`
Data interface{} `json:"data,omitempty" description:"数据集"`
Error interface{} `json:"error,omitempty" description:"错误信息"`
Timestamp int64 `json:"timestamp" example:"1640966400" description:"服务器时间戳"`
TraceID string `json:"traceID" v:"0" example:"d0bb93048bc5c9164cdee845dcb7f820" description:"链路ID"`
}

View File

@ -0,0 +1 @@
package packed

View File

66
internal/service/admin.go Normal file
View File

@ -0,0 +1,66 @@
// ================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// You can delete these comments if you wish manually maintain this interface file.
// ================================================================================
package service
import (
"context"
"hotgo/internal/model"
"hotgo/internal/model/input/adminin"
)
type (
IAdminProject interface {
// List 查询列表
List(ctx context.Context, in *adminin.ProjectListInp) (list []*adminin.ProjectListModel, totalCount int, err error)
// Delete 删除
Delete(ctx context.Context, in *adminin.ProjectDeleteInp) (err error)
// Create 新增
Create(ctx context.Context, in *adminin.ProjectCreateInp) (res *adminin.ProjectCreateModel, err error)
// Edit 修改
Edit(ctx context.Context, in *adminin.ProjectEditInp) (err error)
// SaveData 保存数据
SaveData(ctx context.Context, in *adminin.ProjectSaveDataInp) (err error)
// Publish 修改发布状态
Publish(ctx context.Context, in *adminin.ProjectPublishInp) (err error)
// GetData 获取指定信息
GetData(ctx context.Context, in *adminin.ProjectGetDataInp) (res *adminin.ProjectGetDataModel, err error)
}
IAdminSite interface {
// Register 账号注册
Register(ctx context.Context, in *adminin.RegisterInp) (err error)
// AccountLogin 账号登录
AccountLogin(ctx context.Context, in *adminin.AccountLoginInp) (res *adminin.LoginModel, err error)
// BindUserContext 绑定用户上下文
BindUserContext(ctx context.Context, claims *model.Identity) (err error)
}
)
var (
localAdminProject IAdminProject
localAdminSite IAdminSite
)
func AdminProject() IAdminProject {
if localAdminProject == nil {
panic("implement not found for interface IAdminProject, forgot register?")
}
return localAdminProject
}
func RegisterAdminProject(i IAdminProject) {
localAdminProject = i
}
func AdminSite() IAdminSite {
if localAdminSite == nil {
panic("implement not found for interface IAdminSite, forgot register?")
}
return localAdminSite
}
func RegisterAdminSite(i IAdminSite) {
localAdminSite = i
}

View File

@ -0,0 +1,57 @@
// ================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// You can delete these comments if you wish manually maintain this interface file.
// ================================================================================
package service
import (
"context"
"github.com/gogf/gf/v2/net/ghttp"
)
type (
IMiddleware interface {
// AdminAuth 后台鉴权中间件
AdminAuth(r *ghttp.Request)
// Ctx 初始化请求上下文
Ctx(r *ghttp.Request)
// CORS allows Cross-origin resource sharing.
CORS(r *ghttp.Request)
// DeliverUserContext 将用户信息传递到上下文中
DeliverUserContext(r *ghttp.Request) (err error)
// IsExceptAuth 是否是不需要验证权限的路由地址
IsExceptAuth(ctx context.Context, appName, path string) bool
// IsExceptLogin 是否是不需要登录的路由地址
IsExceptLogin(ctx context.Context, appName, path string) bool
// GetFilterRoutes 获取支持预处理的web路由
GetFilterRoutes(r *ghttp.Request) map[string]ghttp.RouterItem
// GenFilterRequestKey 根据请求生成唯一key
GenFilterRequestKey(r *ghttp.Request) string
// GenFilterRouteKey 根据路由生成唯一key
GenFilterRouteKey(r *ghttp.Router) string
// GenRouteKey 生成唯一key
GenRouteKey(method, path string) string
// PreFilter 请求输入预处理
// api使用gf规范路由并且XxxReq结构体实现了validate.Filter接口即可
PreFilter(r *ghttp.Request)
// ResponseHandler HTTP响应预处理
ResponseHandler(r *ghttp.Request)
}
)
var (
localMiddleware IMiddleware
)
func Middleware() IMiddleware {
if localMiddleware == nil {
panic("implement not found for interface IMiddleware, forgot register?")
}
return localMiddleware
}
func RegisterMiddleware(i IMiddleware) {
localMiddleware = i
}

18
main.go Normal file
View File

@ -0,0 +1,18 @@
package main
import (
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
_ "hotgo/internal/packed"
_ "hotgo/internal/logic"
"github.com/gogf/gf/v2/os/gctx"
"hotgo/internal/cmd"
)
func main() {
cmd.Main.Run(gctx.GetInitCtx())
}

103
manifest/config/config.yaml Normal file
View File

@ -0,0 +1,103 @@
# hotgo配置
hotgo:
# debug开关开启后接口出现错误时会向前端输出堆栈信息可选false|true默认为true
debug: true
# 路由配置
router:
# 后台
admin:
# 前缀
prefix: "/admin"
# 不需要验证登录的路由地址
exceptLogin: [
"/api/goview/project/getData", # 获取项目数据
]
# 统一默认日志配置
defaultLogger: &defaultLogger
level: "all"
flags: 42
file: "{Y-m-d}.log" # 日志文件格式默认为"{Y-m-d}.log"
stdoutColorDisabled: false # 关闭终端的颜色打印可选false|true默认false
writerColorEnable: false # 日志文件是否带上颜色可选false|true默认false表示不带颜色
rotateExpire: "7d" # 日志保留天数
rotateBackupLimit: 2 # 最大备份数量
rotateBackupCompress: 2 # 日志文件压缩级别0-9,9最高
# gf配置配置参考https://goframe.org/pages/viewpage.action?pageId=44449486
server:
address: ":8090" # 本地监听地址
openapiPath: "/api.json" # OpenAPI接口文档地址
swaggerPath: "/swagger" # 内置SwaggerUI展示地址
serverRoot: "resource/public" # 静态文件服务的目录根路径配置时自动开启静态文件服务
DumpRouterMap: false # 是否在Server启动时打印所有的路由列表
logPath: "logs/server" # 服务日志保存路径
ErrorStack: true # 当Server捕获到异常时是否记录堆栈信息到日志中默认为true
ErrorLogEnabled: true # 是否记录异常日志信息到日志中默认为true
errorLogPattern: "error/{Y-m-d}.log" # 异常错误日志文件格式默认为"error-{Ymd}.log"
accessLogEnabled: true # 是否记录访问日志默认为false
accessLogPattern: "access/{Y-m-d}.log" # 访问日志文件格式默认为"access-{Ymd}.log"
maxHeaderBytes: "100KB" # 请求头大小限制请求头包括客户端提交的Cookie数据默认设置为100KB
clientMaxBodySize: "300MB" # 客户端提交的Body大小限制同时也影响文件上传大小默认设置为300MB
serverAgent: "HG HTTP Server" # 服务端Agent信息默认为"HG HTTP Server"
# PProf配置
pprofEnabled: true # 是否开启PProf性能调试特性默认为false
pprofPattern: "/pprof" # 开启PProf时有效表示PProf特性的页面访问路径对当前Server绑定的所有域名有效
# 服务日志配置
logger:
path: "logs/server" # 日志文件路径默认为空表示关闭仅输出到终端
<<: *defaultLogger
#缓存
cache:
adapter: "file" # 缓存驱动方式支持memory|redis|file不填默认memory
fileDir: "./storage/cache" # 文件缓存路径adapter=file时必填
# 登录令牌
token:
secretKey: "hotgo123" # 令牌加密秘钥考虑安全问题生产环境中请修改默认值
expires: 604800 # 令牌有效期单位默认7天
autoRefresh: true # 是否开启自动刷新过期时间 false|true 默认为true
refreshInterval: 86400 # 刷新间隔单位必须小于expires否则无法触发默认1天内只允许刷新一次
maxRefreshTimes: 30 # 最大允许刷新次数-1不限制默认30次
multiLogin: true # 是否允许多端登录 false|true 默认为true
# 上传驱动
storager:
# 通用配置
drive: "local" # 上传驱动local本地存储
fileSize: "10" # 上传图片大小限制单位MB
fileType: "doc,docx,pdf,zip,tar,xls,xlsx,rar,jpg,jpeg,gif,npm,png,svg" # 上传文件类型限制文件上传后缀类型限制
imageSize: "your_image_size_value" # 上传图片大小限制单位MB
imageType: "jpg,jpeg,gif,npm,png,svg" # 上传图片类型限制图片上传后缀类型限制
localPath: "attachment/" # 本地存储路径对外访问的相对路径
# Redis. 配置参考https://goframe.org/pages/viewpage.action?pageId=1114217
redis:
default:
address: "192.168.1.100:6379"
db: "3"
pass: "jhkdjhkjdhsIUTYURTU_37fMei"
idleTimeout: "20"
# Database. 配置参考https://goframe.org/pages/viewpage.action?pageId=1114245
database:
logger:
path: "logs/database" # 日志文件路径默认为空表示关闭仅输出到终端
<<: *defaultLogger
stdout: true
default:
link: "mysql:goview:ZZ6amaa8xnHJQffb@tcp(192.168.1.100:3306)/goview?loc=Local&parseTime=true&charset=utf8mb4"
debug: true
Prefix: "hg_"

View File

@ -0,0 +1,21 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: template-single
labels:
app: template-single
spec:
replicas: 1
selector:
matchLabels:
app: template-single
template:
metadata:
labels:
app: template-single
spec:
containers:
- name : main
image: template-single
imagePullPolicy: Always

View File

@ -0,0 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml

View File

@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: template-single
spec:
ports:
- port: 80
protocol: TCP
targetPort: 8000
selector:
app: template-single

View File

@ -0,0 +1,14 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: template-single-configmap
data:
config.yaml: |
server:
address: ":8000"
openapiPath: "/api.json"
swaggerPath: "/swagger"
logger:
level : "all"
stdout: true

View File

@ -0,0 +1,10 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: template-single
spec:
template:
spec:
containers:
- name : main
image: template-single:develop

View File

@ -0,0 +1,14 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
- configmap.yaml
patchesStrategicMerge:
- deployment.yaml
namespace: default

View File

@ -0,0 +1,16 @@
FROM loads/alpine:3.8
###############################################################################
# INSTALLATION
###############################################################################
ENV WORKDIR /app
ADD resource $WORKDIR/
ADD ./temp/linux_amd64/main $WORKDIR/main
RUN chmod +x $WORKDIR/main
###############################################################################
# START
###############################################################################
WORKDIR $WORKDIR
CMD ./main

View File

@ -0,0 +1,8 @@
#!/bin/bash
# This shell is executed before docker build.

0
manifest/i18n/.gitkeep Normal file
View File

View File

View File

View File

View File

View File

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