commit 07847a2d9ed2f487389b1c1de2c302a26f7f5bba Author: hucan <951870319@qq.com> Date: Thu Feb 6 11:14:33 2025 +0800 1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..24cb16d --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +.idea +.vscode +*/.DS_Store +static/uploadfile +main.exe +*.exe +go-admin +go-admin.exe +temp/ +!temp +vendor +config/settings.dev.yml +config/settings.dev.*.yml +config/settings.dev.*.yml.log +temp/logs +config/settings.dev.yml.log +config/settings.b.dev.yml +cmd/migrate/migration/version-local/* +!cmd/migrate/migration/version-local/doc.go + +# go sum +go.sum +config/settings.deva.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fb3e023 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM alpine + +# ENV GOPROXY https://goproxy.cn/ + +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories + +RUN apk update --no-cache +RUN apk add --update gcc g++ libc6-compat +RUN apk add --no-cache ca-certificates +RUN apk add --no-cache tzdata +ENV TZ Asia/Shanghai + +COPY ./main /main +COPY ./config/settings.demo.yml /config/settings.yml +COPY ./go-admin-db.db /go-admin-db.db +EXPOSE 8000 +RUN chmod +x /main +CMD ["/main","server","-c", "/config/settings.yml"] \ No newline at end of file diff --git a/Dockerfilebak b/Dockerfilebak new file mode 100644 index 0000000..c5e33a5 --- /dev/null +++ b/Dockerfilebak @@ -0,0 +1,28 @@ +FROM golang:alpine as builder + +MAINTAINER lwnmengjing + +ENV GOPROXY https://goproxy.cn/ + +WORKDIR /go/release +#RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories +RUN apk update && apk add tzdata + +COPY go.mod ./go.mod +RUN go mod tidy +COPY . . +RUN pwd && ls + +RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -a -installsuffix cgo -o go-admin . + +FROM alpine + +COPY --from=builder /go/release/go-admin / + +COPY --from=builder /go/release/config/settings.yml /config/settings.yml + +COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +EXPOSE 8000 + +CMD ["/go-admin","server","-c", "/config/settings.yml"] \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..36b35f4 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 go-admin-team + +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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..afdb1a1 --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +PROJECT:=go-admin + +.PHONY: build +build: + CGO_ENABLED=0 go build -ldflags="-w -s" -a -installsuffix "" -o go-admin . + +# make build-linux +build-linux: + @docker build -t go-admin:latest . + @echo "build successful" + +build-sqlite: + go build -tags sqlite3 -ldflags="-w -s" -a -installsuffix -o go-admin . + +# make run +run: + # delete go-admin-api container + @if [ $(shell docker ps -aq --filter name=go-admin --filter publish=8000) ]; then docker rm -f go-admin; fi + + # 启动方法一 run go-admin-api container docker-compose 启动方式 + # 进入到项目根目录 执行 make run 命令 + @docker-compose up -d + + # 启动方式二 docker run 这里注意-v挂载的宿主机的地址改为部署时的实际决对路径 + #@docker run --name=go-admin -p 8000:8000 -v /home/code/go/src/go-admin/go-admin/config:/go-admin-api/config -v /home/code/go/src/go-admin/go-admin-api/static:/go-admin/static -v /home/code/go/src/go-admin/go-admin/temp:/go-admin-api/temp -d --restart=always go-admin:latest + + @echo "go-admin service is running..." + + # delete Tag= 的镜像 + @docker image prune -f + @docker ps -a | grep "go-admin" + +stop: + # delete go-admin-api container + @if [ $(shell docker ps -aq --filter name=go-admin --filter publish=8000) ]; then docker-compose down; fi + #@if [ $(shell docker ps -aq --filter name=go-admin --filter publish=8000) ]; then docker rm -f go-admin; fi + #@echo "go-admin stop success" + + +#.PHONY: test +#test: +# go test -v ./... -cover + +#.PHONY: docker +#docker: +# docker build . -t go-admin:latest + +# make deploy +deploy: + + #@git checkout master + #@git pull origin master + make build-linux + make run diff --git a/README.Zh-cn.md b/README.Zh-cn.md new file mode 100644 index 0000000..f7acfb8 --- /dev/null +++ b/README.Zh-cn.md @@ -0,0 +1,352 @@ +# go-admin + + + + +[![Build Status](https://github.com/wenjianzhang/go-admin/workflows/build/badge.svg)](https://github.com/go-admin-team/go-admin) +[![Release](https://img.shields.io/github/release/go-admin-team/go-admin.svg?style=flat-square)](https://github.com/go-admin-team/go-admin/releases) +[![License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/go-admin-team/go-admin) + +[English](https://github.com/go-admin-team/go-admin/blob/master/README.md) | 简体中文 + +基于Gin + Vue + Element UI OR Arco Design OR Ant Design的前后端分离权限管理系统,系统初始化极度简单,只需要配置文件中,修改数据库连接,系统支持多指令操作,迁移指令可以让初始化数据库信息变得更简单,服务指令可以很简单的启动api服务 + +[在线文档](https://www.go-admin.pro) + +[前端项目](https://github.com/go-admin-team/go-admin-ui) + +[视频教程](https://space.bilibili.com/565616721/channel/detail?cid=125737) + +## 🎬 在线体验 + +Element UI vue体验:[https://vue2.go-admin.dev](https://vue2.go-admin.dev/#/login) +> ⚠️⚠️⚠️ 账号 / 密码: admin / 123456 + +Arco Design vue3 demo:[https://vue3.go-admin.dev](https://vue3.go-admin.dev/#/login) +> ⚠️⚠️⚠️ 账号 / 密码: admin / 123456 + +antd体验:[https://antd.go-admin.pro](https://antd.go-admin.pro/) +> ⚠️⚠️⚠️ 账号 / 密码: admin / 123456 + +## ✨ 特性 + +- 遵循 RESTful API 设计规范 + +- 基于 GIN WEB API 框架,提供了丰富的中间件支持(用户认证、跨域、访问日志、追踪ID等) + +- 基于Casbin的 RBAC 访问控制模型 + +- JWT 认证 + +- 支持 Swagger 文档(基于swaggo) + +- 基于 GORM 的数据库存储,可扩展多种类型数据库 + +- 配置文件简单的模型映射,快速能够得到想要的配置 + +- 代码生成工具 + +- 表单构建工具 + +- 多指令模式 + +- 多租户的支持 + +- TODO: 单元测试 + +## 🎁 内置 + +1. 多租户:系统默认支持多租户,按库分离,一个库一个租户。 +1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。 +2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。 +3. 岗位管理:配置系统用户所属担任职务。 +4. 菜单管理:配置系统菜单,操作权限,按钮权限标识,接口权限等。 +5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。 +6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。 +7. 参数管理:对系统动态配置常用参数。 +8. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。 +9. 登录日志:系统登录日志记录查询包含登录异常。 +1. 接口文档:根据业务代码自动生成相关的api接口文档。 +1. 代码生成:根据数据表结构生成对应的增删改查相对应业务,全程可视化操作,让基本业务可以零代码实现。 +1. 表单构建:自定义页面样式,拖拉拽实现页面布局。 +1. 服务监控:查看一些服务器的基本信息。 +1. 内容管理:demo功能,下设分类管理、内容管理。可以参考使用方便快速入门。 +1. 定时任务:自动化任务,目前支持接口调用和函数调用。 + +## 准备工作 + +你需要在本地安装 [go] [gin] [node](http://nodejs.org/) 和 [git](https://git-scm.com/) + +同时配套了系列教程包含视频和文档,如何从下载完成到熟练使用,强烈建议大家先看完这些教程再来实践本项目!!! + +### 轻松实现go-admin写出第一个应用 - 文档教程 + +[步骤一 - 基础内容介绍](https://doc.zhangwj.com/guide/intro/tutorial01.html) + +[步骤二 - 实际应用 - 编写增删改查](https://doc.zhangwj.com/guide/intro/tutorial02.html) + +### 手把手教你从入门到放弃 - 视频教程 + +[如何启动go-admin](https://www.bilibili.com/video/BV1z5411x7JG) + +[使用生成工具轻松实现业务](https://www.bilibili.com/video/BV1Dg4y1i79D) + +[v1.1.0版本代码生成工具-释放双手](https://www.bilibili.com/video/BV1N54y1i71P) [进阶] + +[多命令启动方式讲解以及IDE配置](https://www.bilibili.com/video/BV1Fg4y1q7ph) + +[go-admin菜单的配置说明](https://www.bilibili.com/video/BV1Wp4y1D715) [必看] + +[如何配置菜单信息以及接口信息](https://www.bilibili.com/video/BV1zv411B7nG) [必看] + +[go-admin权限配置使用说明](https://www.bilibili.com/video/BV1rt4y197d3) [必看] + +[go-admin数据权限使用说明](https://www.bilibili.com/video/BV1LK4y1s71e) [必看] + +**如有问题请先看上述使用文档和文章,若不能满足,欢迎 issue 和 pr ,视频教程和文档持续更新中** + +## 📦 本地开发 + +### 环境要求 + +go 1.18 + +node版本: v14.16.0 + +npm版本: 6.14.11 + +### 开发目录创建 + +```bash + +# 创建开发目录 +mkdir goadmin +cd goadmin +``` + +### 获取代码 + +> 重点注意:两个项目必须放在同一文件夹下; + +```bash +# 获取后端代码 +git clone https://github.com/go-admin-team/go-admin.git + +# 获取前端代码 +git clone https://github.com/go-admin-team/go-admin-ui.git + +``` + +### 启动说明 + +#### 服务端启动说明 + +```bash +# 进入 go-admin 后端项目 +cd ./go-admin + +# 更新整理依赖 +go mod tidy + +# 编译项目 +go build + +# 修改配置 +# 文件路径 go-admin/config/settings.yml +vi ./config/settings.yml + +# 1. 配置文件中修改数据库信息 +# 注意: settings.database 下对应的配置数据 +# 2. 确认log路径 +``` + +⚠️注意 在windows环境如果没有安装中CGO,会出现这个问题; + +```bash +E:\go-admin>go build +# github.com/mattn/go-sqlite3 +cgo: exec /missing-cc: exec: "/missing-cc": file does not exist +``` + +or + +```bash +D:\Code\go-admin>go build +# github.com/mattn/go-sqlite3 +cgo: exec gcc: exec: "gcc": executable file not found in %PATH% +``` + +[解决cgo问题进入](https://doc.go-admin.dev/zh-CN/guide/faq#cgo-%E7%9A%84%E9%97%AE%E9%A2%98) + + +#### 初始化数据库,以及服务启动 + +``` bash +# 首次配置需要初始化数据库资源信息 +# macOS or linux 下使用 +$ ./go-admin migrate -c config/settings.dev.yml + +# ⚠️注意:windows 下使用 +$ go-admin.exe migrate -c config/settings.dev.yml + + +# 启动项目,也可以用IDE进行调试 +# macOS or linux 下使用 +$ ./go-admin server -c config/settings.yml + + +# ⚠️注意:windows 下使用 +$ go-admin.exe server -c config/settings.yml +``` + +#### sys_api 表的数据如何添加 + +在项目启动时,使用`-a true` 系统会自动添加缺少的接口数据 +```bash +./go-admin server -c config/settings.yml -a true +``` + +#### 使用docker 编译启动 + +```shell +# 编译镜像 +docker build -t go-admin . + +# 启动容器,第一个go-admin是容器名字,第二个go-admin是镜像名称 +# -v 映射配置文件 本地路径:容器路径 +docker run --name go-admin -p 8000:8000 -v /config/settings.yml:/config/settings.yml -d go-admin-server +``` + +#### 文档生成 + +```bash +go generate +``` + +#### 交叉编译 + +```bash +# windows +env GOOS=windows GOARCH=amd64 go build main.go + +# or +# linux +env GOOS=linux GOARCH=amd64 go build main.go +``` + +### UI交互端启动说明 + +```bash +# 安装依赖 +npm install + +# 建议不要直接使用 cnpm 安装依赖,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题 +npm install --registry=https://registry.npmmirror.com + +# 启动服务 +npm run dev +``` + +## 📨 互动 + + + + + + + + + + + + + + +
wenjianzhang
微信公众号🔥🔥🔥go-admin技术交流乙号哔哩哔哩🔥🔥🔥
+ +## 💎 贡献者 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +## JetBrains 开源证书支持 + +`go-admin` 项目一直以来都是在 JetBrains 公司旗下的 GoLand 集成开发环境中进行开发,基于 **free JetBrains Open Source license(s)** 正版免费授权,在此表达我的谢意。 + + + +## 🤝 特别感谢 + +1. [ant-design](https://github.com/ant-design/ant-design) +2. [ant-design-pro](https://github.com/ant-design/ant-design-pro) +2. [arco-design](https://github.com/arco-design/arco-design) +2. [arco-design-pro](https://github.com/arco-design/arco-design-pro) +4. [gin](https://github.com/gin-gonic/gin) +5. [casbin](https://github.com/casbin/casbin) +6. [spf13/viper](https://github.com/spf13/viper) +7. [gorm](https://github.com/jinzhu/gorm) +8. [gin-swagger](https://github.com/swaggo/gin-swagger) +9. [jwt-go](https://github.com/dgrijalva/jwt-go) +10. [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) +11. [ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue) +12. [form-generator](https://github.com/JakHuang/form-generator) + + +## 🤟 打赏 + +> 如果你觉得这个项目帮助到了你,你可以帮作者买一杯果汁表示鼓励 :tropical_drink: + + + +## 🤝 链接 + +[Go开发者成长线路图](http://www.golangroadmap.com/) + +## 🔑 License + +[MIT](https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md) + +Copyright (c) 2024 wenjianzhang diff --git a/README.md b/README.md new file mode 100644 index 0000000..39bcf0a --- /dev/null +++ b/README.md @@ -0,0 +1,342 @@ + +# go-admin + + + + +[![Build Status](https://github.com/wenjianzhang/go-admin/workflows/build/badge.svg)](https://github.com/go-admin-team/go-admin) +[![Release](https://img.shields.io/github/release/go-admin-team/go-admin.svg?style=flat-square)](https://github.com/go-admin-team/go-admin/releases) +[![License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/go-admin-team/go-admin) + +English | [简体中文](https://github.com/go-admin-team/go-admin/blob/master/README.Zh-cn.md) + +The front-end and back-end separation authority management system based on Gin + Vue + Element UI OR Arco Design is extremely simple to initialize the system. You only need to modify the database connection in the configuration file. The system supports multi-instruction operations. Migration instructions can make it easier to initialize database information. Service instructions It's easy to start the api service. + +[documentation](https://www.go-admin.dev) + +[Front-end project](https://github.com/go-admin-team/go-admin-ui) + +[Video tutorial](https://space.bilibili.com/565616721/channel/detail?cid=125737) + +## 🎬 Online Demo + +Element UI vue demo:[https://vue2.go-admin.dev](https://vue2.go-admin.dev/#/login) +> 账号 / 密码: admin / 123456 + +Arco Design vue3 demo:[https://vue3.go-admin.dev](https://vue3.go-admin.dev/#/login) +> 账号 / 密码: admin / 123456 + +antd demo:[https://antd.go-admin.pro](https://antd.go-admin.pro/) +> 账号 / 密码: admin / 123456 +> +## ✨ Feature + +- Follow RESTful API design specifications + +- Based on the GIN WEB API framework, it provides rich middleware support (user authentication, cross-domain, access log, tracking ID, etc.) + +- RBAC access control model based on Casbin + +- JWT authentication + +- Support Swagger documents (based on swaggo) + +- Database storage based on GORM, which can expand multiple types of databases + +- Simple model mapping of configuration files to quickly get the desired configuration + +- Code generation tool + +- Form builder + +- Multi-command mode + +- TODO: unit test + + +## 🎁 Internal + +1. User management: The user is the system operator, this function mainly completes the system user configuration. +2. Department management: configure the system organization (company, department, group), and display the tree structure to support data permissions. +3. Position management: configure the positions of system users. +4. Menu management: configure the system menu, operation authority, button authority identification, interface authority, etc. +5. Role management: Role menu permission assignment and role setting are divided into data scope permissions by organization. +6. Dictionary management: Maintain some relatively fixed data frequently used in the system. +7. Parameter management: dynamically configure common parameters for the system. +8. Operation log: system normal operation log record and query; system abnormal information log record and query. +9. Login log: The system login log record query contains login exceptions. +1. Interface documentation: Automatically generate related api interface documents according to the business code. +1. Code generation: According to the data table structure, generate the corresponding addition, deletion, modification, and check corresponding business, and the whole process of visual operation, so that the basic business can be implemented with zero code. +1. Form construction: Customize the page style, drag and drop to realize the page layout. +1. Service monitoring: View the basic information of some servers. +1. Content management: demo function, including classification management and content management. You can refer to the easy to use quick start. + +## Ready to work + +You need to install locally [go] [gin] [node](http://nodejs.org/) 和 [git](https://git-scm.com/) + +At the same time, a series of tutorials including videos and documents are provided. How to complete the downloading to the proficient use, it is strongly recommended that you read these tutorials before you practice this project! ! ! + +### Easily implement go-admin to write the first application-documentation tutorial + +[Step 1 - basic content introduction](https://doc.zhangwj.com/guide/intro/tutorial01.html) + +[Step 2 - Practical application - writing database operations](https://doc.zhangwj.com/guide/intro/tutorial02.html) + +### Teach you from getting started to giving up-video tutorial + +[How to start go-admin](https://www.bilibili.com/video/BV1z5411x7JG) + +[Easily implement business using build tools](https://www.bilibili.com/video/BV1Dg4y1i79D) + +[v1.1.0 version code generation tool-free your hands](https://www.bilibili.com/video/BV1N54y1i71P) [Advanced] + +[Explanation of multi-command startup mode and IDE configuration](https://www.bilibili.com/video/BV1Fg4y1q7ph) + +[Configuration instructions for go-admin menu](https://www.bilibili.com/video/BV1Wp4y1D715) [Must see] + +[How to configure menu information and interface information](https://www.bilibili.com/video/BV1zv411B7nG) [Must see] + +[go-admin permission configuration instructions](https://www.bilibili.com/video/BV1rt4y197d3) [Must see] + +[Instructions for use of go-admin data permissions](https://www.bilibili.com/video/BV1LK4y1s71e) [Must see] + +**If you have any questions, please read the above-mentioned usage documents and articles first. If you are not satisfied, welcome to issue and pr. Video tutorials and documents are being updated continuously.** + +## 📦 Local development + +### Environmental requirements + +go 1.18 + +nodejs: v14.16.0 + +npm: 6.14.11 + +### Development directory creation + +```bash + +# Create a development directory +mkdir goadmin +cd goadmin +``` + +### Get the code + +> Important note: the two projects must be placed in the same folder; + +```bash +# Get backend code +git clone https://github.com/go-admin-team/go-admin.git + +# Get the front-end code +git clone https://github.com/go-admin-team/go-admin-ui.git + +``` + +### Startup instructions + +#### Server startup instructions + +```bash +# Enter the go-admin backend project +cd ./go-admin + +# Update dependencies +go mod tidy + +# Compile the project +go build + +# Change setting +# File path go-admin/config/settings.yml +vi ./config/settings.yml + +# 1. Modify the database information in the configuration file +# Note: The corresponding configuration data under settings.database +# 2. Confirm the log path +``` + +:::tip ⚠️Note that this problem will occur if CGO is not installed in the windows10+ environment; + +```bash +E:\go-admin>go build +# github.com/mattn/go-sqlite3 +cgo: exec /missing-cc: exec: "/missing-cc": file does not exist +``` + +or + +```bash +D:\Code\go-admin>go build +# github.com/mattn/go-sqlite3 +cgo: exec gcc: exec: "gcc": executable file not found in %PATH% +``` + +[Solve the cgo problem and enter](https://doc.go-admin.dev/guide/faq#cgo-%E7%9A%84%E9%97%AE%E9%A2%98) + +::: + +#### Initialize the database, and start the service + +``` bash +# The first configuration needs to initialize the database resource information +# Use under macOS or linux +$ ./go-admin migrate -c config/settings.dev.yml + +# ⚠️Note: Use under windows +$ go-admin.exe migrate -c config/settings.dev.yml + +# Start the project, you can also use the IDE for debugging +# Use under macOS or linux +$ ./go-admin server -c config/settings.yml + +# ⚠️Note: Use under windows +$ go-admin.exe server -c config/settings.yml +``` + +#### Use docker to compile and start + +```shell +# Compile the image +docker build -t go-admin . + + +# Start the container, the first go-admin is the container name, and the second go-admin is the image name +# -v Mapping configuration file Local path: container path +docker run --name go-admin -p 8000:8000 -v /config/settings.yml:/config/settings.yml -d go-admin-server +``` + + + +#### Generation Document + +```bash +go generate +``` + +#### Cross compile +```bash +# windows +env GOOS=windows GOARCH=amd64 go build main.go + +# or +# linux +env GOOS=linux GOARCH=amd64 go build main.go +``` + +### UI interactive terminal startup instructions + +```bash +# Installation dependencies +npm install # or cnpm install + +# Start service +npm run dev +``` + +## 📨 Interactive + + + + + + + + + + + + + + +
wenjianzhang
WechatWechat公众号🔥🔥🔥go-admin技术交流乙号bilibili🔥🔥🔥
+ +## 💎 Contributors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +## JetBrains open source certificate support + +The `go-admin` project has always been developed in the GoLand integrated development environment under JetBrains, based on the **free JetBrains Open Source license(s)** genuine free license. I would like to express my gratitude. + + + + +## 🤝 Thanks + +1. [ant-design](https://github.com/ant-design/ant-design) +2. [ant-design-pro](https://github.com/ant-design/ant-design-pro) +2. [arco-design](https://github.com/arco-design/arco-design) +2. [arco-design-pro](https://github.com/arco-design/arco-design-pro) +2. [gin](https://github.com/gin-gonic/gin) +2. [casbin](https://github.com/casbin/casbin) +2. [spf13/viper](https://github.com/spf13/viper) +2. [gorm](https://github.com/jinzhu/gorm) +2. [gin-swagger](https://github.com/swaggo/gin-swagger) +2. [jwt-go](https://github.com/dgrijalva/jwt-go) +2. [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) +2. [ruoyi-vue](https://gitee.com/y_project/RuoYi-Vue) +2. [form-generator](https://github.com/JakHuang/form-generator) + +## 🤟 Sponsor Us + +> If you think this project helped you, you can buy a glass of juice for the author to show encouragement :tropical_drink: + + + +## 🤝 Link +- [Go developer growth roadmap](http://www.golangroadmap.com/) +- [mss-boot-io](https://docs.mss-boot-io.top/) + +## 🔑 License + +[MIT](https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md) + +Copyright (c) 2022 wenjianzhang diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..c419263 --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-cayman \ No newline at end of file diff --git a/app/admin/apis/captcha.go b/app/admin/apis/captcha.go new file mode 100644 index 0000000..f5bccac --- /dev/null +++ b/app/admin/apis/captcha.go @@ -0,0 +1,37 @@ +package apis + +import ( + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/captcha" +) + +type System struct { + api.Api +} + +// GenerateCaptchaHandler 获取验证码 +// @Summary 获取验证码 +// @Description 获取验证码 +// @Tags 登陆 +// @Success 200 {object} response.Response{data=string,id=string,msg=string} "{"code": 200, "data": [...]}" +// @Router /api/v1/captcha [get] +func (e System) GenerateCaptchaHandler(c *gin.Context) { + err := e.MakeContext(c).Errors + if err != nil { + e.Error(500, err, "服务初始化失败!") + return + } + id, b64s, err := captcha.DriverDigitFunc() + if err != nil { + e.Logger.Errorf("DriverDigitFunc error, %s", err.Error()) + e.Error(500, err, "验证码获取失败") + return + } + e.Custom(gin.H{ + "code": 200, + "data": b64s, + "id": id, + "msg": "success", + }) +} diff --git a/app/admin/apis/go_admin.go b/app/admin/apis/go_admin.go new file mode 100644 index 0000000..a44da2f --- /dev/null +++ b/app/admin/apis/go_admin.go @@ -0,0 +1,39 @@ +package apis + +import ( + "github.com/gin-gonic/gin" +) + +const INDEX = ` + + + + +GO-ADMIN欢迎您 + + + + + + + + +` + +func GoAdmin(c *gin.Context) { + c.Header("Content-Type", "text/html; charset=utf-8") + c.String(200, INDEX) +} diff --git a/app/admin/apis/line_account_setting.go b/app/admin/apis/line_account_setting.go new file mode 100644 index 0000000..a82da5f --- /dev/null +++ b/app/admin/apis/line_account_setting.go @@ -0,0 +1,191 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineAccountSetting struct { + api.Api +} + +// GetPage 获取登录账户管理列表 +// @Summary 获取登录账户管理列表 +// @Description 获取登录账户管理列表 +// @Tags 登录账户管理 +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineAccountSetting}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-account-setting [get] +// @Security Bearer +func (e LineAccountSetting) GetPage(c *gin.Context) { + req := dto.LineAccountSettingGetPageReq{} + s := service.LineAccountSetting{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineAccountSetting, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取登录账户管理失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取登录账户管理 +// @Summary 获取登录账户管理 +// @Description 获取登录账户管理 +// @Tags 登录账户管理 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineAccountSetting} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-account-setting/{id} [get] +// @Security Bearer +func (e LineAccountSetting) Get(c *gin.Context) { + req := dto.LineAccountSettingGetReq{} + s := service.LineAccountSetting{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LineAccountSetting + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取登录账户管理失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK( object, "查询成功") +} + +// Insert 创建登录账户管理 +// @Summary 创建登录账户管理 +// @Description 创建登录账户管理 +// @Tags 登录账户管理 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineAccountSettingInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-account-setting [post] +// @Security Bearer +func (e LineAccountSetting) Insert(c *gin.Context) { + req := dto.LineAccountSettingInsertReq{} + s := service.LineAccountSetting{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建登录账户管理失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改登录账户管理 +// @Summary 修改登录账户管理 +// @Description 修改登录账户管理 +// @Tags 登录账户管理 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineAccountSettingUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-account-setting/{id} [put] +// @Security Bearer +func (e LineAccountSetting) Update(c *gin.Context) { + req := dto.LineAccountSettingUpdateReq{} + s := service.LineAccountSetting{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改登录账户管理失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "修改成功") +} + +// Delete 删除登录账户管理 +// @Summary 删除登录账户管理 +// @Description 删除登录账户管理 +// @Tags 登录账户管理 +// @Param data body dto.LineAccountSettingDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-account-setting [delete] +// @Security Bearer +func (e LineAccountSetting) Delete(c *gin.Context) { + s := service.LineAccountSetting{} + req := dto.LineAccountSettingDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除登录账户管理失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "删除成功") +} diff --git a/app/admin/apis/line_api_group.go b/app/admin/apis/line_api_group.go new file mode 100644 index 0000000..ab7b91d --- /dev/null +++ b/app/admin/apis/line_api_group.go @@ -0,0 +1,192 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineApiGroup struct { + api.Api +} + +// GetPage 获取用户绑定从属关系表列表 +// @Summary 获取用户绑定从属关系表列表 +// @Description 获取用户绑定从属关系表列表 +// @Tags 用户绑定从属关系表 +// @Param groupName query string false "用户组名称" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineApiGroup}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-api-group [get] +// @Security Bearer +func (e LineApiGroup) GetPage(c *gin.Context) { + req := dto.LineApiGroupGetPageReq{} + s := service.LineApiGroup{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineApiGroup, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取用户绑定从属关系表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取用户绑定从属关系表 +// @Summary 获取用户绑定从属关系表 +// @Description 获取用户绑定从属关系表 +// @Tags 用户绑定从属关系表 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineApiGroup} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-api-group/{id} [get] +// @Security Bearer +func (e LineApiGroup) Get(c *gin.Context) { + req := dto.LineApiGroupGetReq{} + s := service.LineApiGroup{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LineApiGroup + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取用户绑定从属关系表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(object, "查询成功") +} + +// Insert 创建用户绑定从属关系表 +// @Summary 创建用户绑定从属关系表 +// @Description 创建用户绑定从属关系表 +// @Tags 用户绑定从属关系表 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineApiGroupInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-api-group [post] +// @Security Bearer +func (e LineApiGroup) Insert(c *gin.Context) { + req := dto.LineApiGroupInsertReq{} + s := service.LineApiGroup{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建用户绑定从属关系表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改用户绑定从属关系表 +// @Summary 修改用户绑定从属关系表 +// @Description 修改用户绑定从属关系表 +// @Tags 用户绑定从属关系表 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineApiGroupUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-api-group/{id} [put] +// @Security Bearer +func (e LineApiGroup) Update(c *gin.Context) { + req := dto.LineApiGroupUpdateReq{} + s := service.LineApiGroup{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改用户绑定从属关系表失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "修改成功") +} + +// Delete 删除用户绑定从属关系表 +// @Summary 删除用户绑定从属关系表 +// @Description 删除用户绑定从属关系表 +// @Tags 用户绑定从属关系表 +// @Param data body dto.LineApiGroupDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-api-group [delete] +// @Security Bearer +func (e LineApiGroup) Delete(c *gin.Context) { + s := service.LineApiGroup{} + req := dto.LineApiGroupDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除用户绑定从属关系表失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "删除成功") +} diff --git a/app/admin/apis/line_api_user.go b/app/admin/apis/line_api_user.go new file mode 100644 index 0000000..71a7983 --- /dev/null +++ b/app/admin/apis/line_api_user.go @@ -0,0 +1,267 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineApiUser struct { + api.Api +} + +// GetPage 获取api用户管理列表 +// @Summary 获取api用户管理列表 +// @Description 获取api用户管理列表 +// @Tags api用户管理 +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineApiUser}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-api-user [get] +// @Security Bearer +func (e LineApiUser) GetPage(c *gin.Context) { + req := dto.LineApiUserGetPageReq{} + s := service.LineApiUser{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineApiUser, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取api用户管理失败,\r\n失败信息 %s", err.Error())) + return + } + + for i, apiUser := range list { + if apiUser.UserId <= 0 { + list[i].OpenStatus = 1 + } + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取api用户管理 +// @Summary 获取api用户管理 +// @Description 获取api用户管理 +// @Tags api用户管理 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineApiUser} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-api-user/{id} [get] +// @Security Bearer +func (e LineApiUser) Get(c *gin.Context) { + req := dto.LineApiUserGetReq{} + s := service.LineApiUser{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LineApiUser + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取api用户管理失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(object, "查询成功") +} + +// Insert 创建api用户管理 +// @Summary 创建api用户管理 +// @Description 创建api用户管理 +// @Tags api用户管理 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineApiUserInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-api-user [post] +// @Security Bearer +func (e LineApiUser) Insert(c *gin.Context) { + req := dto.LineApiUserInsertReq{} + s := service.LineApiUser{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建api用户管理失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改api用户管理 +// @Summary 修改api用户管理 +// @Description 修改api用户管理 +// @Tags api用户管理 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineApiUserUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-api-user/{id} [put] +// @Security Bearer +func (e LineApiUser) Update(c *gin.Context) { + req := dto.LineApiUserUpdateReq{} + s := service.LineApiUser{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改api用户管理失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "修改成功") +} + +// Delete 删除api用户管理 +// @Summary 删除api用户管理 +// @Description 删除api用户管理 +// @Tags api用户管理 +// @Param data body dto.LineApiUserDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-api-user [delete] +// @Security Bearer +func (e LineApiUser) Delete(c *gin.Context) { + s := service.LineApiUser{} + req := dto.LineApiUserDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除api用户管理失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "删除成功") +} + +func (e LineApiUser) Bind(c *gin.Context) { + s := service.LineApiUser{} + req := dto.LineApiUserBindSubordinateReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + + err = s.Bind(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("绑定api用户关系失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(nil, "操作成功") +} + +func (e LineApiUser) GetUser(c *gin.Context) { + s := service.LineApiUser{} + req := dto.LineApiUserBindSubordinateReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + //p := actions.GetPermissionFromContext(c) + list := make([]models.LineApiUser, 0) + err = s.GetUser(&list) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(list, "操作成功") +} + +// GetMainUser 获取主账号 +func (e LineApiUser) GetMainUser(c *gin.Context) { + s := service.LineApiUser{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + //p := actions.GetPermissionFromContext(c) + list := make([]models.LineApiUser, 0) + err = s.GetMainUser(&list) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(list, "操作成功") +} diff --git a/app/admin/apis/line_coinnetwork.go b/app/admin/apis/line_coinnetwork.go new file mode 100644 index 0000000..1a85256 --- /dev/null +++ b/app/admin/apis/line_coinnetwork.go @@ -0,0 +1,191 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineCoinnetwork struct { + api.Api +} + +// GetPage 获取【币种网络】列表 +// @Summary 获取【币种网络】列表 +// @Description 获取【币种网络】列表 +// @Tags 【币种网络】 +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineCoinnetwork}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-coinnetwork [get] +// @Security Bearer +func (e LineCoinnetwork) GetPage(c *gin.Context) { + req := dto.LineCoinnetworkGetPageReq{} + s := service.LineCoinnetwork{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineCoinnetwork, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取【币种网络】失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取【币种网络】 +// @Summary 获取【币种网络】 +// @Description 获取【币种网络】 +// @Tags 【币种网络】 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineCoinnetwork} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-coinnetwork/{id} [get] +// @Security Bearer +func (e LineCoinnetwork) Get(c *gin.Context) { + req := dto.LineCoinnetworkGetReq{} + s := service.LineCoinnetwork{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LineCoinnetwork + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取【币种网络】失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK( object, "查询成功") +} + +// Insert 创建【币种网络】 +// @Summary 创建【币种网络】 +// @Description 创建【币种网络】 +// @Tags 【币种网络】 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineCoinnetworkInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-coinnetwork [post] +// @Security Bearer +func (e LineCoinnetwork) Insert(c *gin.Context) { + req := dto.LineCoinnetworkInsertReq{} + s := service.LineCoinnetwork{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建【币种网络】失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改【币种网络】 +// @Summary 修改【币种网络】 +// @Description 修改【币种网络】 +// @Tags 【币种网络】 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineCoinnetworkUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-coinnetwork/{id} [put] +// @Security Bearer +func (e LineCoinnetwork) Update(c *gin.Context) { + req := dto.LineCoinnetworkUpdateReq{} + s := service.LineCoinnetwork{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改【币种网络】失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "修改成功") +} + +// Delete 删除【币种网络】 +// @Summary 删除【币种网络】 +// @Description 删除【币种网络】 +// @Tags 【币种网络】 +// @Param data body dto.LineCoinnetworkDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-coinnetwork [delete] +// @Security Bearer +func (e LineCoinnetwork) Delete(c *gin.Context) { + s := service.LineCoinnetwork{} + req := dto.LineCoinnetworkDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除【币种网络】失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "删除成功") +} diff --git a/app/admin/apis/line_cointonetwork.go b/app/admin/apis/line_cointonetwork.go new file mode 100644 index 0000000..2a067a2 --- /dev/null +++ b/app/admin/apis/line_cointonetwork.go @@ -0,0 +1,191 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineCointonetwork struct { + api.Api +} + +// GetPage 获取币种与网络关系列表 +// @Summary 获取币种与网络关系列表 +// @Description 获取币种与网络关系列表 +// @Tags 币种与网络关系 +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineCointonetwork}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-cointonetwork [get] +// @Security Bearer +func (e LineCointonetwork) GetPage(c *gin.Context) { + req := dto.LineCointonetworkGetPageReq{} + s := service.LineCointonetwork{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineCointonetwork, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取币种与网络关系失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取币种与网络关系 +// @Summary 获取币种与网络关系 +// @Description 获取币种与网络关系 +// @Tags 币种与网络关系 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineCointonetwork} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-cointonetwork/{id} [get] +// @Security Bearer +func (e LineCointonetwork) Get(c *gin.Context) { + req := dto.LineCointonetworkGetReq{} + s := service.LineCointonetwork{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LineCointonetwork + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取币种与网络关系失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK( object, "查询成功") +} + +// Insert 创建币种与网络关系 +// @Summary 创建币种与网络关系 +// @Description 创建币种与网络关系 +// @Tags 币种与网络关系 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineCointonetworkInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-cointonetwork [post] +// @Security Bearer +func (e LineCointonetwork) Insert(c *gin.Context) { + req := dto.LineCointonetworkInsertReq{} + s := service.LineCointonetwork{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建币种与网络关系失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改币种与网络关系 +// @Summary 修改币种与网络关系 +// @Description 修改币种与网络关系 +// @Tags 币种与网络关系 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineCointonetworkUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-cointonetwork/{id} [put] +// @Security Bearer +func (e LineCointonetwork) Update(c *gin.Context) { + req := dto.LineCointonetworkUpdateReq{} + s := service.LineCointonetwork{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改币种与网络关系失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "修改成功") +} + +// Delete 删除币种与网络关系 +// @Summary 删除币种与网络关系 +// @Description 删除币种与网络关系 +// @Tags 币种与网络关系 +// @Param data body dto.LineCointonetworkDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-cointonetwork [delete] +// @Security Bearer +func (e LineCointonetwork) Delete(c *gin.Context) { + s := service.LineCointonetwork{} + req := dto.LineCointonetworkDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除币种与网络关系失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "删除成功") +} diff --git a/app/admin/apis/line_direction.go b/app/admin/apis/line_direction.go new file mode 100644 index 0000000..0d50b35 --- /dev/null +++ b/app/admin/apis/line_direction.go @@ -0,0 +1,240 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineDirection struct { + api.Api +} + +// GetPage 获取预估方向管理列表 +// @Summary 获取预估方向管理列表 +// @Description 获取预估方向管理列表 +// @Tags 预估方向管理 +// @Param symbol query string false "交易对" +// @Param type query int64 false "交易对类型:1=现货,2=合约" +// @Param direction query string false "预估方向" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineDirection}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-direction [get] +// @Security Bearer +func (e LineDirection) GetPage(c *gin.Context) { + req := dto.LineDirectionGetPageReq{} + s := service.LineDirection{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineDirection, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取预估方向管理失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取预估方向管理 +// @Summary 获取预估方向管理 +// @Description 获取预估方向管理 +// @Tags 预估方向管理 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineDirection} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-direction/{id} [get] +// @Security Bearer +func (e LineDirection) Get(c *gin.Context) { + req := dto.LineDirectionGetReq{} + s := service.LineDirection{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LineDirection + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取预估方向管理失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(object, "查询成功") +} + +// Insert 创建预估方向管理 +// @Summary 创建预估方向管理 +// @Description 创建预估方向管理 +// @Tags 预估方向管理 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineDirectionInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-direction [post] +// @Security Bearer +func (e LineDirection) Insert(c *gin.Context) { + req := dto.LineDirectionInsertReq{} + s := service.LineDirection{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建预估方向管理失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改预估方向管理 +// @Summary 修改预估方向管理 +// @Description 修改预估方向管理 +// @Tags 预估方向管理 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineDirectionUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-direction/{id} [put] +// @Security Bearer +func (e LineDirection) Update(c *gin.Context) { + req := dto.LineDirectionUpdateReq{} + s := service.LineDirection{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改预估方向管理失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "修改成功") +} + +// Delete 删除预估方向管理 +// @Summary 删除预估方向管理 +// @Description 删除预估方向管理 +// @Tags 预估方向管理 +// @Param data body dto.LineDirectionDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-direction [delete] +// @Security Bearer +func (e LineDirection) Delete(c *gin.Context) { + s := service.LineDirection{} + req := dto.LineDirectionDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除预估方向管理失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "删除成功") +} + +// AddDirection 新增预估方向 +func (e LineDirection) AddDirection(c *gin.Context) { + s := service.LineDirection{} + req := dto.AddDirectionReq{} + + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + err = s.AddDirection(req) + if err != nil { + e.Error(500, err, fmt.Sprintf("生成预估方向失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(nil, "操作成功") +} + +// 重新统计分组信息 +func (e LineDirection) ReloadGroupData(c *gin.Context) { + s := service.LineDirection{} + + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + if err := s.ReloadGroup(); err != nil { + e.Error(500, err, fmt.Sprintf("重新统计分组信息失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(nil, "操作成功") +} diff --git a/app/admin/apis/line_order_template_logs.go b/app/admin/apis/line_order_template_logs.go new file mode 100644 index 0000000..7160a1e --- /dev/null +++ b/app/admin/apis/line_order_template_logs.go @@ -0,0 +1,194 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineOrderTemplateLogs struct { + api.Api +} + +// GetPage 获取委托下单模板列表 +// @Summary 获取委托下单模板列表 +// @Description 获取委托下单模板列表 +// @Tags 委托下单模板 +// @Param name query string false "模板名称" +// @Param userId query int64 false "用户id" +// @Param type query int64 false "模板类型:1=单独添加;2=批量添加" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineOrderTemplateLogs}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-order-template-logs [get] +// @Security Bearer +func (e LineOrderTemplateLogs) GetPage(c *gin.Context) { + req := dto.LineOrderTemplateLogsGetPageReq{} + s := service.LineOrderTemplateLogs{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineOrderTemplateLogs, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取委托下单模板失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取委托下单模板 +// @Summary 获取委托下单模板 +// @Description 获取委托下单模板 +// @Tags 委托下单模板 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineOrderTemplateLogs} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-order-template-logs/{id} [get] +// @Security Bearer +func (e LineOrderTemplateLogs) Get(c *gin.Context) { + req := dto.LineOrderTemplateLogsGetReq{} + s := service.LineOrderTemplateLogs{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LineOrderTemplateLogs + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取委托下单模板失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK( object, "查询成功") +} + +// Insert 创建委托下单模板 +// @Summary 创建委托下单模板 +// @Description 创建委托下单模板 +// @Tags 委托下单模板 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineOrderTemplateLogsInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-order-template-logs [post] +// @Security Bearer +func (e LineOrderTemplateLogs) Insert(c *gin.Context) { + req := dto.LineOrderTemplateLogsInsertReq{} + s := service.LineOrderTemplateLogs{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建委托下单模板失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改委托下单模板 +// @Summary 修改委托下单模板 +// @Description 修改委托下单模板 +// @Tags 委托下单模板 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineOrderTemplateLogsUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-order-template-logs/{id} [put] +// @Security Bearer +func (e LineOrderTemplateLogs) Update(c *gin.Context) { + req := dto.LineOrderTemplateLogsUpdateReq{} + s := service.LineOrderTemplateLogs{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改委托下单模板失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "修改成功") +} + +// Delete 删除委托下单模板 +// @Summary 删除委托下单模板 +// @Description 删除委托下单模板 +// @Tags 委托下单模板 +// @Param data body dto.LineOrderTemplateLogsDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-order-template-logs [delete] +// @Security Bearer +func (e LineOrderTemplateLogs) Delete(c *gin.Context) { + s := service.LineOrderTemplateLogs{} + req := dto.LineOrderTemplateLogsDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除委托下单模板失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "删除成功") +} diff --git a/app/admin/apis/line_pre_order.go b/app/admin/apis/line_pre_order.go new file mode 100644 index 0000000..735983a --- /dev/null +++ b/app/admin/apis/line_pre_order.go @@ -0,0 +1,613 @@ +package apis + +import ( + "fmt" + "go-admin/common/const/rediskey" + "go-admin/common/global" + "go-admin/common/helper" + "strings" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LinePreOrder struct { + api.Api +} + +// GetPage 获取委托管理列表 +// @Summary 获取委托管理列表 +// @Description 获取委托管理列表 +// @Tags 委托管理 +// @Param apiId query string false "api用户" +// @Param symbol query string false "交易对" +// @Param quoteSymbol query string false "计较货币" +// @Param signPriceType query string false "对标价类型: new=最新价格;tall=24小时最高;low=24小时最低;mixture=标记价;entrust=委托实价;add=补仓" +// @Param rate query string false "下单百分比" +// @Param site query string false "购买方向:BUY=买;SELL=卖" +// @Param orderSn query string false "订单号" +// @Param orderType query string false "订单类型:1=现货;2=合约;3=合约止盈;4=合约止损;5=现货止盈;6=现货止损;7=止损补仓;8=现货加仓;9=现货平仓;10 = 合约止损补仓,11=合约加仓;12=合约平仓" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LinePreOrder}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-pre-order [get] +// @Security Bearer +func (e LinePreOrder) GetPage(c *gin.Context) { + req := dto.LinePreOrderGetPageReq{} + s := service.LinePreOrder{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LinePreOrder, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取委托管理失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// GetOrderPage 获取LinePreOrder列表 +func (e LinePreOrder) GetOrderPage(c *gin.Context) { + req := dto.LinePreOrderGetPageReq{} + s := service.LinePreOrder{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LinePreOrder, 0) + var count int64 + + err = s.GetOrderPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取委托管理失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +func (e LinePreOrder) GetChildOrderList(c *gin.Context) { + s := service.LinePreOrder{} + req := dto.GetChildOrderReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + if req.Id <= 0 { + e.Error(500, err, "参数错误") + return + } + p := actions.GetPermissionFromContext(c) + list := make([]models.LinePreOrder, 0) + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + err = s.GetChildList(&req, p, &list) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取子订单信息失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(list, "操作成功") +} + +// Get 获取委托管理 +// @Summary 获取委托管理 +// @Description 获取委托管理 +// @Tags 委托管理 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LinePreOrder} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-pre-order/{id} [get] +// @Security Bearer +func (e LinePreOrder) Get(c *gin.Context) { + req := dto.LinePreOrderGetReq{} + s := service.LinePreOrder{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LinePreOrder + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取委托管理失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(object, "查询成功") +} + +// Insert 创建委托管理 +// @Summary 创建委托管理 +// @Description 创建委托管理 +// @Tags 委托管理 +// @Accept application/json +// @Product application/json +// @Param data body dto.LinePreOrderInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-pre-order [post] +// @Security Bearer +func (e LinePreOrder) Insert(c *gin.Context) { + req := dto.LinePreOrderInsertReq{} + s := service.LinePreOrder{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建委托管理失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改委托管理 +// @Summary 修改委托管理 +// @Description 修改委托管理 +// @Tags 委托管理 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LinePreOrderUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-pre-order/{id} [put] +// @Security Bearer +func (e LinePreOrder) Update(c *gin.Context) { + req := dto.LinePreOrderUpdateReq{} + s := service.LinePreOrder{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改委托管理失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "修改成功") +} + +// Delete 删除委托管理 +// @Summary 删除委托管理 +// @Description 删除委托管理 +// @Tags 委托管理 +// @Param data body dto.LinePreOrderDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-pre-order [delete] +// @Security Bearer +func (e LinePreOrder) Delete(c *gin.Context) { + s := service.LinePreOrder{} + req := dto.LinePreOrderDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除委托管理失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "删除成功") +} + +// AddPreOrder 单个添加 +func (e LinePreOrder) AddPreOrder(c *gin.Context) { + s := service.LinePreOrder{} + req := dto.LineAddPreOrderReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + err = req.CheckParams() + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + p := actions.GetPermissionFromContext(c) + errs := make([]error, 0) + errStr := make([]string, 0) + var tickerSymbol string + if req.SymbolType == global.SYMBOL_SPOT { + tickerSymbol = helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val() + } else { + tickerSymbol = helper.DefaultRedis.Get(rediskey.FutSymbolTicker).Val() + } + s.AddPreOrder(&req, p, &errs, tickerSymbol) + + if len(errs) > 0 { + //e.Logger.Error(err) + for _, err2 := range errs { + errStr = append(errStr, err2.Error()) + } + e.Error(500, nil, strings.Join(errStr, ",")) + return + } + e.OK(nil, "操作成功") +} + +// BatchAddOrder 批量添加 +func (e LinePreOrder) BatchAddOrder(c *gin.Context) { + s := service.LinePreOrder{} + req := dto.LineBatchAddPreOrderReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + err = req.CheckParams() + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + p := actions.GetPermissionFromContext(c) + errs := make([]error, 0) + errStr := make([]string, 0) + s.AddBatchPreOrder(&req, p, &errs) + if len(errs) > 0 { + //e.Logger.Error(err) + for _, err2 := range errs { + errStr = append(errStr, err2.Error()) + } + e.Error(500, nil, strings.Join(errStr, ",")) + return + } + e.OK(nil, "操作成功") +} + +// QuickAddPreOrder 模板快速下单 +func (e LinePreOrder) QuickAddPreOrder(c *gin.Context) { + s := service.LinePreOrder{} + req := dto.QuickAddPreOrderReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + if req.Ids == "" { + e.Error(500, err, "参数错误") + return + } + p := actions.GetPermissionFromContext(c) + errs := make([]error, 0) + errStr := make([]string, 0) + err = s.QuickAddPreOrder(&req, p, &errs) + if len(errs) > 0 { + //e.Logger.Error(err) + for _, err2 := range errs { + errStr = append(errStr, err2.Error()) + } + e.Error(500, nil, strings.Join(errStr, ",")) + return + } + e.OK(nil, "操作成功") + +} + +// Lever 设置杠杆 +func (e LinePreOrder) Lever(c *gin.Context) { + s := service.LinePreOrder{} + req := dto.LeverReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + err = req.CheckParams() + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + errs := make([]error, 0) + errStr := make([]string, 0) + p := actions.GetPermissionFromContext(c) + + s.Lever(&req, p, &errs) + if len(errs) > 0 { + for _, err2 := range errs { + errStr = append(errStr, err2.Error()) + } + e.Error(500, nil, strings.Join(errStr, ",")) + return + } + e.OK(nil, "操作成功") +} + +// MarginType 设置仓位模式 +func (e LinePreOrder) MarginType(c *gin.Context) { + s := service.LinePreOrder{} + req := dto.MarginTypeReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + err = req.CheckParams() + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + errs := make([]error, 0) + errStr := make([]string, 0) + p := actions.GetPermissionFromContext(c) + + s.MarginType(&req, p, &errs) + if len(errs) > 0 { + for _, err2 := range errs { + errStr = append(errStr, err2.Error()) + } + e.Error(500, nil, strings.Join(errStr, ",")) + return + } + e.OK(nil, "操作成功") +} + +// CancelOpenOrder 取消指定交易对的委托 +func (e LinePreOrder) CancelOpenOrder(c *gin.Context) { + s := service.LinePreOrder{} + req := dto.CancelOpenOrderReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + if req.ApiId <= 0 { + e.Error(500, err, "参数错误") + return + } + errs := make([]error, 0) + errStr := make([]string, 0) + s.CancelOpenOrder(&req, &errs) + if len(errs) > 0 { + for _, err2 := range errs { + errStr = append(errStr, err2.Error()) + } + e.Error(500, nil, strings.Join(errStr, ",")) + return + } + e.OK(nil, "操作成功") +} + +// ClearAll 一键清除数据 +func (e LinePreOrder) ClearAll(c *gin.Context) { + s := service.LinePreOrder{} + req := dto.MarginTypeReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + err = s.ClearAll() + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + e.OK(nil, "操作成功") +} + +// ManuallyCover 手动加仓 +func (e LinePreOrder) ManuallyCover(c *gin.Context) { + s := service.LinePreOrder{} + req := dto.ManuallyCover{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + err = req.CheckParams() + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + p := actions.GetPermissionFromContext(c) + errs := make([]error, 0) + errStr := make([]string, 0) + + s.ManuallyCover(req, p, &errs) + if len(errs) > 0 { + for _, err2 := range errs { + errStr = append(errStr, err2.Error()) + } + e.Error(500, nil, strings.Join(errStr, ",")) + return + } + e.OK(nil, "操作成功") +} + +// ClosePosition 平仓 +func (e LinePreOrder) ClosePosition(c *gin.Context) { + s := service.LinePreOrder{} + req := dto.ClosePosition{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + err = req.CheckParams() + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + errs := make([]error, 0) + errStr := make([]string, 0) + if req.CloseType == 1 { + s.SpotClosePosition(&req, &errs) + } else { + s.FutClosePosition(&req, &errs) + } + if len(errs) > 0 { + for _, err2 := range errs { + errStr = append(errStr, err2.Error()) + } + e.Error(500, nil, strings.Join(errStr, ",")) + return + } + e.OK(nil, "操作成功") +} + +// ClearUnTriggered 清除待触发的交易对 +func (e LinePreOrder) ClearUnTriggered(c *gin.Context) { + s := service.LinePreOrder{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + err = s.ClearUnTriggered() + if err != nil { + e.Error(500, err, err.Error()) + return + } + e.OK(nil, "操作成功") +} + +func (e LinePreOrder) QueryOrder(c *gin.Context) { + s := service.LinePreOrder{} + req := dto.QueryOrderReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + res, err := s.QueryOrder(&req) + if err != nil { + e.Error(500, err, err.Error()) + return + } + e.OK(res, "操作成功") +} diff --git a/app/admin/apis/line_pre_order_status.go b/app/admin/apis/line_pre_order_status.go new file mode 100644 index 0000000..2388989 --- /dev/null +++ b/app/admin/apis/line_pre_order_status.go @@ -0,0 +1,191 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LinePreOrderStatus struct { + api.Api +} + +// GetPage 获取订单状态信息列表 +// @Summary 获取订单状态信息列表 +// @Description 获取订单状态信息列表 +// @Tags 订单状态信息 +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LinePreOrderStatus}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-pre-order-status [get] +// @Security Bearer +func (e LinePreOrderStatus) GetPage(c *gin.Context) { + req := dto.LinePreOrderStatusGetPageReq{} + s := service.LinePreOrderStatus{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LinePreOrderStatus, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取订单状态信息失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取订单状态信息 +// @Summary 获取订单状态信息 +// @Description 获取订单状态信息 +// @Tags 订单状态信息 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LinePreOrderStatus} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-pre-order-status/{id} [get] +// @Security Bearer +func (e LinePreOrderStatus) Get(c *gin.Context) { + req := dto.LinePreOrderStatusGetReq{} + s := service.LinePreOrderStatus{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LinePreOrderStatus + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取订单状态信息失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK( object, "查询成功") +} + +// Insert 创建订单状态信息 +// @Summary 创建订单状态信息 +// @Description 创建订单状态信息 +// @Tags 订单状态信息 +// @Accept application/json +// @Product application/json +// @Param data body dto.LinePreOrderStatusInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-pre-order-status [post] +// @Security Bearer +func (e LinePreOrderStatus) Insert(c *gin.Context) { + req := dto.LinePreOrderStatusInsertReq{} + s := service.LinePreOrderStatus{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建订单状态信息失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改订单状态信息 +// @Summary 修改订单状态信息 +// @Description 修改订单状态信息 +// @Tags 订单状态信息 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LinePreOrderStatusUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-pre-order-status/{id} [put] +// @Security Bearer +func (e LinePreOrderStatus) Update(c *gin.Context) { + req := dto.LinePreOrderStatusUpdateReq{} + s := service.LinePreOrderStatus{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改订单状态信息失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "修改成功") +} + +// Delete 删除订单状态信息 +// @Summary 删除订单状态信息 +// @Description 删除订单状态信息 +// @Tags 订单状态信息 +// @Param data body dto.LinePreOrderStatusDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-pre-order-status [delete] +// @Security Bearer +func (e LinePreOrderStatus) Delete(c *gin.Context) { + s := service.LinePreOrderStatus{} + req := dto.LinePreOrderStatusDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除订单状态信息失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "删除成功") +} diff --git a/app/admin/apis/line_pre_script.go b/app/admin/apis/line_pre_script.go new file mode 100644 index 0000000..3fbfc6d --- /dev/null +++ b/app/admin/apis/line_pre_script.go @@ -0,0 +1,193 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LinePreScript struct { + api.Api +} + +// GetPage 获取委托下单脚本记录表列表 +// @Summary 获取委托下单脚本记录表列表 +// @Description 获取委托下单脚本记录表列表 +// @Tags 委托下单脚本记录表 +// @Param apiId query int64 false "api用户" +// @Param status query string false "执行状态:0=等待执行,1=执行中,2=执行结束" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LinePreScript}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-pre-script [get] +// @Security Bearer +func (e LinePreScript) GetPage(c *gin.Context) { + req := dto.LinePreScriptGetPageReq{} + s := service.LinePreScript{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LinePreScript, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取委托下单脚本记录表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取委托下单脚本记录表 +// @Summary 获取委托下单脚本记录表 +// @Description 获取委托下单脚本记录表 +// @Tags 委托下单脚本记录表 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LinePreScript} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-pre-script/{id} [get] +// @Security Bearer +func (e LinePreScript) Get(c *gin.Context) { + req := dto.LinePreScriptGetReq{} + s := service.LinePreScript{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LinePreScript + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取委托下单脚本记录表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK( object, "查询成功") +} + +// Insert 创建委托下单脚本记录表 +// @Summary 创建委托下单脚本记录表 +// @Description 创建委托下单脚本记录表 +// @Tags 委托下单脚本记录表 +// @Accept application/json +// @Product application/json +// @Param data body dto.LinePreScriptInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-pre-script [post] +// @Security Bearer +func (e LinePreScript) Insert(c *gin.Context) { + req := dto.LinePreScriptInsertReq{} + s := service.LinePreScript{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建委托下单脚本记录表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改委托下单脚本记录表 +// @Summary 修改委托下单脚本记录表 +// @Description 修改委托下单脚本记录表 +// @Tags 委托下单脚本记录表 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LinePreScriptUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-pre-script/{id} [put] +// @Security Bearer +func (e LinePreScript) Update(c *gin.Context) { + req := dto.LinePreScriptUpdateReq{} + s := service.LinePreScript{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改委托下单脚本记录表失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "修改成功") +} + +// Delete 删除委托下单脚本记录表 +// @Summary 删除委托下单脚本记录表 +// @Description 删除委托下单脚本记录表 +// @Tags 委托下单脚本记录表 +// @Param data body dto.LinePreScriptDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-pre-script [delete] +// @Security Bearer +func (e LinePreScript) Delete(c *gin.Context) { + s := service.LinePreScript{} + req := dto.LinePreScriptDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除委托下单脚本记录表失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "删除成功") +} diff --git a/app/admin/apis/line_price_limit.go b/app/admin/apis/line_price_limit.go new file mode 100644 index 0000000..e59b546 --- /dev/null +++ b/app/admin/apis/line_price_limit.go @@ -0,0 +1,213 @@ +package apis + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LinePriceLimit struct { + api.Api +} + +// GetPage 获取涨跌幅列表 +// @Summary 获取涨跌幅列表 +// @Description 获取涨跌幅列表 +// @Tags 涨跌幅 +// @Param symbol query string false "交易对" +// @Param type query string false "类型:1=现货,2=合约" +// @Param directionStatus query string false "方向:1=涨,2=跌" +// @Param range query string false "幅度" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LinePriceLimit}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-price-limit [get] +// @Security Bearer +func (e LinePriceLimit) GetPage(c *gin.Context) { + req := dto.LinePriceLimitGetPageReq{} + s := service.LinePriceLimit{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LinePriceLimit, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取涨跌幅失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取涨跌幅 +// @Summary 获取涨跌幅 +// @Description 获取涨跌幅 +// @Tags 涨跌幅 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LinePriceLimit} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-price-limit/{id} [get] +// @Security Bearer +func (e LinePriceLimit) Get(c *gin.Context) { + req := dto.LinePriceLimitGetReq{} + s := service.LinePriceLimit{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LinePriceLimit + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取涨跌幅失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(object, "查询成功") +} + +// Insert 创建涨跌幅 +// @Summary 创建涨跌幅 +// @Description 创建涨跌幅 +// @Tags 涨跌幅 +// @Accept application/json +// @Product application/json +// @Param data body dto.LinePriceLimitInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-price-limit [post] +// @Security Bearer +func (e LinePriceLimit) Insert(c *gin.Context) { + req := dto.LinePriceLimitInsertReq{} + s := service.LinePriceLimit{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建涨跌幅失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改涨跌幅 +// @Summary 修改涨跌幅 +// @Description 修改涨跌幅 +// @Tags 涨跌幅 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LinePriceLimitUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-price-limit/{id} [put] +// @Security Bearer +func (e LinePriceLimit) Update(c *gin.Context) { + req := dto.LinePriceLimitUpdateReq{} + s := service.LinePriceLimit{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改涨跌幅失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "修改成功") +} + +// Delete 删除涨跌幅 +// @Summary 删除涨跌幅 +// @Description 删除涨跌幅 +// @Tags 涨跌幅 +// @Param data body dto.LinePriceLimitDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-price-limit [delete] +// @Security Bearer +func (e LinePriceLimit) Delete(c *gin.Context) { + s := service.LinePriceLimit{} + req := dto.LinePriceLimitDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除涨跌幅失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "删除成功") +} + +func (e LinePriceLimit) UpRange(c *gin.Context) { + s := service.LinePriceLimit{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + err = s.UpRange() + if err != nil { + e.Error(500, err, fmt.Sprintf("更新涨跌幅失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(nil, "操作成功") +} diff --git a/app/admin/apis/line_recharge.go b/app/admin/apis/line_recharge.go new file mode 100644 index 0000000..45c7cfc --- /dev/null +++ b/app/admin/apis/line_recharge.go @@ -0,0 +1,191 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineRecharge struct { + api.Api +} + +// GetPage 获取充值记录表列表 +// @Summary 获取充值记录表列表 +// @Description 获取充值记录表列表 +// @Tags 充值记录表 +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineRecharge}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-recharge [get] +// @Security Bearer +func (e LineRecharge) GetPage(c *gin.Context) { + req := dto.LineRechargeGetPageReq{} + s := service.LineRecharge{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineRecharge, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取充值记录表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取充值记录表 +// @Summary 获取充值记录表 +// @Description 获取充值记录表 +// @Tags 充值记录表 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineRecharge} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-recharge/{id} [get] +// @Security Bearer +func (e LineRecharge) Get(c *gin.Context) { + req := dto.LineRechargeGetReq{} + s := service.LineRecharge{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LineRecharge + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取充值记录表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK( object, "查询成功") +} + +// Insert 创建充值记录表 +// @Summary 创建充值记录表 +// @Description 创建充值记录表 +// @Tags 充值记录表 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineRechargeInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-recharge [post] +// @Security Bearer +func (e LineRecharge) Insert(c *gin.Context) { + req := dto.LineRechargeInsertReq{} + s := service.LineRecharge{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建充值记录表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改充值记录表 +// @Summary 修改充值记录表 +// @Description 修改充值记录表 +// @Tags 充值记录表 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineRechargeUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-recharge/{id} [put] +// @Security Bearer +func (e LineRecharge) Update(c *gin.Context) { + req := dto.LineRechargeUpdateReq{} + s := service.LineRecharge{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改充值记录表失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "修改成功") +} + +// Delete 删除充值记录表 +// @Summary 删除充值记录表 +// @Description 删除充值记录表 +// @Tags 充值记录表 +// @Param data body dto.LineRechargeDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-recharge [delete] +// @Security Bearer +func (e LineRecharge) Delete(c *gin.Context) { + s := service.LineRecharge{} + req := dto.LineRechargeDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除充值记录表失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "删除成功") +} diff --git a/app/admin/apis/line_symbol.go b/app/admin/apis/line_symbol.go new file mode 100644 index 0000000..6aff8df --- /dev/null +++ b/app/admin/apis/line_symbol.go @@ -0,0 +1,497 @@ +package apis + +import ( + "errors" + "fmt" + "go-admin/common/const/rediskey" + "go-admin/common/global" + "go-admin/common/helper" + "go-admin/config/serverinit" + models2 "go-admin/models" + "go-admin/pkg/utility" + "go-admin/services/binanceservice" + "strings" + + "github.com/bytedance/sonic" + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "github.com/shopspring/decimal" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineSymbol struct { + api.Api +} + +// GetPage 获取交易对管理列表 +// @Summary 获取交易对管理列表 +// @Description 获取交易对管理列表 +// @Tags 交易对管理 +// @Param symbol query string false "交易对" +// @Param baseAsset query string false "基础货币" +// @Param quoteAsset query string false "计价货币" +// @Param type query string false "交易对类型" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineSymbol}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-symbol [get] +// @Security Bearer +func (e LineSymbol) GetPage(c *gin.Context) { + req := dto.LineSymbolGetPageReq{} + s := service.LineSymbol{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineSymbol, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取交易对管理失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取交易对管理 +// @Summary 获取交易对管理 +// @Description 获取交易对管理 +// @Tags 交易对管理 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineSymbol} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-symbol/{id} [get] +// @Security Bearer +func (e LineSymbol) Get(c *gin.Context) { + req := dto.LineSymbolGetReq{} + s := service.LineSymbol{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LineSymbol + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取交易对管理失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(object, "查询成功") +} + +// Insert 创建交易对管理 +// @Summary 创建交易对管理 +// @Description 创建交易对管理 +// @Tags 交易对管理 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineSymbolInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-symbol [post] +// @Security Bearer +func (e LineSymbol) Insert(c *gin.Context) { + req := dto.LineSymbolInsertReq{} + s := service.LineSymbol{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建交易对管理失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改交易对管理 +// @Summary 修改交易对管理 +// @Description 修改交易对管理 +// @Tags 交易对管理 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineSymbolUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-symbol/{id} [put] +// @Security Bearer +func (e LineSymbol) Update(c *gin.Context) { + req := dto.LineSymbolUpdateReq{} + s := service.LineSymbol{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改交易对管理失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "修改成功") +} + +// Delete 删除交易对管理 +// @Summary 删除交易对管理 +// @Description 删除交易对管理 +// @Tags 交易对管理 +// @Param data body dto.LineSymbolDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-symbol [delete] +// @Security Bearer +func (e LineSymbol) Delete(c *gin.Context) { + s := service.LineSymbol{} + req := dto.LineSymbolDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除交易对管理失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "删除成功") +} + +// SyncSpotSymbol 同步交易对 +func (e LineSymbol) SyncSpotSymbol(c *gin.Context) { + s := service.LineSymbol{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + tradeSets, deleteSymbols := serverinit.SpotCurrencyInit() + symbols := make([]models.LineSymbol, 0) + + sysConfig := service.SysConfig{Service: s.Service} + var req = new(dto.SysConfigByKeyReq) + var resp = new(dto.GetSysConfigByKEYForServiceResp) + req.ConfigKey = "quote_volume_24hr" + sysConfig.GetWithKey(req, resp) + symbolBlack := make([]models.LineSymbolBlack, 0) + e.Orm.Model(&models.LineSymbolBlack{}).Where("type = 1").Find(&symbolBlack) + + type Ticker struct { + Symbol string `json:"symbol"` + Price string `json:"price"` + } + tickerSymbol := helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val() + tickerSymbolMaps := make([]Ticker, 0) + sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps) + + for symbol, tradeSet := range tradeSets { + var lineSymbol models.LineSymbol + err := e.Orm.Model(&models.LineSymbol{}).Where("symbol = ? AND type = 1", symbol).Find(&lineSymbol).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + continue + } + key := fmt.Sprintf("%s:%s", global.TICKER_SPOT, symbol) + + //判断是否在黑名单里面 + for _, black := range symbolBlack { + if black.Symbol == symbol { + helper.DefaultRedis.DeleteString(key) + deleteSymbols = append(deleteSymbols, symbol) + continue + } + } + val := helper.DefaultRedis.Get(key).Val() + var spotTicker24h models2.TradeSet + sonic.Unmarshal([]byte(val), &spotTicker24h) + //成交量 + if spotTicker24h.Currency == "USDT" { + if utility.StringToFloat64(spotTicker24h.QuoteVolume) < utility.StringToFloat64(resp.ConfigValue) { + helper.DefaultRedis.DeleteString(key) + deleteSymbols = append(deleteSymbols, symbol) + continue + } + } else { + + var tickerPrice decimal.Decimal + for _, symbolMap := range tickerSymbolMaps { + if symbolMap.Symbol == strings.ToUpper(spotTicker24h.Currency+"USDT") { + tickerPrice, _ = decimal.NewFromString(symbolMap.Price) + } + } + if tickerPrice.GreaterThan(decimal.Zero) { + mul := decimal.NewFromFloat(utility.StringToFloat64(spotTicker24h.QuoteVolume)).Mul(tickerPrice) + if mul.LessThan(decimal.NewFromFloat(utility.StringToFloat64(resp.ConfigValue))) { + helper.DefaultRedis.DeleteString(key) + deleteSymbols = append(deleteSymbols, symbol) + continue + } + } + } + if lineSymbol.Id <= 0 { + lineSymbol.Symbol = symbol + lineSymbol.BaseAsset = tradeSet.Coin + lineSymbol.QuoteAsset = tradeSet.Currency + lineSymbol.Switch = "1" + lineSymbol.Type = "1" + if lineSymbol.Symbol == "" { + continue + } + symbols = append(symbols, lineSymbol) + } + } + if len(deleteSymbols) > 0 { + for _, symbol := range deleteSymbols { + //如果在交易对组里面 + //var symbolGroup []models.LineSymbolGroup + groups := make([]models.LineSymbolGroup, 0) + e.Orm.Model(&models.LineSymbolGroup{}).Find(&groups) + for _, group := range groups { + if group.Id > 0 && strings.Contains(group.Symbol, symbol) { + split := strings.Split(group.Symbol, ",") + value := utility.RemoveByValue(split, symbol) + join := strings.Join(value, ",") + e.Orm.Model(&models.LineSymbolGroup{}).Where("id = ?", group.Id).Update("symbol", join) + } + } + e.Orm.Model(&models.LineSymbol{}).Where("symbol = ? AND type = 1", symbol).Unscoped().Delete(&models.LineSymbol{}) + } + } + + if len(symbols) > 0 { + err = e.Orm.Model(&models.LineSymbol{}).Omit("api_id").Create(&symbols).Error + if err != nil { + e.Error(500, err, err.Error()) + return + } + } + e.OK(nil, "操作成功") +} + +// SyncFutSymbol 更新合约交易对 +func (e LineSymbol) SyncFutSymbol(c *gin.Context) { + s := service.LineSymbol{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + tradeSets := make(map[string]models2.TradeSet, 0) + //获取交易对 + err = binanceservice.GetAndReloadSymbols(&tradeSets) + //获取交易对24小时行情 + deleteSymbols, err := binanceservice.InitSymbolsTicker24h(&tradeSets) + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + sysConfig := service.SysConfig{Service: s.Service} + var req = new(dto.SysConfigByKeyReq) + var resp = new(dto.GetSysConfigByKEYForServiceResp) + req.ConfigKey = "quote_volume_24hr" + sysConfig.GetWithKey(req, resp) + symbols := make([]models.LineSymbol, 0) + symbolBlack := make([]models.LineSymbolBlack, 0) + + type Ticker struct { + Symbol string `json:"symbol"` + Price string `json:"price"` + } + tickerSymbol := helper.DefaultRedis.Get(rediskey.FutSymbolTicker).Val() + tickerSymbolMaps := make([]Ticker, 0) + sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps) + + e.Orm.Model(&models.LineSymbolBlack{}).Where("type = 1").Find(&symbolBlack) + for symbol, tradeSet := range tradeSets { + var lineSymbol models.LineSymbol + err := e.Orm.Model(&models.LineSymbol{}).Where("symbol = ? AND type = 2", symbol).Find(&lineSymbol).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + continue + } + key := fmt.Sprintf("%s:%s", global.TICKER_FUTURES, symbol) + + //判断是否在黑名单里面 + for _, black := range symbolBlack { + if black.Symbol == symbol { + helper.DefaultRedis.DeleteString(key) + deleteSymbols = append(deleteSymbols, symbol) + continue + } + } + val := helper.DefaultRedis.Get(key).Val() + var spotTicker24h models2.TradeSet + sonic.Unmarshal([]byte(val), &spotTicker24h) + //成交量 + if spotTicker24h.Currency == "USDT" { + if utility.StringToFloat64(spotTicker24h.QuoteVolume) < utility.StringToFloat64(resp.ConfigValue) { + helper.DefaultRedis.DeleteString(key) + deleteSymbols = append(deleteSymbols, symbol) + continue + } + } else { + var tickerPrice decimal.Decimal + for _, symbolMap := range tickerSymbolMaps { + if symbolMap.Symbol == strings.ToUpper(spotTicker24h.Currency+"USDT") { + tickerPrice, _ = decimal.NewFromString(symbolMap.Price) + } + } + if tickerPrice.GreaterThan(decimal.Zero) { + mul := decimal.NewFromFloat(utility.StringToFloat64(spotTicker24h.QuoteVolume)).Mul(tickerPrice) + if mul.LessThan(decimal.NewFromFloat(utility.StringToFloat64(resp.ConfigValue))) { + helper.DefaultRedis.DeleteString(key) + deleteSymbols = append(deleteSymbols, symbol) + continue + } + } + } + + if lineSymbol.Id <= 0 { + lineSymbol.Symbol = symbol + lineSymbol.BaseAsset = tradeSet.Coin + lineSymbol.QuoteAsset = tradeSet.Currency + lineSymbol.Switch = "1" + lineSymbol.Type = "2" + if lineSymbol.Symbol == "" { + continue + } + symbols = append(symbols, lineSymbol) + } + } + + if len(deleteSymbols) > 0 { + for _, symbol := range deleteSymbols { + //如果在交易对组里面 + groups := make([]models.LineSymbolGroup, 0) + //var symbolGroup []models.LineSymbolGroup + sql := "SELECT * FROM line_symbol_group WHERE FIND_IN_SET( ? , symbol) AND type = 2 AND deleted_at is NULL;" + e.Orm.Model(&models.LineSymbolGroup{}).Exec(sql, symbol).Find(&groups) + for _, group := range groups { + if group.Id > 0 { + split := strings.Split(group.Symbol, ",") + value := utility.RemoveByValue(split, symbol) + join := strings.Join(value, ",") + e.Orm.Model(&models.LineSymbolGroup{}).Where("id = ?", group.Id).Update("symbol", join) + } + } + e.Orm.Model(&models.LineSymbol{}).Where("symbol = ? AND type = 2", symbol).Unscoped().Delete(&models.LineSymbol{}) + } + } + + if len(symbols) > 0 { + err = e.Orm.Model(&models.LineSymbol{}).Omit("api_id").Create(&symbols).Error + if err != nil { + e.Error(500, err, err.Error()) + return + } + } + e.OK(nil, "操作成功") +} + +// GetSymbol 获取现货和合约都有的交易对 +func (e LineSymbol) GetSymbol(c *gin.Context) { + s := service.LineSymbol{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + data, err := s.GetSymbol() + if err != nil { + e.Error(500, err, fmt.Sprintf("获取失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(data, "操作成功") +} + +// GetSameSymbol 获取现货和合约都有的交易对 +func (e LineSymbol) GetSameSymbol(c *gin.Context) { + req := dto.LineSymbolGetPageReq{} + s := service.LineSymbol{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineSymbol, 0) + var count int64 + + err = s.GetSamePage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取交易对管理失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} diff --git a/app/admin/apis/line_symbol_black.go b/app/admin/apis/line_symbol_black.go new file mode 100644 index 0000000..6c1c95a --- /dev/null +++ b/app/admin/apis/line_symbol_black.go @@ -0,0 +1,193 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineSymbolBlack struct { + api.Api +} + +// GetPage 获取交易对黑名单列表 +// @Summary 获取交易对黑名单列表 +// @Description 获取交易对黑名单列表 +// @Tags 交易对黑名单 +// @Param symbol query string false "交易对" +// @Param type query string false "类型:1=现货,2=合约" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineSymbolBlack}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-symbol-black [get] +// @Security Bearer +func (e LineSymbolBlack) GetPage(c *gin.Context) { + req := dto.LineSymbolBlackGetPageReq{} + s := service.LineSymbolBlack{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineSymbolBlack, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取交易对黑名单失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取交易对黑名单 +// @Summary 获取交易对黑名单 +// @Description 获取交易对黑名单 +// @Tags 交易对黑名单 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineSymbolBlack} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-symbol-black/{id} [get] +// @Security Bearer +func (e LineSymbolBlack) Get(c *gin.Context) { + req := dto.LineSymbolBlackGetReq{} + s := service.LineSymbolBlack{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LineSymbolBlack + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取交易对黑名单失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK( object, "查询成功") +} + +// Insert 创建交易对黑名单 +// @Summary 创建交易对黑名单 +// @Description 创建交易对黑名单 +// @Tags 交易对黑名单 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineSymbolBlackInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-symbol-black [post] +// @Security Bearer +func (e LineSymbolBlack) Insert(c *gin.Context) { + req := dto.LineSymbolBlackInsertReq{} + s := service.LineSymbolBlack{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建交易对黑名单失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改交易对黑名单 +// @Summary 修改交易对黑名单 +// @Description 修改交易对黑名单 +// @Tags 交易对黑名单 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineSymbolBlackUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-symbol-black/{id} [put] +// @Security Bearer +func (e LineSymbolBlack) Update(c *gin.Context) { + req := dto.LineSymbolBlackUpdateReq{} + s := service.LineSymbolBlack{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改交易对黑名单失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "修改成功") +} + +// Delete 删除交易对黑名单 +// @Summary 删除交易对黑名单 +// @Description 删除交易对黑名单 +// @Tags 交易对黑名单 +// @Param data body dto.LineSymbolBlackDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-symbol-black [delete] +// @Security Bearer +func (e LineSymbolBlack) Delete(c *gin.Context) { + s := service.LineSymbolBlack{} + req := dto.LineSymbolBlackDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除交易对黑名单失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "删除成功") +} diff --git a/app/admin/apis/line_symbol_group.go b/app/admin/apis/line_symbol_group.go new file mode 100644 index 0000000..2dc803d --- /dev/null +++ b/app/admin/apis/line_symbol_group.go @@ -0,0 +1,192 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineSymbolGroup struct { + api.Api +} + +// GetPage 获取交易对组列表列表 +// @Summary 获取交易对组列表列表 +// @Description 获取交易对组列表列表 +// @Tags 交易对组列表 +// @Param type query string false "类型:1=现货,2=合约" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineSymbolGroup}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-symbol-group [get] +// @Security Bearer +func (e LineSymbolGroup) GetPage(c *gin.Context) { + req := dto.LineSymbolGroupGetPageReq{} + s := service.LineSymbolGroup{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineSymbolGroup, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取交易对组列表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取交易对组列表 +// @Summary 获取交易对组列表 +// @Description 获取交易对组列表 +// @Tags 交易对组列表 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineSymbolGroup} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-symbol-group/{id} [get] +// @Security Bearer +func (e LineSymbolGroup) Get(c *gin.Context) { + req := dto.LineSymbolGroupGetReq{} + s := service.LineSymbolGroup{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LineSymbolGroup + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取交易对组列表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK( object, "查询成功") +} + +// Insert 创建交易对组列表 +// @Summary 创建交易对组列表 +// @Description 创建交易对组列表 +// @Tags 交易对组列表 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineSymbolGroupInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-symbol-group [post] +// @Security Bearer +func (e LineSymbolGroup) Insert(c *gin.Context) { + req := dto.LineSymbolGroupInsertReq{} + s := service.LineSymbolGroup{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建交易对组列表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改交易对组列表 +// @Summary 修改交易对组列表 +// @Description 修改交易对组列表 +// @Tags 交易对组列表 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineSymbolGroupUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-symbol-group/{id} [put] +// @Security Bearer +func (e LineSymbolGroup) Update(c *gin.Context) { + req := dto.LineSymbolGroupUpdateReq{} + s := service.LineSymbolGroup{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改交易对组列表失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "修改成功") +} + +// Delete 删除交易对组列表 +// @Summary 删除交易对组列表 +// @Description 删除交易对组列表 +// @Tags 交易对组列表 +// @Param data body dto.LineSymbolGroupDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-symbol-group [delete] +// @Security Bearer +func (e LineSymbolGroup) Delete(c *gin.Context) { + s := service.LineSymbolGroup{} + req := dto.LineSymbolGroupDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除交易对组列表失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "删除成功") +} diff --git a/app/admin/apis/line_system_setting.go b/app/admin/apis/line_system_setting.go new file mode 100644 index 0000000..bbe1ee3 --- /dev/null +++ b/app/admin/apis/line_system_setting.go @@ -0,0 +1,191 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineSystemSetting struct { + api.Api +} + +// GetPage 获取挂单配置列表 +// @Summary 获取挂单配置列表 +// @Description 获取挂单配置列表 +// @Tags 挂单配置 +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineSystemSetting}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-system-setting [get] +// @Security Bearer +func (e LineSystemSetting) GetPage(c *gin.Context) { + req := dto.LineSystemSettingGetPageReq{} + s := service.LineSystemSetting{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineSystemSetting, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取挂单配置失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取挂单配置 +// @Summary 获取挂单配置 +// @Description 获取挂单配置 +// @Tags 挂单配置 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineSystemSetting} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-system-setting/{id} [get] +// @Security Bearer +func (e LineSystemSetting) Get(c *gin.Context) { + req := dto.LineSystemSettingGetReq{} + s := service.LineSystemSetting{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LineSystemSetting + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取挂单配置失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(object, "查询成功") +} + +// Insert 创建挂单配置 +// @Summary 创建挂单配置 +// @Description 创建挂单配置 +// @Tags 挂单配置 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineSystemSettingInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-system-setting [post] +// @Security Bearer +func (e LineSystemSetting) Insert(c *gin.Context) { + req := dto.LineSystemSettingInsertReq{} + s := service.LineSystemSetting{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建挂单配置失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改挂单配置 +// @Summary 修改挂单配置 +// @Description 修改挂单配置 +// @Tags 挂单配置 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineSystemSettingUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-system-setting/{id} [put] +// @Security Bearer +func (e LineSystemSetting) Update(c *gin.Context) { + req := dto.LineSystemSettingUpdateReq{} + s := service.LineSystemSetting{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改挂单配置失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "修改成功") +} + +// Delete 删除挂单配置 +// @Summary 删除挂单配置 +// @Description 删除挂单配置 +// @Tags 挂单配置 +// @Param data body dto.LineSystemSettingDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-system-setting [delete] +// @Security Bearer +func (e LineSystemSetting) Delete(c *gin.Context) { + s := service.LineSystemSetting{} + req := dto.LineSystemSettingDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除挂单配置失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "删除成功") +} diff --git a/app/admin/apis/line_uduncoin.go b/app/admin/apis/line_uduncoin.go new file mode 100644 index 0000000..91da4a2 --- /dev/null +++ b/app/admin/apis/line_uduncoin.go @@ -0,0 +1,191 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineUduncoin struct { + api.Api +} + +// GetPage 获取【u盾支持的币种信息】列表 +// @Summary 获取【u盾支持的币种信息】列表 +// @Description 获取【u盾支持的币种信息】列表 +// @Tags 【u盾支持的币种信息】 +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineUduncoin}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-uduncoin [get] +// @Security Bearer +func (e LineUduncoin) GetPage(c *gin.Context) { + req := dto.LineUduncoinGetPageReq{} + s := service.LineUduncoin{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineUduncoin, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取【u盾支持的币种信息】失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取【u盾支持的币种信息】 +// @Summary 获取【u盾支持的币种信息】 +// @Description 获取【u盾支持的币种信息】 +// @Tags 【u盾支持的币种信息】 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineUduncoin} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-uduncoin/{id} [get] +// @Security Bearer +func (e LineUduncoin) Get(c *gin.Context) { + req := dto.LineUduncoinGetReq{} + s := service.LineUduncoin{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LineUduncoin + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取【u盾支持的币种信息】失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK( object, "查询成功") +} + +// Insert 创建【u盾支持的币种信息】 +// @Summary 创建【u盾支持的币种信息】 +// @Description 创建【u盾支持的币种信息】 +// @Tags 【u盾支持的币种信息】 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineUduncoinInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-uduncoin [post] +// @Security Bearer +func (e LineUduncoin) Insert(c *gin.Context) { + req := dto.LineUduncoinInsertReq{} + s := service.LineUduncoin{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建【u盾支持的币种信息】失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改【u盾支持的币种信息】 +// @Summary 修改【u盾支持的币种信息】 +// @Description 修改【u盾支持的币种信息】 +// @Tags 【u盾支持的币种信息】 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineUduncoinUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-uduncoin/{id} [put] +// @Security Bearer +func (e LineUduncoin) Update(c *gin.Context) { + req := dto.LineUduncoinUpdateReq{} + s := service.LineUduncoin{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改【u盾支持的币种信息】失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "修改成功") +} + +// Delete 删除【u盾支持的币种信息】 +// @Summary 删除【u盾支持的币种信息】 +// @Description 删除【u盾支持的币种信息】 +// @Tags 【u盾支持的币种信息】 +// @Param data body dto.LineUduncoinDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-uduncoin [delete] +// @Security Bearer +func (e LineUduncoin) Delete(c *gin.Context) { + s := service.LineUduncoin{} + req := dto.LineUduncoinDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除【u盾支持的币种信息】失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "删除成功") +} diff --git a/app/admin/apis/line_user.go b/app/admin/apis/line_user.go new file mode 100644 index 0000000..bdeef5d --- /dev/null +++ b/app/admin/apis/line_user.go @@ -0,0 +1,191 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineUser struct { + api.Api +} + +// GetPage 获取会员表列表 +// @Summary 获取会员表列表 +// @Description 获取会员表列表 +// @Tags 会员表 +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineUser}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-user [get] +// @Security Bearer +func (e LineUser) GetPage(c *gin.Context) { + req := dto.LineUserGetPageReq{} + s := service.LineUser{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineUser, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取会员表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取会员表 +// @Summary 获取会员表 +// @Description 获取会员表 +// @Tags 会员表 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineUser} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-user/{id} [get] +// @Security Bearer +func (e LineUser) Get(c *gin.Context) { + req := dto.LineUserGetReq{} + s := service.LineUser{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LineUser + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取会员表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK( object, "查询成功") +} + +// Insert 创建会员表 +// @Summary 创建会员表 +// @Description 创建会员表 +// @Tags 会员表 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineUserInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-user [post] +// @Security Bearer +func (e LineUser) Insert(c *gin.Context) { + req := dto.LineUserInsertReq{} + s := service.LineUser{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建会员表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改会员表 +// @Summary 修改会员表 +// @Description 修改会员表 +// @Tags 会员表 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineUserUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-user/{id} [put] +// @Security Bearer +func (e LineUser) Update(c *gin.Context) { + req := dto.LineUserUpdateReq{} + s := service.LineUser{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改会员表失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "修改成功") +} + +// Delete 删除会员表 +// @Summary 删除会员表 +// @Description 删除会员表 +// @Tags 会员表 +// @Param data body dto.LineUserDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-user [delete] +// @Security Bearer +func (e LineUser) Delete(c *gin.Context) { + s := service.LineUser{} + req := dto.LineUserDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除会员表失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "删除成功") +} diff --git a/app/admin/apis/line_user_funding_trend.go b/app/admin/apis/line_user_funding_trend.go new file mode 100644 index 0000000..d034be4 --- /dev/null +++ b/app/admin/apis/line_user_funding_trend.go @@ -0,0 +1,191 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineUserFundingTrend struct { + api.Api +} + +// GetPage 获取用户资金走势列表 +// @Summary 获取用户资金走势列表 +// @Description 获取用户资金走势列表 +// @Tags 用户资金走势 +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineUserFundingTrend}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-user-funding-trend [get] +// @Security Bearer +func (e LineUserFundingTrend) GetPage(c *gin.Context) { + req := dto.LineUserFundingTrendGetPageReq{} + s := service.LineUserFundingTrend{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineUserFundingTrend, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取用户资金走势失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取用户资金走势 +// @Summary 获取用户资金走势 +// @Description 获取用户资金走势 +// @Tags 用户资金走势 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineUserFundingTrend} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-user-funding-trend/{id} [get] +// @Security Bearer +func (e LineUserFundingTrend) Get(c *gin.Context) { + req := dto.LineUserFundingTrendGetReq{} + s := service.LineUserFundingTrend{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LineUserFundingTrend + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取用户资金走势失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK( object, "查询成功") +} + +// Insert 创建用户资金走势 +// @Summary 创建用户资金走势 +// @Description 创建用户资金走势 +// @Tags 用户资金走势 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineUserFundingTrendInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-user-funding-trend [post] +// @Security Bearer +func (e LineUserFundingTrend) Insert(c *gin.Context) { + req := dto.LineUserFundingTrendInsertReq{} + s := service.LineUserFundingTrend{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建用户资金走势失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改用户资金走势 +// @Summary 修改用户资金走势 +// @Description 修改用户资金走势 +// @Tags 用户资金走势 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineUserFundingTrendUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-user-funding-trend/{id} [put] +// @Security Bearer +func (e LineUserFundingTrend) Update(c *gin.Context) { + req := dto.LineUserFundingTrendUpdateReq{} + s := service.LineUserFundingTrend{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改用户资金走势失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "修改成功") +} + +// Delete 删除用户资金走势 +// @Summary 删除用户资金走势 +// @Description 删除用户资金走势 +// @Tags 用户资金走势 +// @Param data body dto.LineUserFundingTrendDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-user-funding-trend [delete] +// @Security Bearer +func (e LineUserFundingTrend) Delete(c *gin.Context) { + s := service.LineUserFundingTrend{} + req := dto.LineUserFundingTrendDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除用户资金走势失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "删除成功") +} diff --git a/app/admin/apis/line_user_profit_logs.go b/app/admin/apis/line_user_profit_logs.go new file mode 100644 index 0000000..de17c12 --- /dev/null +++ b/app/admin/apis/line_user_profit_logs.go @@ -0,0 +1,196 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineUserProfitLogs struct { + api.Api +} + +// GetPage 获取会员盈利记录表列表 +// @Summary 获取会员盈利记录表列表 +// @Description 获取会员盈利记录表列表 +// @Tags 会员盈利记录表 +// @Param userId query int64 false "line_user 表的id" +// @Param apiId query int64 false "line_apiuser 表的id" +// @Param preOrderId query int64 false "line_pre_order 主订单id" +// @Param num query string false "成交数量" +// @Param symbol query string false "交易对" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineUserProfitLogs}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-user-profit-logs [get] +// @Security Bearer +func (e LineUserProfitLogs) GetPage(c *gin.Context) { + req := dto.LineUserProfitLogsGetPageReq{} + s := service.LineUserProfitLogs{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineUserProfitLogs, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取会员盈利记录表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取会员盈利记录表 +// @Summary 获取会员盈利记录表 +// @Description 获取会员盈利记录表 +// @Tags 会员盈利记录表 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineUserProfitLogs} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-user-profit-logs/{id} [get] +// @Security Bearer +func (e LineUserProfitLogs) Get(c *gin.Context) { + req := dto.LineUserProfitLogsGetReq{} + s := service.LineUserProfitLogs{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LineUserProfitLogs + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取会员盈利记录表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK( object, "查询成功") +} + +// Insert 创建会员盈利记录表 +// @Summary 创建会员盈利记录表 +// @Description 创建会员盈利记录表 +// @Tags 会员盈利记录表 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineUserProfitLogsInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-user-profit-logs [post] +// @Security Bearer +func (e LineUserProfitLogs) Insert(c *gin.Context) { + req := dto.LineUserProfitLogsInsertReq{} + s := service.LineUserProfitLogs{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建会员盈利记录表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改会员盈利记录表 +// @Summary 修改会员盈利记录表 +// @Description 修改会员盈利记录表 +// @Tags 会员盈利记录表 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineUserProfitLogsUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-user-profit-logs/{id} [put] +// @Security Bearer +func (e LineUserProfitLogs) Update(c *gin.Context) { + req := dto.LineUserProfitLogsUpdateReq{} + s := service.LineUserProfitLogs{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改会员盈利记录表失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "修改成功") +} + +// Delete 删除会员盈利记录表 +// @Summary 删除会员盈利记录表 +// @Description 删除会员盈利记录表 +// @Tags 会员盈利记录表 +// @Param data body dto.LineUserProfitLogsDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-user-profit-logs [delete] +// @Security Bearer +func (e LineUserProfitLogs) Delete(c *gin.Context) { + s := service.LineUserProfitLogs{} + req := dto.LineUserProfitLogsDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除会员盈利记录表失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "删除成功") +} diff --git a/app/admin/apis/line_wallet.go b/app/admin/apis/line_wallet.go new file mode 100644 index 0000000..f3e5531 --- /dev/null +++ b/app/admin/apis/line_wallet.go @@ -0,0 +1,191 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type LineWallet struct { + api.Api +} + +// GetPage 获取用户钱包地址列表 +// @Summary 获取用户钱包地址列表 +// @Description 获取用户钱包地址列表 +// @Tags 用户钱包地址 +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.LineWallet}} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-wallet [get] +// @Security Bearer +func (e LineWallet) GetPage(c *gin.Context) { + req := dto.LineWalletGetPageReq{} + s := service.LineWallet{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.LineWallet, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取用户钱包地址失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取用户钱包地址 +// @Summary 获取用户钱包地址 +// @Description 获取用户钱包地址 +// @Tags 用户钱包地址 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.LineWallet} "{"code": 200, "data": [...]}" +// @Router /api/v1/line-wallet/{id} [get] +// @Security Bearer +func (e LineWallet) Get(c *gin.Context) { + req := dto.LineWalletGetReq{} + s := service.LineWallet{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.LineWallet + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取用户钱包地址失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK( object, "查询成功") +} + +// Insert 创建用户钱包地址 +// @Summary 创建用户钱包地址 +// @Description 创建用户钱包地址 +// @Tags 用户钱包地址 +// @Accept application/json +// @Product application/json +// @Param data body dto.LineWalletInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/line-wallet [post] +// @Security Bearer +func (e LineWallet) Insert(c *gin.Context) { + req := dto.LineWalletInsertReq{} + s := service.LineWallet{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建用户钱包地址失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改用户钱包地址 +// @Summary 修改用户钱包地址 +// @Description 修改用户钱包地址 +// @Tags 用户钱包地址 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.LineWalletUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/line-wallet/{id} [put] +// @Security Bearer +func (e LineWallet) Update(c *gin.Context) { + req := dto.LineWalletUpdateReq{} + s := service.LineWallet{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改用户钱包地址失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "修改成功") +} + +// Delete 删除用户钱包地址 +// @Summary 删除用户钱包地址 +// @Description 删除用户钱包地址 +// @Tags 用户钱包地址 +// @Param data body dto.LineWalletDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/line-wallet [delete] +// @Security Bearer +func (e LineWallet) Delete(c *gin.Context) { + s := service.LineWallet{} + req := dto.LineWalletDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除用户钱包地址失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "删除成功") +} diff --git a/app/admin/apis/sys_api.go b/app/admin/apis/sys_api.go new file mode 100644 index 0000000..c2d3474 --- /dev/null +++ b/app/admin/apis/sys_api.go @@ -0,0 +1,148 @@ +package apis + +import ( + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type SysApi struct { + api.Api +} + +// GetPage 获取接口管理列表 +// @Summary 获取接口管理列表 +// @Description 获取接口管理列表 +// @Tags 接口管理 +// @Param name query string false "名称" +// @Param title query string false "标题" +// @Param path query string false "地址" +// @Param action query string false "类型" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.SysApi}} "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-api [get] +// @Security Bearer +func (e SysApi) GetPage(c *gin.Context) { + s := service.SysApi{} + req := dto.SysApiGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + //数据权限检查 + p := actions.GetPermissionFromContext(c) + list := make([]models.SysApi, 0) + var count int64 + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取接口管理 +// @Summary 获取接口管理 +// @Description 获取接口管理 +// @Tags 接口管理 +// @Param id path string false "id" +// @Success 200 {object} response.Response{data=models.SysApi} "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-api/{id} [get] +// @Security Bearer +func (e SysApi) Get(c *gin.Context) { + req := dto.SysApiGetReq{} + s := service.SysApi{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + p := actions.GetPermissionFromContext(c) + var object models.SysApi + err = s.Get(&req, p, &object).Error + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.OK(object, "查询成功") +} + +// Update 修改接口管理 +// @Summary 修改接口管理 +// @Description 修改接口管理 +// @Tags 接口管理 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysApiUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/sys-api/{id} [put] +// @Security Bearer +func (e SysApi) Update(c *gin.Context) { + req := dto.SysApiUpdateReq{} + s := service.SysApi{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, "更新失败") + return + } + e.OK(req.GetId(), "更新成功") +} + +// DeleteSysApi 删除接口管理 +// @Summary 删除接口管理 +// @Description 删除接口管理 +// @Tags 接口管理 +// @Param data body dto.SysApiDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/sys-api [delete] +// @Security Bearer +func (e SysApi) DeleteSysApi(c *gin.Context) { + req := dto.SysApiDeleteReq{} + s := service.SysApi{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + return + } + p := actions.GetPermissionFromContext(c) + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, "删除失败") + return + } + e.OK(req.GetId(), "删除成功") +} \ No newline at end of file diff --git a/app/admin/apis/sys_config.go b/app/admin/apis/sys_config.go new file mode 100644 index 0000000..0a95c8b --- /dev/null +++ b/app/admin/apis/sys_config.go @@ -0,0 +1,313 @@ +package apis + +import ( + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" +) + +type SysConfig struct { + api.Api +} + +// GetPage 获取配置管理列表 +// @Summary 获取配置管理列表 +// @Description 获取配置管理列表 +// @Tags 配置管理 +// @Param configName query string false "名称" +// @Param configKey query string false "key" +// @Param configType query string false "类型" +// @Param isFrontend query int false "是否前端" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.SysApi}} "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-config [get] +// @Security Bearer +func (e SysConfig) GetPage(c *gin.Context) { + s := service.SysConfig{} + req := dto.SysConfigGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + return + } + + list := make([]models.SysConfig, 0) + var count int64 + err = s.GetPage(&req, &list, &count) + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取配置管理 +// @Summary 获取配置管理 +// @Description 获取配置管理 +// @Tags 配置管理 +// @Param id path string false "id" +// @Success 200 {object} response.Response{data=models.SysConfig} "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-config/{id} [get] +// @Security Bearer +func (e SysConfig) Get(c *gin.Context) { + req := dto.SysConfigGetReq{} + s := service.SysConfig{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.SysConfig + + err = s.Get(&req, &object) + if err != nil { + e.Error(500, err, err.Error()) + return + } + + e.OK(object, "查询成功") +} + +// Insert 创建配置管理 +// @Summary 创建配置管理 +// @Description 创建配置管理 +// @Tags 配置管理 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysConfigControl true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "创建成功"}" +// @Router /api/v1/sys-config [post] +// @Security Bearer +func (e SysConfig) Insert(c *gin.Context) { + s := service.SysConfig{} + req := dto.SysConfigControl{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, "创建失败") + return + } + e.OK(req.GetId(), "创建成功") +} + +// Update 修改配置管理 +// @Summary 修改配置管理 +// @Description 修改配置管理 +// @Tags 配置管理 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysConfigControl true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/sys-config/{id} [put] +// @Security Bearer +func (e SysConfig) Update(c *gin.Context) { + s := service.SysConfig{} + req := dto.SysConfigControl{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + err = s.Update(&req) + if err != nil { + e.Error(500, err, "更新失败") + return + } + e.OK(req.GetId(), "更新成功") +} + +// Delete 删除配置管理 +// @Summary 删除配置管理 +// @Description 删除配置管理 +// @Tags 配置管理 +// @Param ids body []int false "ids" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/sys-config [delete] +// @Security Bearer +func (e SysConfig) Delete(c *gin.Context) { + s := service.SysConfig{} + req := dto.SysConfigDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + + err = s.Remove(&req) + if err != nil { + e.Error(500, err, "删除失败") + return + } + e.OK(req.GetId(), "删除成功") +} + +// Get2SysApp 获取系统配置信息 +// @Summary 获取系统前台配置信息,主要注意这里不在验证权限 +// @Description 获取系统配置信息,主要注意这里不在验证权限 +// @Tags 配置管理 +// @Success 200 {object} response.Response{data=map[string]string} "{"code": 200, "data": [...]}" +// @Router /api/v1/app-config [get] +func (e SysConfig) Get2SysApp(c *gin.Context) { + req := dto.SysConfigGetToSysAppReq{} + s := service.SysConfig{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + return + } + // 控制只读前台的数据 + req.IsFrontend = "1" + list := make([]models.SysConfig, 0) + err = s.GetWithKeyList(&req, &list) + if err != nil { + e.Error(500, err, "查询失败") + return + } + mp := make(map[string]string) + for i := 0; i < len(list); i++ { + key := list[i].ConfigKey + if key != "" { + mp[key] = list[i].ConfigValue + } + } + e.OK(mp, "查询成功") +} + +// Get2Set 获取配置 +// @Summary 获取配置 +// @Description 界面操作设置配置值的获取 +// @Tags 配置管理 +// @Accept application/json +// @Product application/json +// @Success 200 {object} response.Response{data=map[string]interface{}} "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/set-config [get] +// @Security Bearer +func (e SysConfig) Get2Set(c *gin.Context) { + s := service.SysConfig{} + req := make([]dto.GetSetSysConfigReq, 0) + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + err = s.GetForSet(&req) + if err != nil { + e.Error(500, err, "查询失败") + return + } + m := make(map[string]interface{}, 0) + for _, v := range req { + m[v.ConfigKey] = v.ConfigValue + } + e.OK(m, "查询成功") +} + +// Update2Set 设置配置 +// @Summary 设置配置 +// @Description 界面操作设置配置值 +// @Tags 配置管理 +// @Accept application/json +// @Product application/json +// @Param data body []dto.GetSetSysConfigReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/set-config [put] +// @Security Bearer +func (e SysConfig) Update2Set(c *gin.Context) { + s := service.SysConfig{} + req := make([]dto.GetSetSysConfigReq, 0) + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + err = s.UpdateForSet(&req) + if err != nil { + e.Error(500, err, err.Error()) + return + } + + e.OK("", "更新成功") +} + +// GetSysConfigByKEYForService 根据Key获取SysConfig的Service +// @Summary 根据Key获取SysConfig的Service +// @Description 根据Key获取SysConfig的Service +// @Tags 配置管理 +// @Param configKey path string false "configKey" +// @Success 200 {object} response.Response{data=dto.SysConfigByKeyReq} "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-config/{id} [get] +// @Security Bearer +func (e SysConfig) GetSysConfigByKEYForService(c *gin.Context) { + var s = new(service.SysConfig) + var req = new(dto.SysConfigByKeyReq) + var resp = new(dto.GetSysConfigByKEYForServiceResp) + err := e.MakeContext(c). + MakeOrm(). + Bind(req, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + err = s.GetWithKey(req, resp) + if err != nil { + e.Error(500, err, err.Error()) + return + } + e.OK(resp, s.Msg) +} diff --git a/app/admin/apis/sys_dept.go b/app/admin/apis/sys_dept.go new file mode 100644 index 0000000..8f1663f --- /dev/null +++ b/app/admin/apis/sys_dept.go @@ -0,0 +1,238 @@ +package apis + +import ( + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "go-admin/app/admin/models" + + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" +) + +type SysDept struct { + api.Api +} + +// GetPage +// @Summary 分页部门列表数据 +// @Description 分页列表 +// @Tags 部门 +// @Param deptName query string false "deptName" +// @Param deptId query string false "deptId" +// @Param position query string false "position" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dept [get] +// @Security Bearer +func (e SysDept) GetPage(c *gin.Context) { + s := service.SysDept{} + req := dto.SysDeptGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + list := make([]models.SysDept, 0) + list, err = s.SetDeptPage(&req) + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.OK(list, "查询成功") +} + +// Get +// @Summary 获取部门数据 +// @Description 获取JSON +// @Tags 部门 +// @Param deptId path string false "deptId" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dept/{deptId} [get] +// @Security Bearer +func (e SysDept) Get(c *gin.Context) { + s := service.SysDept{} + req := dto.SysDeptGetReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.SysDept + + err = s.Get(&req, &object) + if err != nil { + e.Error(500, err, "查询失败") + return + } + + e.OK(object, "查询成功") +} + +// Insert 添加部门 +// @Summary 添加部门 +// @Description 获取JSON +// @Tags 部门 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysDeptInsertReq true "data" +// @Success 200 {string} string "{"code": 200, "message": "添加成功"}" +// @Success 200 {string} string "{"code": -1, "message": "添加失败"}" +// @Router /api/v1/dept [post] +// @Security Bearer +func (e SysDept) Insert(c *gin.Context) { + s := service.SysDept{} + req := dto.SysDeptInsertReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + err = s.Insert(&req) + if err != nil { + e.Error(500, err, "创建失败") + return + } + e.OK(req.GetId(), "创建成功") +} + +// Update +// @Summary 修改部门 +// @Description 获取JSON +// @Tags 部门 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.SysDeptUpdateReq true "body" +// @Success 200 {string} string "{"code": 200, "message": "添加成功"}" +// @Success 200 {string} string "{"code": -1, "message": "添加失败"}" +// @Router /api/v1/dept/{deptId} [put] +// @Security Bearer +func (e SysDept) Update(c *gin.Context) { + s := service.SysDept{} + req := dto.SysDeptUpdateReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + err = s.Update(&req) + if err != nil { + e.Error(500, err, err.Error()) + return + } + e.OK(req.GetId(), "更新成功") +} + +// Delete +// @Summary 删除部门 +// @Description 删除数据 +// @Tags 部门 +// @Param data body dto.SysDeptDeleteReq true "body" +// @Success 200 {string} string "{"code": 200, "message": "删除成功"}" +// @Success 200 {string} string "{"code": -1, "message": "删除失败"}" +// @Router /api/v1/dept [delete] +// @Security Bearer +func (e SysDept) Delete(c *gin.Context) { + s := service.SysDept{} + req := dto.SysDeptDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + err = s.Remove(&req) + if err != nil { + e.Error(500, err, "删除失败") + return + } + e.OK(req.GetId(), "删除成功") +} + +// Get2Tree 用户管理 左侧部门树 +func (e SysDept) Get2Tree(c *gin.Context) { + s := service.SysDept{} + req := dto.SysDeptGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + list := make([]dto.DeptLabel, 0) + list, err = s.SetDeptTree(&req) + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.OK(list, "") +} + +// GetDeptTreeRoleSelect TODO: 此接口需要调整不应该将list和选中放在一起 +func (e SysDept) GetDeptTreeRoleSelect(c *gin.Context) { + s := service.SysDept{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + id, err := pkg.StringToInt(c.Param("roleId")) + result, err := s.SetDeptLabel() + if err != nil { + e.Error(500, err, err.Error()) + return + } + menuIds := make([]int, 0) + if id != 0 { + menuIds, err = s.GetWithRoleId(id) + if err != nil { + e.Error(500, err, err.Error()) + return + } + } + e.OK(gin.H{ + "depts": result, + "checkedKeys": menuIds, + }, "") +} diff --git a/app/admin/apis/sys_dict_data.go b/app/admin/apis/sys_dict_data.go new file mode 100644 index 0000000..049fe99 --- /dev/null +++ b/app/admin/apis/sys_dict_data.go @@ -0,0 +1,220 @@ +package apis + +import ( + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "go-admin/app/admin/models" + + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" +) + +type SysDictData struct { + api.Api +} + +// GetPage +// @Summary 字典数据列表 +// @Description 获取JSON +// @Tags 字典数据 +// @Param status query string false "status" +// @Param dictCode query string false "dictCode" +// @Param dictType query string false "dictType" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dict/data [get] +// @Security Bearer +func (e SysDictData) GetPage(c *gin.Context) { + s := service.SysDictData{} + req := dto.SysDictDataGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + list := make([]models.SysDictData, 0) + var count int64 + err = s.GetPage(&req, &list, &count) + if err != nil { + e.Error(500, err, "查询失败") + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get +// @Summary 通过编码获取字典数据 +// @Description 获取JSON +// @Tags 字典数据 +// @Param dictCode path int true "字典编码" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dict/data/{dictCode} [get] +// @Security Bearer +func (e SysDictData) Get(c *gin.Context) { + s := service.SysDictData{} + req := dto.SysDictDataGetReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + var object models.SysDictData + + err = s.Get(&req, &object) + if err != nil { + e.Logger.Warnf("Get error: %s", err.Error()) + e.Error(500, err, "查询失败") + return + } + + e.OK(object, "查询成功") +} + +// Insert +// @Summary 添加字典数据 +// @Description 获取JSON +// @Tags 字典数据 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysDictDataInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/dict/data [post] +// @Security Bearer +func (e SysDictData) Insert(c *gin.Context) { + s := service.SysDictData{} + req := dto.SysDictDataInsertReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetCreateBy(user.GetUserId(c)) + err = s.Insert(&req) + if err != nil { + e.Error(500, err, "创建失败") + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update +// @Summary 修改字典数据 +// @Description 获取JSON +// @Tags 字典数据 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysDictDataUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/dict/data/{dictCode} [put] +// @Security Bearer +func (e SysDictData) Update(c *gin.Context) { + s := service.SysDictData{} + req := dto.SysDictDataUpdateReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + err = s.Update(&req) + if err != nil { + e.Error(500, err, "更新失败") + return + } + e.OK(req.GetId(), "更新成功") +} + +// Delete +// @Summary 删除字典数据 +// @Description 删除数据 +// @Tags 字典数据 +// @Param dictCode body dto.SysDictDataDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/dict/data [delete] +// @Security Bearer +func (e SysDictData) Delete(c *gin.Context) { + s := service.SysDictData{} + req := dto.SysDictDataDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + err = s.Remove(&req) + if err != nil { + e.Error(500, err, "删除失败") + return + } + e.OK(req.GetId(), "删除成功") +} + +// GetAll 数据字典根据key获取 业务页面使用 +// @Summary 数据字典根据key获取 +// @Description 数据字典根据key获取 +// @Tags 字典数据 +// @Param dictType query int true "dictType" +// @Success 200 {object} response.Response{data=[]dto.SysDictDataGetAllResp} "{"code": 200, "data": [...]}" +// @Router /api/v1/dict-data/option-select [get] +// @Security Bearer +func (e SysDictData) GetAll(c *gin.Context) { + s := service.SysDictData{} + req := dto.SysDictDataGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + list := make([]models.SysDictData, 0) + err = s.GetAll(&req, &list) + if err != nil { + e.Error(500, err, "查询失败") + return + } + l := make([]dto.SysDictDataGetAllResp, 0) + for _, i := range list { + d := dto.SysDictDataGetAllResp{} + e.Translate(i, &d) + l = append(l, d) + } + + e.OK(l,"查询成功") +} diff --git a/app/admin/apis/sys_dict_type.go b/app/admin/apis/sys_dict_type.go new file mode 100644 index 0000000..b93035e --- /dev/null +++ b/app/admin/apis/sys_dict_type.go @@ -0,0 +1,210 @@ +package apis + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "go-admin/app/admin/models" + + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" +) + +type SysDictType struct { + api.Api +} + +// GetPage 字典类型列表数据 +// @Summary 字典类型列表数据 +// @Description 获取JSON +// @Tags 字典类型 +// @Param dictName query string false "dictName" +// @Param dictId query string false "dictId" +// @Param dictType query string false "dictType" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dict/type [get] +// @Security Bearer +func (e SysDictType) GetPage(c *gin.Context) { + s := service.SysDictType{} + req :=dto.SysDictTypeGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + list := make([]models.SysDictType, 0) + var count int64 + err = s.GetPage(&req, &list, &count) + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 字典类型通过字典id获取 +// @Summary 字典类型通过字典id获取 +// @Description 获取JSON +// @Tags 字典类型 +// @Param dictId path int true "字典类型编码" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dict/type/{dictId} [get] +// @Security Bearer +func (e SysDictType) Get(c *gin.Context) { + s := service.SysDictType{} + req :=dto.SysDictTypeGetReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.SysDictType + err = s.Get(&req, &object) + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.OK(object, "查询成功") +} + +//Insert 字典类型创建 +// @Summary 添加字典类型 +// @Description 获取JSON +// @Tags 字典类型 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysDictTypeInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dict/type [post] +// @Security Bearer +func (e SysDictType) Insert(c *gin.Context) { + s := service.SysDictType{} + req :=dto.SysDictTypeInsertReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetCreateBy(user.GetUserId(c)) + err = s.Insert(&req) + if err != nil { + e.Logger.Error(err) + e.Error(500, err,fmt.Sprintf(" 创建字典类型失败,详情:%s", err.Error())) + return + } + e.OK(req.GetId(), "创建成功") +} + +// Update +// @Summary 修改字典类型 +// @Description 获取JSON +// @Tags 字典类型 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysDictTypeUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dict/type/{dictId} [put] +// @Security Bearer +func (e SysDictType) Update(c *gin.Context) { + s := service.SysDictType{} + req :=dto.SysDictTypeUpdateReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Error(500, err, err.Error()) + e.Logger.Error(err) + return + } + req.SetUpdateBy(user.GetUserId(c)) + err = s.Update(&req) + if err != nil { + e.Logger.Error(err) + return + } + e.OK(req.GetId(), "更新成功") +} + +// Delete +// @Summary 删除字典类型 +// @Description 删除数据 +// @Tags 字典类型 +// @Param dictCode body dto.SysDictTypeDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dict/type [delete] +// @Security Bearer +func (e SysDictType) Delete(c *gin.Context) { + s := service.SysDictType{} + req :=dto.SysDictTypeDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + err = s.Remove(&req) + if err != nil { + e.Error(500, err, err.Error()) + return + } + e.OK(req.GetId(), "删除成功") +} + +// GetAll +// @Summary 字典类型全部数据 代码生成使用接口 +// @Description 获取JSON +// @Tags 字典类型 +// @Param dictName query string false "dictName" +// @Param dictId query string false "dictId" +// @Param dictType query string false "dictType" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dict/type-option-select [get] +// @Security Bearer +func (e SysDictType) GetAll(c *gin.Context) { + s := service.SysDictType{} + req :=dto.SysDictTypeGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + list := make([]models.SysDictType, 0) + err = s.GetAll(&req, &list) + if err != nil { + e.Error(500, err, err.Error()) + return + } + e.OK(list, "查询成功") +} \ No newline at end of file diff --git a/app/admin/apis/sys_login_log.go b/app/admin/apis/sys_login_log.go new file mode 100644 index 0000000..6b6b4af --- /dev/null +++ b/app/admin/apis/sys_login_log.go @@ -0,0 +1,110 @@ +package apis + +import ( + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "go-admin/app/admin/models" + + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" +) + +type SysLoginLog struct { + api.Api +} + +// GetPage 登录日志列表 +// @Summary 登录日志列表 +// @Description 获取JSON +// @Tags 登录日志 +// @Param username query string false "用户名" +// @Param ipaddr query string false "ip地址" +// @Param loginLocation query string false "归属地" +// @Param status query string false "状态" +// @Param beginTime query string false "开始时间" +// @Param endTime query string false "结束时间" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-login-log [get] +// @Security Bearer +func (e SysLoginLog) GetPage(c *gin.Context) { + s := service.SysLoginLog{} + req :=dto.SysLoginLogGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + list := make([]models.SysLoginLog, 0) + var count int64 + err = s.GetPage(&req, &list, &count) + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 登录日志通过id获取 +// @Summary 登录日志通过id获取 +// @Description 获取JSON +// @Tags 登录日志 +// @Param id path string false "id" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-login-log/{id} [get] +// @Security Bearer +func (e SysLoginLog) Get(c *gin.Context) { + s := service.SysLoginLog{} + req :=dto.SysLoginLogGetReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.SysLoginLog + err = s.Get(&req, &object) + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.OK(object, "查询成功") +} + +// Delete 登录日志删除 +// @Summary 登录日志删除 +// @Description 登录日志删除 +// @Tags 登录日志 +// @Param data body dto.SysLoginLogDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-login-log [delete] +// @Security Bearer +func (e SysLoginLog) Delete(c *gin.Context) { + s := service.SysLoginLog{} + req :=dto.SysLoginLogDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + err = s.Remove(&req) + if err != nil { + e.Error(500, err, "删除失败") + return + } + e.OK(req.GetId(), "删除成功") +} \ No newline at end of file diff --git a/app/admin/apis/sys_menu.go b/app/admin/apis/sys_menu.go new file mode 100644 index 0000000..d6eab24 --- /dev/null +++ b/app/admin/apis/sys_menu.go @@ -0,0 +1,287 @@ +package apis + +import ( + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + "go-admin/app/admin/models" + + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" +) + +type SysMenu struct { + api.Api +} + +// GetPage Menu列表数据 +// @Summary Menu列表数据 +// @Description 获取JSON +// @Tags 菜单 +// @Param menuName query string false "menuName" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/menu [get] +// @Security Bearer +func (e SysMenu) GetPage(c *gin.Context) { + s := service.SysMenu{} + req := dto.SysMenuGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var list = make([]models.SysMenu, 0) + err = s.GetPage(&req, &list).Error + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.OK(list, "查询成功") +} + +// Get 获取菜单详情 +// @Summary Menu详情数据 +// @Description 获取JSON +// @Tags 菜单 +// @Param id path string false "id" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/menu/{id} [get] +// @Security Bearer +func (e SysMenu) Get(c *gin.Context) { + req := dto.SysMenuGetReq{} + s := new(service.SysMenu) + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object = models.SysMenu{} + + err = s.Get(&req, &object).Error + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.OK(object, "查询成功") +} + +// Insert 创建菜单 +// @Summary 创建菜单 +// @Description 获取JSON +// @Tags 菜单 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysMenuInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/menu [post] +// @Security Bearer +func (e SysMenu) Insert(c *gin.Context) { + req := dto.SysMenuInsertReq{} + s := new(service.SysMenu) + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + err = s.Insert(&req).Error + if err != nil { + e.Error(500, err, "创建失败") + return + } + e.OK(req.GetId(), "创建成功") +} + +// Update 修改菜单 +// @Summary 修改菜单 +// @Description 获取JSON +// @Tags 菜单 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.SysMenuUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/menu/{id} [put] +// @Security Bearer +func (e SysMenu) Update(c *gin.Context) { + req := dto.SysMenuUpdateReq{} + s := new(service.SysMenu) + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + req.SetUpdateBy(user.GetUserId(c)) + err = s.Update(&req).Error + if err != nil { + e.Error(500, err, "更新失败") + return + } + e.OK(req.GetId(), "更新成功") +} + +// Delete 删除菜单 +// @Summary 删除菜单 +// @Description 删除数据 +// @Tags 菜单 +// @Param data body dto.SysMenuDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/menu [delete] +// @Security Bearer +func (e SysMenu) Delete(c *gin.Context) { + control := new(dto.SysMenuDeleteReq) + s := new(service.SysMenu) + err := e.MakeContext(c). + MakeOrm(). + Bind(control, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + err = s.Remove(control).Error + if err != nil { + e.Logger.Errorf("RemoveSysMenu error, %s", err) + e.Error(500, err, "删除失败") + return + } + e.OK(control.GetId(), "删除成功") +} + +// GetMenuRole 根据登录角色名称获取菜单列表数据(左菜单使用) +// @Summary 根据登录角色名称获取菜单列表数据(左菜单使用) +// @Description 获取JSON +// @Tags 菜单 +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/menurole [get] +// @Security Bearer +func (e SysMenu) GetMenuRole(c *gin.Context) { + s := new(service.SysMenu) + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + result, err := s.SetMenuRole(user.GetRoleName(c)) + + if err != nil { + e.Error(500, err, "查询失败") + return + } + + e.OK(result, "") +} + +//// GetMenuIDS 获取角色对应的菜单id数组 +//// @Summary 获取角色对应的菜单id数组,设置角色权限使用 +//// @Description 获取JSON +//// @Tags 菜单 +//// @Param id path int true "id" +//// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +//// @Router /api/v1/menuids/{id} [get] +//// @Security Bearer +//func (e SysMenu) GetMenuIDS(c *gin.Context) { +// s := new(service.SysMenu) +// r := service.SysRole{} +// m := dto.SysRoleByName{} +// err := e.MakeContext(c). +// MakeOrm(). +// Bind(&m, binding.JSON). +// MakeService(&s.Service). +// MakeService(&r.Service). +// Errors +// if err != nil { +// e.Logger.Error(err) +// e.Error(500, err, err.Error()) +// return +// } +// var data models.SysRole +// err = r.GetWithName(&m, &data).Error +// +// //data.RoleName = c.GetString("role") +// //data.UpdateBy = user.GetUserId(c) +// //result, err := data.GetIDS(s.Orm) +// +// if err != nil { +// e.Logger.Errorf("GetIDS error, %s", err.Error()) +// e.Error(500, err, "获取失败") +// return +// } +// e.OK(result, "") +//} + +// GetMenuTreeSelect 根据角色ID查询菜单下拉树结构 +// @Summary 角色修改使用的菜单列表 +// @Description 获取JSON +// @Tags 菜单 +// @Accept application/json +// @Product application/json +// @Param roleId path int true "roleId" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/menuTreeselect/{roleId} [get] +// @Security Bearer +func (e SysMenu) GetMenuTreeSelect(c *gin.Context) { + m := service.SysMenu{} + r := service.SysRole{} + req :=dto.SelectRole{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&m.Service). + MakeService(&r.Service). + Bind(&req, nil). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + result, err := m.SetLabel() + if err != nil { + e.Error(500, err, "查询失败") + return + } + + menuIds := make([]int, 0) + if req.RoleId != 0 { + menuIds, err = r.GetRoleMenuId(req.RoleId) + if err != nil { + e.Error(500, err, "") + return + } + } + e.OK(gin.H{ + "menus": result, + "checkedKeys": menuIds, + }, "获取成功") +} \ No newline at end of file diff --git a/app/admin/apis/sys_opera_log.go b/app/admin/apis/sys_opera_log.go new file mode 100644 index 0000000..2803924 --- /dev/null +++ b/app/admin/apis/sys_opera_log.go @@ -0,0 +1,118 @@ +package apis + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" +) + +type SysOperaLog struct { + api.Api +} + +// GetPage 操作日志列表 +// @Summary 操作日志列表 +// @Description 获取JSON +// @Tags 操作日志 +// @Param title query string false "title" +// @Param method query string false "method" +// @Param requestMethod query string false "requestMethod" +// @Param operUrl query string false "operUrl" +// @Param operIp query string false "operIp" +// @Param status query string false "status" +// @Param beginTime query string false "beginTime" +// @Param endTime query string false "endTime" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-opera-log [get] +// @Security Bearer +func (e SysOperaLog) GetPage(c *gin.Context) { + s := service.SysOperaLog{} + req := new(dto.SysOperaLogGetPageReq) + err := e.MakeContext(c). + MakeOrm(). + Bind(req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + list := make([]models.SysOperaLog, 0) + var count int64 + + err = s.GetPage(req, &list, &count) + if err != nil { + e.Error(500, err, "查询失败") + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 操作日志通过id获取 +// @Summary 操作日志通过id获取 +// @Description 获取JSON +// @Tags 操作日志 +// @Param id path string false "id" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-opera-log/{id} [get] +// @Security Bearer +func (e SysOperaLog) Get(c *gin.Context) { + s := new(service.SysOperaLog) + req :=dto.SysOperaLogGetReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.SysOperaLog + err = s.Get(&req, &object) + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.OK(object, "查询成功") +} + +// Delete 操作日志删除 +// DeleteSysMenu 操作日志删除 +// @Summary 删除操作日志 +// @Description 删除数据 +// @Tags 操作日志 +// @Param data body dto.SysOperaLogDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-opera-log [delete] +// @Security Bearer +func (e SysOperaLog) Delete(c *gin.Context) { + s := new(service.SysOperaLog) + req :=dto.SysOperaLogDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + err = s.Remove(&req) + if err != nil { + e.Logger.Error(err) + e.Error(500,err, fmt.Sprintf("删除失败!错误详情:%s", err.Error())) + return + } + e.OK(req.GetId(), "删除成功") +} diff --git a/app/admin/apis/sys_post.go b/app/admin/apis/sys_post.go new file mode 100644 index 0000000..a12d54a --- /dev/null +++ b/app/admin/apis/sys_post.go @@ -0,0 +1,184 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" +) + +type SysPost struct { + api.Api +} + +// GetPage +// @Summary 岗位列表数据 +// @Description 获取JSON +// @Tags 岗位 +// @Param postName query string false "postName" +// @Param postCode query string false "postCode" +// @Param postId query string false "postId" +// @Param status query string false "status" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/post [get] +// @Security Bearer +func (e SysPost) GetPage(c *gin.Context) { + s := service.SysPost{} + req :=dto.SysPostPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + list := make([]models.SysPost, 0) + var count int64 + + err = s.GetPage(&req, &list, &count) + if err != nil { + e.Error(500, err, "查询失败") + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get +// @Summary 获取岗位信息 +// @Description 获取JSON +// @Tags 岗位 +// @Param id path int true "编码" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/post/{postId} [get] +// @Security Bearer +func (e SysPost) Get(c *gin.Context) { + s := service.SysPost{} + req :=dto.SysPostGetReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.SysPost + + err = s.Get(&req, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("岗位信息获取失败!错误详情:%s", err.Error())) + return + } + + e.OK(object, "查询成功") +} + +// Insert +// @Summary 添加岗位 +// @Description 获取JSON +// @Tags 岗位 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysPostInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/post [post] +// @Security Bearer +func (e SysPost) Insert(c *gin.Context) { + s := service.SysPost{} + req :=dto.SysPostInsertReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetCreateBy(user.GetUserId(c)) + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("新建岗位失败!错误详情:%s", err.Error())) + return + } + e.OK(req.GetId(), "创建成功") +} + +// Update +// @Summary 修改岗位 +// @Description 获取JSON +// @Tags 岗位 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysPostUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/post/{id} [put] +// @Security Bearer +func (e SysPost) Update(c *gin.Context) { + s := service.SysPost{} + req :=dto.SysPostUpdateReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + req.SetUpdateBy(user.GetUserId(c)) + + err = s.Update(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("岗位更新失败!错误详情:%s", err.Error())) + return + } + e.OK(req.GetId(), "更新成功") +} + +// Delete +// @Summary 删除岗位 +// @Description 删除数据 +// @Tags 岗位 +// @Param id body dto.SysPostDeleteReq true "请求参数" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/post [delete] +// @Security Bearer +func (e SysPost) Delete(c *gin.Context) { + s := service.SysPost{} + req :=dto.SysPostDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + err = s.Remove(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("岗位删除失败!错误详情:%s", err.Error())) + return + } + e.OK(req.GetId(), "删除成功") +} \ No newline at end of file diff --git a/app/admin/apis/sys_role.go b/app/admin/apis/sys_role.go new file mode 100644 index 0000000..d7ee1e7 --- /dev/null +++ b/app/admin/apis/sys_role.go @@ -0,0 +1,284 @@ +package apis + +import ( + "fmt" + "go-admin/common/global" + "net/http" + + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk" + "go-admin/app/admin/models" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" +) + +type SysRole struct { + api.Api +} + +// GetPage +// @Summary 角色列表数据 +// @Description Get JSON +// @Tags 角色/Role +// @Param roleName query string false "roleName" +// @Param status query string false "status" +// @Param roleKey query string false "roleKey" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/role [get] +// @Security Bearer +func (e SysRole) GetPage(c *gin.Context) { + s := service.SysRole{} + req := dto.SysRoleGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + list := make([]models.SysRole, 0) + var count int64 + + err = s.GetPage(&req, &list, &count) + if err != nil { + e.Error(500, err, "查询失败") + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get +// @Summary 获取Role数据 +// @Description 获取JSON +// @Tags 角色/Role +// @Param roleId path string false "roleId" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/role/{id} [get] +// @Security Bearer +func (e SysRole) Get(c *gin.Context) { + s := service.SysRole{} + req := dto.SysRoleGetReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, fmt.Sprintf(" %s ", err.Error())) + return + } + + var object models.SysRole + + err = s.Get(&req, &object) + if err != nil { + e.Error(http.StatusUnprocessableEntity, err, "查询失败") + return + } + + e.OK(object, "查询成功") +} + +// Insert +// @Summary 创建角色 +// @Description 获取JSON +// @Tags 角色/Role +// @Accept application/json +// @Product application/json +// @Param data body dto.SysRoleInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/role [post] +// @Security Bearer +func (e SysRole) Insert(c *gin.Context) { + s := service.SysRole{} + req := dto.SysRoleInsertReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // 设置创建人 + req.CreateBy = user.GetUserId(c) + if req.Status == "" { + req.Status = "2" + } + cb := sdk.Runtime.GetCasbinKey(c.Request.Host) + err = s.Insert(&req, cb) + if err != nil { + e.Logger.Error(err) + e.Error(500, err, "创建失败,"+err.Error()) + return + } + _, err = global.LoadPolicy(c) + if err != nil { + e.Logger.Error(err) + e.Error(500, err, "创建失败,"+err.Error()) + return + } + e.OK(req.GetId(), "创建成功") +} + +// Update 修改用户角色 +// @Summary 修改用户角色 +// @Description 获取JSON +// @Tags 角色/Role +// @Accept application/json +// @Product application/json +// @Param data body dto.SysRoleUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/role/{id} [put] +// @Security Bearer +func (e SysRole) Update(c *gin.Context) { + s := service.SysRole{} + req := dto.SysRoleUpdateReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, nil, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + cb := sdk.Runtime.GetCasbinKey(c.Request.Host) + + req.SetUpdateBy(user.GetUserId(c)) + + err = s.Update(&req, cb) + if err != nil { + e.Logger.Error(err) + return + } + + _, err = global.LoadPolicy(c) + if err != nil { + e.Logger.Error(err) + e.Error(500, err, "更新失败,"+err.Error()) + return + } + + e.OK(req.GetId(), "更新成功") +} + +// Delete +// @Summary 删除用户角色 +// @Description 删除数据 +// @Tags 角色/Role +// @Param data body dto.SysRoleDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/role [delete] +// @Security Bearer +func (e SysRole) Delete(c *gin.Context) { + s := new(service.SysRole) + req := dto.SysRoleDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, fmt.Sprintf("删除角色 %v 失败,\r\n失败信息 %s", req.Ids, err.Error())) + return + } + + cb := sdk.Runtime.GetCasbinKey(c.Request.Host) + err = s.Remove(&req, cb) + if err != nil { + e.Logger.Error(err) + e.Error(500, err, "") + return + } + + e.OK(req.GetId(), fmt.Sprintf("删除角色角色 %v 状态成功!", req.GetId())) +} + +// Update2Status 修改用户角色状态 +// @Summary 修改用户角色 +// @Description 获取JSON +// @Tags 角色/Role +// @Accept application/json +// @Product application/json +// @Param data body dto.UpdateStatusReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/role-status/{id} [put] +// @Security Bearer +func (e SysRole) Update2Status(c *gin.Context) { + s := service.SysRole{} + req := dto.UpdateStatusReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, fmt.Sprintf("更新角色状态失败,失败原因:%s ", err.Error())) + return + } + req.SetUpdateBy(user.GetUserId(c)) + err = s.UpdateStatus(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("更新角色状态失败,失败原因:%s ", err.Error())) + return + } + e.OK(req.GetId(), fmt.Sprintf("更新角色 %v 状态成功!", req.GetId())) +} + +// Update2DataScope 更新角色数据权限 +// @Summary 更新角色数据权限 +// @Description 获取JSON +// @Tags 角色/Role +// @Accept application/json +// @Product application/json +// @Param data body dto.RoleDataScopeReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/role-status/{id} [put] +// @Security Bearer +func (e SysRole) Update2DataScope(c *gin.Context) { + s := service.SysRole{} + req := dto.RoleDataScopeReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + data := &models.SysRole{ + RoleId: req.RoleId, + DataScope: req.DataScope, + DeptIds: req.DeptIds, + } + data.UpdateBy = user.GetUserId(c) + err = s.UpdateDataScope(&req).Error + if err != nil { + e.Error(500, err, fmt.Sprintf("更新角色数据权限失败!错误详情:%s", err.Error())) + return + } + e.OK(nil, "操作成功") +} diff --git a/app/admin/apis/sys_user.go b/app/admin/apis/sys_user.go new file mode 100644 index 0000000..0cf6fc0 --- /dev/null +++ b/app/admin/apis/sys_user.go @@ -0,0 +1,459 @@ +package apis + +import ( + "github.com/gin-gonic/gin/binding" + "go-admin/app/admin/models" + "golang.org/x/crypto/bcrypt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "github.com/google/uuid" + + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type SysUser struct { + api.Api +} + +// GetPage +// @Summary 列表用户信息数据 +// @Description 获取JSON +// @Tags 用户 +// @Param username query string false "username" +// @Success 200 {string} {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-user [get] +// @Security Bearer +func (e SysUser) GetPage(c *gin.Context) { + s := service.SysUser{} + req := dto.SysUserGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + //数据权限检查 + p := actions.GetPermissionFromContext(c) + + list := make([]models.SysUser, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, "查询失败") + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get +// @Summary 获取用户 +// @Description 获取JSON +// @Tags 用户 +// @Param userId path int true "用户编码" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-user/{userId} [get] +// @Security Bearer +func (e SysUser) Get(c *gin.Context) { + s := service.SysUser{} + req := dto.SysUserById{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.SysUser + //数据权限检查 + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(http.StatusUnprocessableEntity, err, "查询失败") + return + } + e.OK(object, "查询成功") +} + +// Insert +// @Summary 创建用户 +// @Description 获取JSON +// @Tags 用户 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysUserInsertReq true "用户数据" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-user [post] +// @Security Bearer +func (e SysUser) Insert(c *gin.Context) { + s := service.SysUser{} + req := dto.SysUserInsertReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + err = s.Insert(&req) + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update +// @Summary 修改用户数据 +// @Description 获取JSON +// @Tags 用户 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysUserUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-user/{userId} [put] +// @Security Bearer +func (e SysUser) Update(c *gin.Context) { + s := service.SysUser{} + req := dto.SysUserUpdateReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + req.SetUpdateBy(user.GetUserId(c)) + + //数据权限检查 + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Logger.Error(err) + return + } + e.OK(req.GetId(), "更新成功") +} + +// Delete +// @Summary 删除用户数据 +// @Description 删除数据 +// @Tags 用户 +// @Param userId path int true "userId" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-user/{userId} [delete] +// @Security Bearer +func (e SysUser) Delete(c *gin.Context) { + s := service.SysUser{} + req := dto.SysUserById{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // 设置编辑人 + req.SetUpdateBy(user.GetUserId(c)) + + // 数据权限检查 + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Logger.Error(err) + return + } + e.OK(req.GetId(), "删除成功") +} + +// InsetAvatar +// @Summary 修改头像 +// @Description 获取JSON +// @Tags 个人中心 +// @Accept multipart/form-data +// @Param file formData file true "file" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/user/avatar [post] +// @Security Bearer +func (e SysUser) InsetAvatar(c *gin.Context) { + s := service.SysUser{} + req := dto.UpdateSysUserAvatarReq{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 数据权限检查 + p := actions.GetPermissionFromContext(c) + form, _ := c.MultipartForm() + files := form.File["upload[]"] + guid := uuid.New().String() + filPath := "static/uploadfile/" + guid + ".jpg" + for _, file := range files { + e.Logger.Debugf("upload avatar file: %s", file.Filename) + // 上传文件至指定目录 + err = c.SaveUploadedFile(file, filPath) + if err != nil { + e.Logger.Errorf("save file error, %s", err.Error()) + e.Error(500, err, "") + return + } + } + req.UserId = p.UserId + req.Avatar = "/" + filPath + + err = s.UpdateAvatar(&req, p) + if err != nil { + e.Logger.Error(err) + return + } + e.OK(filPath, "修改成功") +} + +// UpdateStatus 修改用户状态 +// @Summary 修改用户状态 +// @Description 获取JSON +// @Tags 用户 +// @Accept application/json +// @Product application/json +// @Param data body dto.UpdateSysUserStatusReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/user/status [put] +// @Security Bearer +func (e SysUser) UpdateStatus(c *gin.Context) { + s := service.SysUser{} + req := dto.UpdateSysUserStatusReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + req.SetUpdateBy(user.GetUserId(c)) + + //数据权限检查 + p := actions.GetPermissionFromContext(c) + + err = s.UpdateStatus(&req, p) + if err != nil { + e.Logger.Error(err) + return + } + e.OK(req.GetId(), "更新成功") +} + +// ResetPwd 重置用户密码 +// @Summary 重置用户密码 +// @Description 获取JSON +// @Tags 用户 +// @Accept application/json +// @Product application/json +// @Param data body dto.ResetSysUserPwdReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/user/pwd/reset [put] +// @Security Bearer +func (e SysUser) ResetPwd(c *gin.Context) { + s := service.SysUser{} + req := dto.ResetSysUserPwdReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + req.SetUpdateBy(user.GetUserId(c)) + + //数据权限检查 + p := actions.GetPermissionFromContext(c) + + err = s.ResetPwd(&req, p) + if err != nil { + e.Logger.Error(err) + return + } + e.OK(req.GetId(), "更新成功") +} + +// UpdatePwd +// @Summary 修改密码 +// @Description 获取JSON +// @Tags 用户 +// @Accept application/json +// @Product application/json +// @Param data body dto.PassWord true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/user/pwd/set [put] +// @Security Bearer +func (e SysUser) UpdatePwd(c *gin.Context) { + s := service.SysUser{} + req := dto.PassWord{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // 数据权限检查 + p := actions.GetPermissionFromContext(c) + var hash []byte + if hash, err = bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost); err != nil { + req.NewPassword = string(hash) + } + + err = s.UpdatePwd(user.GetUserId(c), req.OldPassword, req.NewPassword, p) + if err != nil { + e.Logger.Error(err) + e.Error(http.StatusForbidden, err, "密码修改失败") + return + } + + e.OK(nil, "密码修改成功") +} + +// GetProfile +// @Summary 获取个人中心用户 +// @Description 获取JSON +// @Tags 个人中心 +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/user/profile [get] +// @Security Bearer +func (e SysUser) GetProfile(c *gin.Context) { + s := service.SysUser{} + req := dto.SysUserById{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + req.Id = user.GetUserId(c) + + sysUser := models.SysUser{} + roles := make([]models.SysRole, 0) + posts := make([]models.SysPost, 0) + err = s.GetProfile(&req, &sysUser, &roles, &posts) + if err != nil { + e.Logger.Errorf("get user profile error, %s", err.Error()) + e.Error(500, err, "获取用户信息失败") + return + } + e.OK(gin.H{ + "user": sysUser, + "roles": roles, + "posts": posts, + }, "查询成功") +} + +// GetInfo +// @Summary 获取个人信息 +// @Description 获取JSON +// @Tags 个人中心 +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/getinfo [get] +// @Security Bearer +func (e SysUser) GetInfo(c *gin.Context) { + req := dto.SysUserById{} + s := service.SysUser{} + r := service.SysRole{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&r.Service). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + p := actions.GetPermissionFromContext(c) + var roles = make([]string, 1) + roles[0] = user.GetRoleName(c) + var permissions = make([]string, 1) + permissions[0] = "*:*:*" + var buttons = make([]string, 1) + buttons[0] = "*:*:*" + + var mp = make(map[string]interface{}) + mp["roles"] = roles + if user.GetRoleName(c) == "admin" || user.GetRoleName(c) == "系统管理员" { + mp["permissions"] = permissions + mp["buttons"] = buttons + } else { + list, _ := r.GetById(user.GetRoleId(c)) + mp["permissions"] = list + mp["buttons"] = list + } + sysUser := models.SysUser{} + req.Id = user.GetUserId(c) + err = s.Get(&req, p, &sysUser) + if err != nil { + e.Error(http.StatusUnauthorized, err, "登录失败") + return + } + mp["introduction"] = " am a super administrator" + mp["avatar"] = "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif" + if sysUser.Avatar != "" { + mp["avatar"] = sysUser.Avatar + } + mp["userName"] = sysUser.Username + mp["userId"] = sysUser.UserId + mp["deptId"] = sysUser.DeptId + mp["name"] = sysUser.NickName + mp["code"] = 200 + e.OK(mp, "") +} diff --git a/app/admin/apis/vts_recharge.go b/app/admin/apis/vts_recharge.go new file mode 100644 index 0000000..8f36528 --- /dev/null +++ b/app/admin/apis/vts_recharge.go @@ -0,0 +1,194 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type VtsRecharge struct { + api.Api +} + +// GetPage 获取coinGate充值记录表列表 +// @Summary 获取coinGate充值记录表列表 +// @Description 获取coinGate充值记录表列表 +// @Tags coinGate充值记录表 +// @Param coinCode query string false "币种" +// @Param orderNo query string false "订单号" +// @Param adUserId query int64 false "用户id" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.VtsRecharge}} "{"code": 200, "data": [...]}" +// @Router /api/v1/vts-recharge [get] +// @Security Bearer +func (e VtsRecharge) GetPage(c *gin.Context) { + req := dto.VtsRechargeGetPageReq{} + s := service.VtsRecharge{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.VtsRecharge, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取coinGate充值记录表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取coinGate充值记录表 +// @Summary 获取coinGate充值记录表 +// @Description 获取coinGate充值记录表 +// @Tags coinGate充值记录表 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.VtsRecharge} "{"code": 200, "data": [...]}" +// @Router /api/v1/vts-recharge/{id} [get] +// @Security Bearer +func (e VtsRecharge) Get(c *gin.Context) { + req := dto.VtsRechargeGetReq{} + s := service.VtsRecharge{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.VtsRecharge + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取coinGate充值记录表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK( object, "查询成功") +} + +// Insert 创建coinGate充值记录表 +// @Summary 创建coinGate充值记录表 +// @Description 创建coinGate充值记录表 +// @Tags coinGate充值记录表 +// @Accept application/json +// @Product application/json +// @Param data body dto.VtsRechargeInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/vts-recharge [post] +// @Security Bearer +func (e VtsRecharge) Insert(c *gin.Context) { + req := dto.VtsRechargeInsertReq{} + s := service.VtsRecharge{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建coinGate充值记录表失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改coinGate充值记录表 +// @Summary 修改coinGate充值记录表 +// @Description 修改coinGate充值记录表 +// @Tags coinGate充值记录表 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.VtsRechargeUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/vts-recharge/{id} [put] +// @Security Bearer +func (e VtsRecharge) Update(c *gin.Context) { + req := dto.VtsRechargeUpdateReq{} + s := service.VtsRecharge{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改coinGate充值记录表失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "修改成功") +} + +// Delete 删除coinGate充值记录表 +// @Summary 删除coinGate充值记录表 +// @Description 删除coinGate充值记录表 +// @Tags coinGate充值记录表 +// @Param data body dto.VtsRechargeDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/vts-recharge [delete] +// @Security Bearer +func (e VtsRecharge) Delete(c *gin.Context) { + s := service.VtsRecharge{} + req := dto.VtsRechargeDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除coinGate充值记录表失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "删除成功") +} diff --git a/app/admin/fronted/line_user.go b/app/admin/fronted/line_user.go new file mode 100644 index 0000000..8cc610a --- /dev/null +++ b/app/admin/fronted/line_user.go @@ -0,0 +1,581 @@ +package fronted + +import ( + "errors" + "fmt" + "go-admin/app/admin/models" + "go-admin/app/admin/models/sysmodel" + "go-admin/app/admin/service" + "go-admin/app/admin/service/aduserdb" + "go-admin/app/admin/service/common" + "go-admin/app/admin/service/dto" + "go-admin/common/const/rediskey" + "go-admin/common/helper" + "go-admin/common/service/sysservice/authservice" + "go-admin/common/service/sysservice/sysstatuscode" + statuscode "go-admin/common/status_code" + ext "go-admin/config" + "go-admin/models/coingatedto" + "go-admin/pkg/cryptohelper/inttostring" + "go-admin/pkg/utility" + "go-admin/services/binanceservice" + "go-admin/services/udunservice" + + "github.com/bytedance/sonic" + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/shopspring/decimal" + "gorm.io/gorm" +) + +type LineUserApi struct { + api.Api +} + +// Register 用户注册 +func (e LineUserApi) Register(c *gin.Context) { + s := service.LineUser{} + req := sysmodel.FrontedUserRegisterReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + //校验参数 + code := req.CheckParams() + if code != statuscode.OK { + e.Error(code, nil, sysstatuscode.GetStatusCodeDescription(c, code)) + return + } + // 注册前校验 + pid, code := authservice.UserRegisterBefore(e.Orm, req) + if code != statuscode.OK { + e.Error(code, nil, sysstatuscode.GetStatusCodeDescription(c, code)) + return + } + req.Pid = pid + req.IP = utility.GetIp(c) + ok, user := authservice.UserRegister(e.Orm, req) + if ok != statuscode.OK { + e.Error(ok, nil, sysstatuscode.GetStatusCodeDescription(c, ok)) + return + } + resp := map[string]interface{}{ + "email": req.Email, + "mobile": req.Phone, + "status": "verify", + "login_type": 2, + "jwt_token": "", + "jwt_expire": "", + } + if req.RegisterType == sysmodel.TSmsCode { + if req.Pid > 0 { + updateSql := `UPDATE line_user SET recommend_num = recommend_num + 1 WHERE id = ?` + if err := e.Orm.Exec(updateSql, req.Pid).Error; err != nil { + e.Logger.Error(err) + } + } + token, expire, _ := authservice.GenerateToken(user.Id, 1, user.Mobile, user.Mobile, user.Email, req.IP, "PC") + resp["jwt_token"] = token + resp["jwt_expire"] = expire + resp["status"] = "normal" + } + + e.OK(resp, "success") +} + +// VerifyEmail 验证邮箱 +func (e LineUserApi) VerifyEmail(c *gin.Context) { + s := service.LineUser{} + req := dto.FrontedUserVerifyEmailReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + if req.VerifyCode == "" || req.Email == "" { + e.Error(statuscode.ParameterInvalid, nil, sysstatuscode.GetStatusCodeDescription(c, statuscode.ParameterInvalid)) + return + } + // 核验邮箱验证码 + code := authservice.UserVerifyEmail(req.Email, req.VerifyCode, e.Orm) + if code != statuscode.OK { + e.Error(code, nil, sysstatuscode.GetStatusCodeDescription(c, code)) + return + } + ok := s.UserVerifyEmail(req.Email) + if ok != statuscode.OK { + e.Error(code, nil, sysstatuscode.GetStatusCodeDescription(c, code)) + return + } + e.OK(nil, "success") + +} + +// SendVerifyEmail 发送注册校验邮箱 +func (e LineUserApi) SendVerifyEmail(c *gin.Context) { + s := service.LineUser{} + req := dto.FrontedSendVerifyEmailReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + code := req.CheckParams() + if code != statuscode.OK { + e.Error(code, nil, sysstatuscode.GetStatusCodeDescription(c, code)) + return + } + emailCode := inttostring.GenerateRandomString(10) + code = authservice.SendRegisterEmail(req.Email, emailCode) + if code != statuscode.OK { + e.Error(code, nil, sysstatuscode.GetStatusCodeDescription(c, code)) + return + } + e.OK(nil, "success") + +} + +// SendRegisterSms 发送注册短信 +func (e LineUserApi) SendRegisterSms(c *gin.Context) { + s := service.LineUser{} + req := dto.FrontedSendVerifySmsReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + code := req.CheckParams() + if code != statuscode.OK { + e.Error(code, nil, sysstatuscode.GetStatusCodeDescription(c, code)) + return + } + + user, _ := aduserdb.GetUserByPhone(e.Orm, req.PhoneAreaCode, req.Phone) + if user.Id > 0 { + e.Error(statuscode.TheAccountIsAlreadyRegistered, nil, sysstatuscode.GetStatusCodeDescription(c, statuscode.TheAccountIsAlreadyRegistered)) + return + } + + code = authservice.SendGoToneSms(req.Phone, req.PhoneAreaCode) + if code != statuscode.OK { + e.Error(code, nil, sysstatuscode.GetStatusCodeDescription(c, code)) + return + } + e.OK(nil, "success") + +} + +// Login 登录 +func (e LineUserApi) Login(c *gin.Context) { + s := service.LineUser{} + req := dto.FrontedLoginReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + code := req.CheckParams() + if code != statuscode.OK { + e.Error(code, nil, sysstatuscode.GetStatusCodeDescription(c, code)) + return + } + // 登录前校验 + user, code, langArg := authservice.UserPwdLoginBefore(e.Orm, req) + if code != statuscode.OK { + e.Error(code, nil, sysstatuscode.GetStatusCodeDiscreptionArgs(c, code, langArg)) + return + } + token, expire, code := authservice.GenerateToken(user.Id, 1, user.Email, user.Mobile, user.Email, utility.GetIp(c), "PC") + if code != statuscode.OK { + e.Error(code, nil, sysstatuscode.GetStatusCodeDescription(c, code)) + return + // return app.Fail(ctx, code) + } + resp := map[string]interface{}{ + "email": req.Email, + "mobile": "", + "status": user.Status, + "login_type": 2, + "jwt_token": token, + "jwt_expire": expire, + } + e.OK(resp, "success") + +} + +func (e LineUserApi) UserInfo(c *gin.Context) { + userId := common.GetUserId(c) + var user models.LineUser + err := e.Orm.Model(&models.LineUser{}).Where("id = ?", userId).Find(&user).Error + if err != nil && !errors.Is(err, gorm.ErrNotImplemented) { + e.Error(statuscode.UserIdInvalid, nil, sysstatuscode.GetStatusCodeDescription(c, statuscode.UserIdInvalid)) + return + } + + if user.Status == "verify" { + e.Error(statuscode.UserNotVerify, nil, sysstatuscode.GetStatusCodeDescription(c, statuscode.UserNotVerify)) + return + } + + e.OK(nil, "success") +} + +// AddApiKey 用户手动添加apikey +func (e LineUserApi) AddApiKey(c *gin.Context) { + s := service.LineUser{} + req := dto.AddApiKeyReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + code := req.CheckParams() + if code != statuscode.OK { + e.Error(code, nil, sysstatuscode.GetStatusCodeDescription(c, code)) + return + } + + code = s.AddApiKey(common.GetUserId(c), &req) + if code != statuscode.OK { + e.Error(code, nil, sysstatuscode.GetStatusCodeDescription(c, code)) + return + } + e.OK(nil, "success") +} + +// UpdateApiKey 修改apikey +func (e LineUserApi) UpdateApiKey(c *gin.Context) { + s := service.LineUser{} + req := dto.AddApiKeyReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + code := req.CheckParams() + if code != statuscode.OK { + e.Error(code, nil, sysstatuscode.GetStatusCodeDescription(c, code)) + return + } + code = s.UpdateApiKey(common.GetUserId(c), &req) + if code != statuscode.OK { + e.Error(code, nil, sysstatuscode.GetStatusCodeDescription(c, code)) + return + } + e.OK(nil, "success") + +} + +// GetWhiteIp 用户手动获取ip +func (e LineUserApi) GetWhiteIp(c *gin.Context) { + //s := service.LineUser{} + configS := service.SysConfig{} + //req := dto.AddApiKeyReq{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&configS.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req := dto.SysConfigByKeyReq{ConfigKey: "sys_user_white_ip"} + var resp dto.GetSysConfigByKEYForServiceResp + err = configS.GetWithKey(&req, &resp) + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + e.OK(resp.ConfigValue, "success") + +} + +// Info 用户中心 +func (e LineUserApi) Info(c *gin.Context) { + s := service.LineUser{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + userId := common.GetUserId(c) + binanceAccount := service.BinanceAccount{Service: s.Service} + + //获取用户资金账户资产 + resp, err := binanceAccount.GetFundingAsset(userId) + if err != nil { + e.Logger.Error(500, err, err.Error()) + return + } + //用户u的余额 + var tickerData []binanceservice.Ticker + val := helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val() + if val != "" { + sonic.Unmarshal([]byte(val), &tickerData) + } + var usdtBalance decimal.Decimal + for i, asset := range resp { + symbol := asset.Asset + "USDT" + for _, datum := range tickerData { + if datum.Symbol == symbol { + mul := utility.StringToDecimal(datum.Price).Mul(utility.StringToDecimal(asset.Free)) + usdtBalance = usdtBalance.Add(mul) + resp[i].UsdtValuation = datum.Price + } + } + //if asset.Asset == "USDT" { + // usdt = asset.Free + //} + } + + // 邀请人数 + //var inviteNum int64 + var userinfo models.LineUser + e.Orm.Model(&models.LineUser{}).Where("id = ?", userId).Find(&userinfo) + + var apiUserinfo models.LineApiUser + e.Orm.Model(&models.LineApiUser{}).Where("user_id = ?", userId).Find(&apiUserinfo) + var isAuth bool + if apiUserinfo.ApiKey != "" && apiUserinfo.ApiSecret != "" { + isAuth = true + } + //持仓分布 + var fundingAsset []models.FundingAsset + if len(resp) > 10 { + fundingAsset = resp[0:10] + } else { + fundingAsset = resp[0:] + } + + //获取盈利情况 + logs := service.LineUserProfitLogs{Service: s.Service} + totalProfit, todayProfit := logs.GetProfitInfoByUserId(userinfo.Id) + user := map[string]interface{}{ + "avatar": userinfo.Avatar, + "invite_num": userinfo.RecommendNum, + "open_status": apiUserinfo.OpenStatus, + "is_auth": isAuth, + "invite_url": fmt.Sprintf("%s/invice_url?invite_code=%s", ext.ExtConfig.Domain, userinfo.InviteCode), + "invite_code": userinfo.InviteCode, + "api_name": apiUserinfo.ApiName, + "api_key": inttostring.EncryptString(apiUserinfo.ApiKey, 4, 4), + "api_secret": inttostring.EncryptString(apiUserinfo.ApiSecret, 4, 4), + } + returnMap := map[string]interface{}{ + "u_balance": usdtBalance.Truncate(2), + "margin": userinfo.Money, + "userinfo": user, + "funding_asset": fundingAsset, + "total_profit": totalProfit.Float64, + "today_profit": todayProfit.Float64, + } + e.OK(returnMap, "success") + +} + +// OpenStatus 开启或者关闭 +func (e LineUserApi) OpenStatus(c *gin.Context) { + s := service.LineUser{} + req := dto.OpenStatusReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + userId := common.GetUserId(c) + var apiUser models.LineApiUser + err = e.Orm.Model(&models.LineApiUser{}).Where("user_id = ?", userId).Find(&apiUser).Error + + if apiUser.ApiSecret == "" || apiUser.ApiKey == "" { + e.Error(statuscode.UserApiKeyRequired, nil, sysstatuscode.GetStatusCodeDescription(c, statuscode.UserApiKeyRequired)) + return + } + err = e.Orm.Model(&models.LineApiUser{}).Where("user_id = ?", userId).Update("open_status", req.Status).Error + if err != nil { + e.Error(500, err, err.Error()) + return + } + e.OK(nil, "success") +} + +// RechargeNetworkList 充值 通过充值币种选择主网络 +func (e LineUserApi) RechargeNetworkList(c *gin.Context) { + s := service.LineUser{} + req := dto.RechargeListReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + data, code := s.RechargeNetworkList(&req) + if code != statuscode.OK { + e.Error(code, nil, sysstatuscode.GetStatusCodeDescription(c, code)) + return + } + e.OK(data, "success") +} + +// RechargeNetworkAddress 充值 通过主网ID和用户ID获取交易地址 +func (e LineUserApi) RechargeNetworkAddress(c *gin.Context) { + s := service.LineUser{} + req := dto.RechargeAddressListReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.UserId = common.GetUserId(c) + data, code := s.RechargeNetworkAddress(&req) + if code != statuscode.OK { + e.Error(code, nil, sysstatuscode.GetStatusCodeDescription(c, code)) + return + } + e.OK(data, "success") +} + +// Notify uDun回调 +func (e LineUserApi) Notify(c *gin.Context) { + timestamp := c.PostForm("timestamp") + nonce := c.PostForm("nonce") + body := c.PostForm("body") + sign := c.PostForm("sign") + callback := udunservice.TradeCallback(e.Orm, timestamp, nonce, body, sign) + c.String(200, callback) + //c.Writer.WriteString(callback) + return +} + +// FundingTrend 资金走势 +func (e LineUserApi) FundingTrend(c *gin.Context) { + s := service.LineUser{} + req := dto.RechargeAddressListReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + userId := common.GetUserId(c) + data, code := s.FundingTrend(userId) + if code != statuscode.OK { + e.Error(code, nil, sysstatuscode.GetStatusCodeDescription(c, code)) + return + } + e.OK(data, "success") +} + +// PreOrder 预下单 +func (e LineUserApi) PreOrder(c *gin.Context) { + s := service.LineUser{} + req := dto.VtsRechargePreOrderReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + userId := common.GetUserId(c) + resp := dto.VtsRechargePreOrderResp{} + err = s.PreOrder(userId, &req, &resp) + if err != nil { + e.Error(500, err, fmt.Sprintf("发起充值失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(resp, "success") +} + +func (e LineUserApi) CallBack(c *gin.Context) { + req := coingatedto.OrderCallBackResponse{} + s := service.LineUser{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + err = s.CallBack(&req) + + if err != nil { + val, _ := sonic.Marshal(req) + e.Logger.Error("回调失败:", err.Error(), " \r\n回调内容:", string(val)) + e.Error(500, err, fmt.Sprintf("支付回调失败, \r\n失败信息 %s", err.Error())) + return + } + + e.OK(nil, "") +} diff --git a/app/admin/models/binance_account.go b/app/admin/models/binance_account.go new file mode 100644 index 0000000..c4fc0cf --- /dev/null +++ b/app/admin/models/binance_account.go @@ -0,0 +1,11 @@ +package models + +type FundingAsset struct { + Asset string `json:"asset"` + Free string `json:"free"` + Locked string `json:"locked"` + Freeze string `json:"freeze"` + Withdrawing string `json:"withdrawing"` + BtcValuation string `json:"btcValuation"` + UsdtValuation string `json:"usdt_valuation"` +} diff --git a/app/admin/models/casbin_rule.go b/app/admin/models/casbin_rule.go new file mode 100644 index 0000000..d9013ee --- /dev/null +++ b/app/admin/models/casbin_rule.go @@ -0,0 +1,16 @@ +package models + +type CasbinRule struct { + ID uint `gorm:"primaryKey;autoIncrement"` + Ptype string `gorm:"size:512;uniqueIndex:unique_index"` + V0 string `gorm:"size:512;uniqueIndex:unique_index"` + V1 string `gorm:"size:512;uniqueIndex:unique_index"` + V2 string `gorm:"size:512;uniqueIndex:unique_index"` + V3 string `gorm:"size:512;uniqueIndex:unique_index"` + V4 string `gorm:"size:512;uniqueIndex:unique_index"` + V5 string `gorm:"size:512;uniqueIndex:unique_index"` +} + +func (CasbinRule) TableName() string { + return "sys_casbin_rule" +} diff --git a/app/admin/models/datascope.go b/app/admin/models/datascope.go new file mode 100644 index 0000000..8ccd047 --- /dev/null +++ b/app/admin/models/datascope.go @@ -0,0 +1,81 @@ +package models + +import ( + "errors" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "gorm.io/gorm" + + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/config" +) + +type DataPermission struct { + DataScope string + UserId int + DeptId int + RoleId int +} + +func (e *DataPermission) GetDataScope(tableName string, db *gorm.DB) (*gorm.DB, error) { + + if !config.ApplicationConfig.EnableDP { + usageStr := `数据权限已经为您` + pkg.Green(`关闭`) + `,如需开启请参考配置文件字段说明` + log.Debug("%s\n", usageStr) + return db, nil + } + user := new(SysUser) + role := new(SysRole) + err := db.Find(user, e.UserId).Error + if err != nil { + return nil, errors.New("获取用户数据出错 msg:" + err.Error()) + } + err = db.Find(role, user.RoleId).Error + if err != nil { + return nil, errors.New("获取用户数据出错 msg:" + err.Error()) + } + if role.DataScope == "2" { + db = db.Where(tableName+".create_by in (select sys_user.user_id from sys_role_dept left join sys_user on sys_user.dept_id=sys_role_dept.dept_id where sys_role_dept.role_id = ?)", user.RoleId) + } + if role.DataScope == "3" { + db = db.Where(tableName+".create_by in (SELECT user_id from sys_user where dept_id = ? )", user.DeptId) + } + if role.DataScope == "4" { + db = db.Where(tableName+".create_by in (SELECT user_id from sys_user where sys_user.dept_id in(select dept_id from sys_dept where dept_path like ? ))", "%"+pkg.IntToString(user.DeptId)+"%") + } + if role.DataScope == "5" || role.DataScope == "" { + db = db.Where(tableName+".create_by = ?", e.UserId) + } + + return db, nil +} + +//func DataScopes(tableName string, userId int) func(db *gorm.DB) *gorm.DB { +// return func(db *gorm.DB) *gorm.DB { +// user := new(SysUser) +// role := new(SysRole) +// user.UserId = userId +// err := db.Find(user, userId).Error +// if err != nil { +// db.Error = errors.New("获取用户数据出错 msg:" + err.Error()) +// return db +// } +// err = db.Find(role, user.RoleId).Error +// if err != nil { +// db.Error = errors.New("获取用户数据出错 msg:" + err.Error()) +// return db +// } +// if role.DataScope == "2" { +// return db.Where(tableName+".create_by in (select sys_user.user_id from sys_role_dept left join sys_user on sys_user.dept_id=sys_role_dept.dept_id where sys_role_dept.role_id = ?)", user.RoleId) +// } +// if role.DataScope == "3" { +// return db.Where(tableName+".create_by in (SELECT user_id from sys_user where dept_id = ? )", user.DeptId) +// } +// if role.DataScope == "4" { +// return db.Where(tableName+".create_by in (SELECT user_id from sys_user where sys_user.dept_id in(select dept_id from sys_dept where dept_path like ? ))", "%"+pkg.IntToString(user.DeptId)+"%") +// } +// if role.DataScope == "5" || role.DataScope == "" { +// return db.Where(tableName+".create_by = ?", userId) +// } +// return db +// } +//} diff --git a/app/admin/models/initdb.go b/app/admin/models/initdb.go new file mode 100644 index 0000000..cdec260 --- /dev/null +++ b/app/admin/models/initdb.go @@ -0,0 +1,55 @@ +package models + +import ( + "fmt" + "go-admin/common/global" + "gorm.io/gorm" + "io/ioutil" + "log" + "strings" +) + +func InitDb(db *gorm.DB) (err error) { + filePath := "config/db.sql" + err = ExecSql(db, filePath) + if global.Driver == "postgres" { + filePath = "config/pg.sql" + err = ExecSql(db, filePath) + } + return err +} + +func ExecSql(db *gorm.DB, filePath string) error { + sql, err := Ioutil(filePath) + if err != nil { + fmt.Println("数据库基础数据初始化脚本读取失败!原因:", err.Error()) + return err + } + sqlList := strings.Split(sql, ";") + for i := 0; i < len(sqlList)-1; i++ { + if strings.Contains(sqlList[i], "--") { + fmt.Println(sqlList[i]) + continue + } + sql := strings.Replace(sqlList[i]+";", "\n", "", -1) + sql = strings.TrimSpace(sql) + if err = db.Exec(sql).Error; err != nil { + log.Printf("error sql: %s", sql) + if !strings.Contains(err.Error(), "Query was empty") { + return err + } + } + } + return nil +} + +func Ioutil(filePath string) (string, error) { + if contents, err := ioutil.ReadFile(filePath); err == nil { + //因为contents是[]byte类型,直接转换成string类型后会多一行空格,需要使用strings.Replace替换换行符 + result := strings.Replace(string(contents), "\n", "", 1) + fmt.Println("Use ioutil.ReadFile to read a file:", result) + return result, nil + } else { + return "", err + } +} diff --git a/app/admin/models/line_account_setting.go b/app/admin/models/line_account_setting.go new file mode 100644 index 0000000..0de4b37 --- /dev/null +++ b/app/admin/models/line_account_setting.go @@ -0,0 +1,29 @@ +package models + +import ( + + "go-admin/common/models" + +) + +type LineAccountSetting struct { + models.Model + + UserName string `json:"userName" gorm:"type:varchar(255);comment:用户"` + Password string `json:"password" gorm:"type:varchar(255);comment:密码"` + models.ModelTime + models.ControlBy +} + +func (LineAccountSetting) TableName() string { + return "line_account_setting" +} + +func (e *LineAccountSetting) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineAccountSetting) GetId() interface{} { + return e.Id +} \ No newline at end of file diff --git a/app/admin/models/line_api_group.go b/app/admin/models/line_api_group.go new file mode 100644 index 0000000..8c105a2 --- /dev/null +++ b/app/admin/models/line_api_group.go @@ -0,0 +1,29 @@ +package models + +import ( + "go-admin/common/models" +) + +type LineApiGroup struct { + models.Model + + GroupName string `json:"groupName" gorm:"type:varchar(255);comment:用户组名称"` + ApiUserId string `json:"apiUserId" gorm:"type:varchar(255);comment:绑定的api账户id"` + AApiName string `json:"a_api_name" gorm:"-"` + BApiName string `json:"b_api_name" gorm:"-"` + models.ModelTime + models.ControlBy +} + +func (LineApiGroup) TableName() string { + return "line_api_group" +} + +func (e *LineApiGroup) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineApiGroup) GetId() interface{} { + return e.Id +} diff --git a/app/admin/models/line_api_user.go b/app/admin/models/line_api_user.go new file mode 100644 index 0000000..e8010d7 --- /dev/null +++ b/app/admin/models/line_api_user.go @@ -0,0 +1,42 @@ +package models + +import ( + "go-admin/common/models" +) + +type LineApiUser struct { + models.Model + + UserId int64 `json:"userId" gorm:"type:int unsigned;comment:用户id"` + JysId int64 `json:"jysId" gorm:"type:int;comment:关联交易所账号id"` + ApiName string `json:"apiName" gorm:"type:varchar(255);comment:api用户名"` + ApiKey string `json:"apiKey" gorm:"type:varchar(255);comment:apiKey"` + ApiSecret string `json:"apiSecret" gorm:"type:varchar(255);comment:apiSecret"` + IpAddress string `json:"ipAddress" gorm:"type:varchar(255);comment:代理地址"` + UserPass string `json:"userPass" gorm:"type:varchar(255);comment:代码账号密码"` + AdminId int64 `json:"adminId" gorm:"type:int unsigned;comment:管理员id"` + Affiliation int64 `json:"affiliation" gorm:"type:int;comment:归属:1=现货,2=合约,3=现货合约"` + AdminShow int64 `json:"adminShow" gorm:"type:int;comment:是否超管可见:1=是,0=否"` + Site string `json:"site" gorm:"type:enum('1','2','3');comment:允许下单的方向:1=多;2=空;3=多空"` + Subordinate string `json:"subordinate" gorm:"type:enum('0','1','2');comment:从属关系:0=未绑定关系;1=主账号;2=副帐号"` + GroupId int64 `json:"groupId" gorm:"type:int unsigned;comment:所属组id"` + OpenStatus int64 `json:"openStatus" gorm:"type:int unsigned;comment:开启状态 0=关闭 1=开启"` + + SpotLastTime string `json:"spotLastTime" gorm:"-"` //现货websocket最后通信时间 + FuturesLastTime string `json:"futuresLastTime" gorm:"-"` //合约websocket最后通信时间 + models.ModelTime + models.ControlBy +} + +func (LineApiUser) TableName() string { + return "line_api_user" +} + +func (e *LineApiUser) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineApiUser) GetId() interface{} { + return e.Id +} diff --git a/app/admin/models/line_coinnetwork.go b/app/admin/models/line_coinnetwork.go new file mode 100644 index 0000000..041425c --- /dev/null +++ b/app/admin/models/line_coinnetwork.go @@ -0,0 +1,41 @@ +package models + +import ( + "go-admin/common/models" +) + +type LineCoinnetwork struct { + models.Model + + NetworkName string `json:"networkName" gorm:"type:varchar(255);comment:网络名称"` + TokenName string `json:"tokenName" gorm:"type:varchar(255);comment:网络token名称"` + ArrivalNum int64 `json:"arrivalNum" gorm:"type:int;comment:充值区块确认数"` + UnlockNum int64 `json:"unlockNum" gorm:"type:int;comment:提现解锁确认数"` + UnlockTime int64 `json:"unlockTime" gorm:"type:int;comment:提现确认平均时间,单位分钟"` + Fee string `json:"fee" gorm:"type:decimal(32,6);comment:网络手续费,该字段是动态的,后面会有服务定时更新该字段"` + models.ModelTime + models.ControlBy +} + +func (LineCoinnetwork) TableName() string { + return "line_coinnetwork" +} + +func (e *LineCoinnetwork) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineCoinnetwork) GetId() interface{} { + return e.Id +} + +type VtsCoinNetWorkDB struct { + Id int `json:"id"` + NetworkName string `json:"network_name"` + TokenName string `json:"token_name"` + ArrivalNum int `json:"arrival_num"` + UnlockNum int `json:"unlock_num"` + UnlockTime int `json:"unlock_time"` + Fee float64 `json:"fee"` +} diff --git a/app/admin/models/line_cointonetwork.go b/app/admin/models/line_cointonetwork.go new file mode 100644 index 0000000..85e0f60 --- /dev/null +++ b/app/admin/models/line_cointonetwork.go @@ -0,0 +1,79 @@ +package models + +import ( + "time" + + "go-admin/common/models" +) + +type LineCointonetwork struct { + models.Model + + CoinId int64 `json:"coinId" gorm:"type:int unsigned;comment:币种id"` + NetworkId int64 `json:"networkId" gorm:"type:int unsigned;comment:公链网络id"` + IsMain int64 `json:"isMain" gorm:"type:int unsigned;comment:是否主网--1否,3是.比如BTC在BTC网络中就属于主网"` + IsDeposit int64 `json:"isDeposit" gorm:"type:int;comment:是否开启充值:1==否,3==是"` + IsWithdraw int64 `json:"isWithdraw" gorm:"type:int unsigned;comment:是否开启提现:1==否,3==是"` + CoinCode string `json:"coinCode" gorm:"type:varchar(255);comment:币种代号"` + Token string `json:"token" gorm:"type:varchar(255);comment:代币token"` + MinChargeNum string `json:"minChargeNum" gorm:"type:decimal(32,8);comment:最小充值数量"` + MinOutNum string `json:"minOutNum" gorm:"type:decimal(32,8);comment:单笔最小提币数量"` + MaxOutNum string `json:"maxOutNum" gorm:"type:decimal(32,8);comment:单笔最大提币数量"` + TransferFee string `json:"transferFee" gorm:"type:decimal(32,8);comment:提币手续费"` + DetailCode string `json:"detailCode" gorm:"type:varchar(255);comment:币种全称"` + NetworkName string `json:"networkName" gorm:"type:varchar(255);comment:公链网络简称"` + TokenName string `json:"tokenName" gorm:"type:varchar(255);comment:公链网络全称"` + ChargeType int64 `json:"chargeType" gorm:"type:int;comment:手续费类型 1==固定 3==百分比"` + RechargeSwitchTime time.Time `json:"rechargeSwitchTime" gorm:"type:timestamp(6);comment:充值开关时间"` + WithdrawSwitchTime time.Time `json:"withdrawSwitchTime" gorm:"type:timestamp(6);comment:提币开关时间"` + IsoutsideWithdrawVerify int64 `json:"isoutsideWithdrawVerify" gorm:"type:int;comment:是否开启外部提币免审1==否3==是"` + OutsideWithdrawVerifyNum string `json:"outsideWithdrawVerifyNum" gorm:"type:decimal(32,8);comment:外部提币免审阈值"` + IsinsideTransferVerify int64 `json:"isinsideTransferVerify" gorm:"type:int;comment:是否开启内部转账免审1==否3==是"` + InsidetransferVerifyNum string `json:"insidetransferVerifyNum" gorm:"type:decimal(32,8);comment:内部转账免审阈值"` + EverydaymaxWithdrawNum string `json:"everydaymaxWithdrawNum" gorm:"type:decimal(32,8);comment:每日最大累计提币数量"` + EverydaymaxVerifyNum string `json:"everydaymaxVerifyNum" gorm:"type:decimal(32,8);comment:每日最大免审累计数量"` + Isinsidetransferfee int64 `json:"isinsidetransferfee" gorm:"type:int;comment:是否开启内部转账免手续费1==否3==是"` + models.ModelTime + models.ControlBy +} + +func (LineCointonetwork) TableName() string { + return "line_cointonetwork" +} + +func (e *LineCointonetwork) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineCointonetwork) GetId() interface{} { + return e.Id +} + +type VtsCoinToNetWorkDB struct { + CoinId int `json:"coin_id"` + CoinCode string `json:"coin_code"` + DetailCode string `json:"detail_code"` + NetworkId int `json:"network_id"` + NetWorkName string `json:"network_name"` + TokenName string `json:"token_name"` + TransferFee float64 `json:"transfer_fee"` +} + +type VtsCoinToNetWorkResp struct { + CoinId int `json:"coin_id"` + CoinCode string `json:"coin_code"` + DetailCode string `json:"detail_code"` + NetWorkId int `json:"network_id"` + NetWorkName string `json:"network_name"` + TokenName string `json:"token_name"` + TransferFee string `json:"transfer_fee"` + TransferFeeUsdt string `json:"transfer_fee_usdt"` +} + +type RechargeAddressListResp struct { + Address string `json:"address"` //地址 + MinNum string `json:"min_num"` //最小充值数量 + ArrivalNum int `json:"arrival_num"` //充值区块确认数 + UnlockNum int `json:"unlock_num"` //提现解锁确认数 +} diff --git a/app/admin/models/line_direction.go b/app/admin/models/line_direction.go new file mode 100644 index 0000000..87496f4 --- /dev/null +++ b/app/admin/models/line_direction.go @@ -0,0 +1,37 @@ +package models + +import ( + + "go-admin/common/models" + +) + +type LineDirection struct { + models.Model + + Symbol string `json:"symbol" gorm:"type:varchar(255);comment:交易对"` + Type int64 `json:"type" gorm:"type:int unsigned;comment:交易对类型:1=现货,2=合约"` + BuyPoint1 string `json:"buyPoint1" gorm:"type:varchar(255);comment:买入点一"` + BuyPoint2 string `json:"buyPoint2" gorm:"type:varchar(255);comment:买入点二"` + BuyPoint3 string `json:"buyPoint3" gorm:"type:varchar(255);comment:买入点三"` + SellPoint1 string `json:"sellPoint1" gorm:"type:varchar(255);comment:卖出点一"` + SellPoint2 string `json:"sellPoint2" gorm:"type:varchar(255);comment:卖出点二"` + SellPoint3 string `json:"sellPoint3" gorm:"type:varchar(255);comment:卖出点三"` + Direction string `json:"direction" gorm:"type:varchar(255);comment:预估方向"` + AiAnswer string `json:"aiAnswer" gorm:"type:text;comment:AI智能分析"` + models.ModelTime + models.ControlBy +} + +func (LineDirection) TableName() string { + return "line_direction" +} + +func (e *LineDirection) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineDirection) GetId() interface{} { + return e.Id +} \ No newline at end of file diff --git a/app/admin/models/line_order_template_logs.go b/app/admin/models/line_order_template_logs.go new file mode 100644 index 0000000..23bbc11 --- /dev/null +++ b/app/admin/models/line_order_template_logs.go @@ -0,0 +1,32 @@ +package models + +import ( + + "go-admin/common/models" + +) + +type LineOrderTemplateLogs struct { + models.Model + + Name string `json:"name" gorm:"type:varchar(255);comment:模板名称"` + UserId int64 `json:"userId" gorm:"type:int;comment:用户id"` + Params string `json:"params" gorm:"type:text;comment:参数"` + Type int64 `json:"type" gorm:"type:int unsigned;comment:模板类型:1=单独添加;2=批量添加"` + Switch string `json:"switch" gorm:"type:enum('0','1');comment:开关:0=关,1=开"` + models.ModelTime + models.ControlBy +} + +func (LineOrderTemplateLogs) TableName() string { + return "line_order_template_logs" +} + +func (e *LineOrderTemplateLogs) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineOrderTemplateLogs) GetId() interface{} { + return e.Id +} \ No newline at end of file diff --git a/app/admin/models/line_pre_order.go b/app/admin/models/line_pre_order.go new file mode 100644 index 0000000..f6bd75a --- /dev/null +++ b/app/admin/models/line_pre_order.go @@ -0,0 +1,60 @@ +package models + +import ( + "go-admin/common/models" + "time" + + "github.com/shopspring/decimal" +) + +type LinePreOrder struct { + models.Model + ExchangeType string `json:"exchangeType" gorm:"type:varchar(20);comment:交易所类型 (字典 exchange_type)"` + Pid int `json:"pid" gorm:"type:int unsigned;omitempty;comment:pid"` + ApiId int `json:"apiId" gorm:"type:varchar(255);omitempty;comment:api用户"` + GroupId string `json:"groupId" gorm:"type:int unsigned;omitempty;comment:交易对组id"` + Symbol string `json:"symbol" gorm:"type:varchar(255);omitempty;comment:交易对"` + QuoteSymbol string `json:"quoteSymbol" gorm:"type:varchar(255);omitempty;comment:计较货币"` + SignPrice string `json:"signPrice" gorm:"type:decimal(18,8);omitempty;comment:对标价"` + SignPriceU decimal.Decimal `json:"signPriceU" gorm:"type:decimal(18,8);omitempty;comment:交易对对标U的行情价"` + SignPriceType string `json:"signPriceType" gorm:"type:enum('new','tall','low','mixture','entrust','add');omitempty;comment:对标价类型: new=最新价格;tall=24小时最高;low=24小时最低;mixture=标记价;entrust=委托实价;add=补仓"` + Rate string `json:"rate" gorm:"type:decimal(18,2);omitempty;comment:下单百分比"` + Price string `json:"price" gorm:"type:decimal(18,8);omitempty;comment:触发价格"` + Num string `json:"num" gorm:"type:decimal(18,8);omitempty;comment:购买数量"` + BuyPrice string `json:"buyPrice" gorm:"type:decimal(18,8);omitempty;comment:购买金额"` + SymbolType int `json:"symbolType" gorm:"type:int;comment:交易对类型:1=现货;2=合约"` + Site string `json:"site" gorm:"type:enum('BUY','SELL');omitempty;comment:购买方向:BUY=买;SELL=卖"` + OrderSn string `json:"orderSn" gorm:"type:varchar(255);omitempty;comment:订单号"` + OrderType int `json:"orderType" gorm:"type:int;omitempty;comment:订单类型:0=主单 1=止盈 2=止损 3=平仓"` + Desc string `json:"desc" gorm:"type:text;omitempty;comment:订单描述"` + Status int `json:"status" gorm:"type:int;omitempty;comment:是否被触发:0=待触发;1=已触发;2=下单失败;4=已取消;5=委托中;6=已成交;9=已平仓"` + AdminId string `json:"adminId" gorm:"type:int unsigned;omitempty;comment:操作管理员id"` + CloseType int `json:"closeType" gorm:"type:int unsigned;omitempty;comment:平仓类型 是否为盈利平仓 1= 是 0 =否"` + CoverType int `json:"coverType" gorm:"type:int unsigned;omitempty;comment:对冲类型 1= 现货对合约 2=合约对合约 3 合约对现货"` + ExpireTime time.Time `json:"expireTime" gorm:"comment:过期时间"` + MainOrderType string `json:"mainOrderType" gorm:"type:enum;comment:第一笔(主单类型) 限价(LIMIT)市价(MARKET)"` + HedgeOrderType string `json:"hedgeOrderType" gorm:"type:enum;comment:第二笔类型 限价(LIMIT)市价(MARKET)"` + Child []LinePreOrder `json:"child" gorm:"-"` + ApiName string `json:"api_name" gorm:"->"` + ChildNum int64 `json:"child_num" gorm:"->"` + // LinePreOrder 线上预埋单\ + models.ModelTime + models.ControlBy +} + +func (LinePreOrder) TableName() string { + return "line_pre_order" +} + +func (e *LinePreOrder) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LinePreOrder) GetId() interface{} { + return e.Id +} + +type PositionSymbol struct { + Symbol string +} diff --git a/app/admin/models/line_pre_order_status.go b/app/admin/models/line_pre_order_status.go new file mode 100644 index 0000000..8f47e64 --- /dev/null +++ b/app/admin/models/line_pre_order_status.go @@ -0,0 +1,29 @@ +package models + +import ( + "go-admin/common/models" +) + +type LinePreOrderStatus struct { + models.Model + + OrderId int `json:"orderId" gorm:"type:bigint;comment:主订单id"` + OrderSn string `json:"orderSn" gorm:"type:varchar(255);comment:主订单号"` + AddPositionStatus int `json:"addPositionStatus" gorm:"type:int;comment:加仓状态 0-无 1-已加仓"` + HedgeStatus int `json:"hedgeStatus" gorm:"type:int;comment:对冲状态 0-无 1-已对冲"` + models.ModelTime + models.ControlBy +} + +func (LinePreOrderStatus) TableName() string { + return "line_pre_order_status" +} + +func (e *LinePreOrderStatus) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LinePreOrderStatus) GetId() interface{} { + return e.Id +} diff --git a/app/admin/models/line_pre_script.go b/app/admin/models/line_pre_script.go new file mode 100644 index 0000000..125eef8 --- /dev/null +++ b/app/admin/models/line_pre_script.go @@ -0,0 +1,33 @@ +package models + +import ( + + "go-admin/common/models" + +) + +type LinePreScript struct { + models.Model + + ApiId int64 `json:"apiId" gorm:"type:int unsigned;comment:api用户"` + ScriptNum int64 `json:"scriptNum" gorm:"type:int unsigned;comment:脚本批次"` + ScriptParams string `json:"scriptParams" gorm:"type:text;comment:脚本参数"` + Status string `json:"status" gorm:"type:enum('0','1','2');comment:执行状态:0=等待执行,1=执行中,2=执行结束"` + Desc string `json:"desc" gorm:"type:longtext;comment:运行备注"` + AdminId int64 `json:"adminId" gorm:"type:int unsigned;comment:管理员id"` + models.ModelTime + models.ControlBy +} + +func (LinePreScript) TableName() string { + return "line_pre_script" +} + +func (e *LinePreScript) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LinePreScript) GetId() interface{} { + return e.Id +} \ No newline at end of file diff --git a/app/admin/models/line_price_limit.go b/app/admin/models/line_price_limit.go new file mode 100644 index 0000000..61a210f --- /dev/null +++ b/app/admin/models/line_price_limit.go @@ -0,0 +1,30 @@ +package models + +import ( + "github.com/shopspring/decimal" + "go-admin/common/models" +) + +type LinePriceLimit struct { + models.Model + + Symbol string `json:"symbol" gorm:"type:varchar(255);comment:交易对"` + Type string `json:"type" gorm:"type:enum('1','2');comment:类型:1=现货,2=合约"` + DirectionStatus string `json:"directionStatus" gorm:"type:enum('1','2');comment:方向:1=涨,2=跌"` + Range decimal.Decimal `json:"range" gorm:"type:decimal(10,5);comment:幅度"` + models.ModelTime + models.ControlBy +} + +func (LinePriceLimit) TableName() string { + return "line_price_limit" +} + +func (e *LinePriceLimit) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LinePriceLimit) GetId() interface{} { + return e.Id +} diff --git a/app/admin/models/line_recharge.go b/app/admin/models/line_recharge.go new file mode 100644 index 0000000..57cde30 --- /dev/null +++ b/app/admin/models/line_recharge.go @@ -0,0 +1,45 @@ +package models + +import ( + "time" + + "go-admin/common/models" + +) + +type LineRecharge struct { + models.Model + + CoinId int64 `json:"coinId" gorm:"type:int;comment:币种id"` + UserId int64 `json:"userId" gorm:"type:int;comment:用户Id"` + Confirms string `json:"confirms" gorm:"type:int;comment:区块确认数"` + TranType string `json:"tranType" gorm:"type:int;comment:类型:1线上,2内部"` + BlockIndex string `json:"blockIndex" gorm:"type:int;comment:区块高度"` + Amount string `json:"amount" gorm:"type:decimal(32,8);comment:数量"` + Account string `json:"account" gorm:"type:varchar(255);comment:账户"` + Address string `json:"address" gorm:"type:varchar(255);comment:地址"` + Txid string `json:"txid" gorm:"type:varchar(255);comment:交易id"` + BlockTime time.Time `json:"blockTime" gorm:"type:timestamp;comment:同步时间"` + TimeReceived time.Time `json:"timeReceived" gorm:"type:timestamp;comment:确认时间"` + MainCoin string `json:"mainCoin" gorm:"type:varchar(255);comment:充值网络"` + OrderNo string `json:"orderNo" gorm:"type:varchar(50);comment:订单号"` + Status string `json:"status" gorm:"type:int;comment:状态1==进行中暂时保留,2==成功,3==失败"` + State string `json:"state" gorm:"type:int;comment:来源状态 0 待審核 1 審核成功 2 審核駁回 3交易成功 4交易失敗"` + Fee string `json:"fee" gorm:"type:decimal(32,8);comment:手续费"` + AddressFrom string `json:"addressFrom" gorm:"type:varchar(255);comment:来源地址"` + models.ModelTime + models.ControlBy +} + +func (LineRecharge) TableName() string { + return "line_recharge" +} + +func (e *LineRecharge) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineRecharge) GetId() interface{} { + return e.Id +} \ No newline at end of file diff --git a/app/admin/models/line_symbol.go b/app/admin/models/line_symbol.go new file mode 100644 index 0000000..724e391 --- /dev/null +++ b/app/admin/models/line_symbol.go @@ -0,0 +1,32 @@ +package models + +import ( + "go-admin/common/models" +) + +type LineSymbol struct { + models.Model + + ApiId string `json:"apiId" gorm:"type:int;comment:api账户id"` + Symbol string `json:"symbol" gorm:"type:varchar(32);comment:交易对"` + BaseAsset string `json:"baseAsset" gorm:"type:varchar(255);comment:基础货币"` + QuoteAsset string `json:"quoteAsset" gorm:"type:varchar(255);comment:计价货币"` + Switch string `json:"switch" gorm:"type:enum('0','1');comment:状态"` + Type string `json:"type" gorm:"type:enum('1','2');comment:交易对类型"` + Number int `json:"number" gorm:"->"` + models.ModelTime + models.ControlBy +} + +func (LineSymbol) TableName() string { + return "line_symbol" +} + +func (e *LineSymbol) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineSymbol) GetId() interface{} { + return e.Id +} diff --git a/app/admin/models/line_symbol_black.go b/app/admin/models/line_symbol_black.go new file mode 100644 index 0000000..ccc3639 --- /dev/null +++ b/app/admin/models/line_symbol_black.go @@ -0,0 +1,29 @@ +package models + +import ( + + "go-admin/common/models" + +) + +type LineSymbolBlack struct { + models.Model + + Symbol string `json:"symbol" gorm:"type:varchar(255);comment:交易对"` + Type string `json:"type" gorm:"type:enum('1','2');comment:类型:1=现货,2=合约"` + models.ModelTime + models.ControlBy +} + +func (LineSymbolBlack) TableName() string { + return "line_symbol_black" +} + +func (e *LineSymbolBlack) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineSymbolBlack) GetId() interface{} { + return e.Id +} \ No newline at end of file diff --git a/app/admin/models/line_symbol_group.go b/app/admin/models/line_symbol_group.go new file mode 100644 index 0000000..cd795a9 --- /dev/null +++ b/app/admin/models/line_symbol_group.go @@ -0,0 +1,30 @@ +package models + +import ( + "go-admin/common/models" +) + +type LineSymbolGroup struct { + models.Model + + GroupName string `json:"groupName" gorm:"type:varchar(255);comment:交易对组名称"` + Symbol string `json:"symbol" gorm:"type:text;comment:交易对"` + GroupType string `json:"groupType" gorm:"type:enum('1','2');comment:分组类型:1=普通类型"` + Type string `json:"type" gorm:"type:enum('1','2');comment:类型:1=现货,2=合约"` + Count int `json:"count" gorm:"->"` + models.ModelTime + models.ControlBy +} + +func (LineSymbolGroup) TableName() string { + return "line_symbol_group" +} + +func (e *LineSymbolGroup) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineSymbolGroup) GetId() interface{} { + return e.Id +} diff --git a/app/admin/models/line_system_setting.go b/app/admin/models/line_system_setting.go new file mode 100644 index 0000000..71133e2 --- /dev/null +++ b/app/admin/models/line_system_setting.go @@ -0,0 +1,41 @@ +package models + +import ( + "go-admin/common/models" + + "github.com/shopspring/decimal" +) + +type LineSystemSetting struct { + models.Model + + Time int64 `json:"time" gorm:"type:int;comment:导入:挂单时长达到时间后失效"` + BatchTime int64 `json:"batchTime" gorm:"type:int;comment:批量:挂单时长达到时间后失效"` + ProfitRate string `json:"profitRate" gorm:"type:decimal(10,2);comment:平仓盈利比例"` + CoverOrderTypeBRate string `json:"coverOrderTypeBRate" gorm:"type:decimal(10,2);comment:b账户限价补单的买入百分比"` + ScaleOrderTypeARate string `json:"scaleOrderTypeARate" gorm:"type:decimal(10,2);comment:a账户限价加仓买入百分比"` + ScaleOrderTypeBRate string `json:"scaleOrderTypeBRate" gorm:"type:decimal(10,2);comment:b账户限价加仓买入百分比"` + ScaleUnrealizedProfitRate string `json:"scaleUnrealizedProfitRate" gorm:"type:decimal(10,5) unsigned;comment:亏损百分比加仓"` + ScaleType int `json:"scaleType" gorm:"type:int;comment:加仓类型 1-百分比 2-数值"` + ScaleNum string `json:"scaleNum" gorm:"type:decimal(18,2) unsigned;comment:加仓数值"` + ScaleSubordinate int64 `json:"scaleSubordinate" gorm:"type:int unsigned;comment:加仓账户:1=A账户;2=副账户;3=都加"` + AutoScaleTimes int64 `json:"autoScaleTimes" gorm:"type:int unsigned;comment:自动加仓次数"` + HedgePerformance decimal.Decimal `json:"hedgePerformance" gorm:"type:decimal(5,2);comment:对冲平仓涨跌幅"` + ProtectHedgeRate decimal.Decimal `json:"protectHedgeRate" gorm:"type:decimal(5,2);comment:保护对冲触发百分比"` + ProtectHedgeEnable int `json:"protectHedgeEnable" gorm:"type:int;comment:是否只开启保护对冲 1-开启 0-关闭"` + models.ModelTime + models.ControlBy +} + +func (LineSystemSetting) TableName() string { + return "line_system_setting" +} + +func (e *LineSystemSetting) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineSystemSetting) GetId() interface{} { + return e.Id +} diff --git a/app/admin/models/line_uduncoin.go b/app/admin/models/line_uduncoin.go new file mode 100644 index 0000000..8b1d951 --- /dev/null +++ b/app/admin/models/line_uduncoin.go @@ -0,0 +1,36 @@ +package models + +import ( + + "go-admin/common/models" + +) + +type LineUduncoin struct { + models.Model + + TokenStatus int64 `json:"tokenStatus" gorm:"type:int;comment:0: 主幣 1:代幣"` + Decimals int64 `json:"decimals" gorm:"type:int;comment:幣種精度,8"` + MainCoinType string `json:"mainCoinType" gorm:"type:varchar(255);comment:主幣種類型"` + CoinType string `json:"coinType" gorm:"type:varchar(255);comment:幣種類型"` + Symbol string `json:"symbol" gorm:"type:varchar(255);comment:幣種symbol"` + Name string `json:"name" gorm:"type:varchar(255);comment:幣種別名,BTC"` + Logo string `json:"logo" gorm:"type:varchar(255);comment:幣種logo地址"` + CoinName string `json:"coinName" gorm:"type:varchar(255);comment:幣種全稱,Bitcoin"` + MainSymbol string `json:"mainSymbol" gorm:"type:varchar(255);comment:主幣種symbol"` + models.ModelTime + models.ControlBy +} + +func (LineUduncoin) TableName() string { + return "line_uduncoin" +} + +func (e *LineUduncoin) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineUduncoin) GetId() interface{} { + return e.Id +} \ No newline at end of file diff --git a/app/admin/models/line_user.go b/app/admin/models/line_user.go new file mode 100644 index 0000000..426b3eb --- /dev/null +++ b/app/admin/models/line_user.go @@ -0,0 +1,56 @@ +package models + +import ( + "github.com/shopspring/decimal" + "time" + + "go-admin/common/models" +) + +type LineUser struct { + models.Model + + GroupId int `json:"groupId" gorm:"type:int unsigned;comment:组别ID"` + Pid int `json:"pid" gorm:"type:int unsigned;comment:推荐人ID"` + Username string `json:"username" gorm:"type:varchar(32);comment:用户名"` + Nickname string `json:"nickname" gorm:"type:varchar(50);comment:昵称"` + Password string `json:"password" gorm:"type:varchar(32);comment:密码"` + Salt string `json:"salt" gorm:"type:varchar(30);comment:密码盐"` + Email string `json:"email" gorm:"type:varchar(100);comment:电子邮箱"` + Mobile string `json:"mobile" gorm:"type:varchar(11);comment:手机号"` + Area string `json:"area" gorm:"type:varchar(255);comment:手机号归属地"` + Avatar string `json:"avatar" gorm:"type:varchar(255);comment:头像"` + Level int `json:"level" gorm:"type:tinyint unsigned;comment:等级"` + Gender int `json:"gender" gorm:"type:tinyint unsigned;comment:性别"` + Bio string `json:"bio" gorm:"type:varchar(100);comment:格言"` + Money decimal.Decimal `json:"money" gorm:"type:decimal(10,2) unsigned;comment:保证金"` + Score int `json:"score" gorm:"type:int unsigned;comment:积分"` + InviteCode string `json:"invite_code" gorm:"type:varchar(255);comment:邀请码"` + Successions int `json:"successions" gorm:"type:int unsigned;comment:连续登录天数"` + MaxSuccessions int `json:"maxSuccessions" gorm:"type:int unsigned;comment:最大连续登录天数"` + Loginip string `json:"loginip" gorm:"type:varchar(50);comment:登录IP"` + Loginfailure int `json:"loginfailure" gorm:"type:tinyint unsigned;comment:失败次数"` + Joinip string `json:"joinip" gorm:"type:varchar(50);comment:加入IP"` + Jointime int `json:"jointime" gorm:"type:int;comment:加入时间"` + RecommendNum int `json:"recommend_num" gorm:"type:int;comment:推荐人数"` + Token string `json:"token" gorm:"type:varchar(50);comment:Token"` + Status string `json:"status" gorm:"type:varchar(30);comment:状态"` + Verification string `json:"verification" gorm:"type:varchar(255);comment:验证"` + LoginTime time.Time `json:"loginTime" gorm:"type:timestamp;comment:登录时间"` + OpenStatus int `json:"open_status" gorm:"-"` + models.ModelTime + models.ControlBy +} + +func (LineUser) TableName() string { + return "line_user" +} + +func (e *LineUser) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineUser) GetId() interface{} { + return e.Id +} diff --git a/app/admin/models/line_user_funding_trend.go b/app/admin/models/line_user_funding_trend.go new file mode 100644 index 0000000..e14f61f --- /dev/null +++ b/app/admin/models/line_user_funding_trend.go @@ -0,0 +1,33 @@ +package models + +import ( + "go-admin/common/models" +) + +type LineUserFundingTrend struct { + models.Model + + UserId int64 `json:"userId" gorm:"type:int unsigned;comment:用户id"` + Funding string `json:"funding" gorm:"type:decimal(32,8) unsigned;comment:资金账户总额 换算U"` + models.ModelTime + models.ControlBy +} + +func (LineUserFundingTrend) TableName() string { + return "line_user_funding_trend" +} + +func (e *LineUserFundingTrend) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineUserFundingTrend) GetId() interface{} { + return e.Id +} + +type LineUserFundingTrendResp struct { + UserId int64 `json:"user_id" gorm:"type:int unsigned;comment:用户id"` + Funding string `json:"funding" gorm:"type:decimal(32,8) unsigned;comment:资金账户总额"` + CreatedAt string `json:"created_at"` +} diff --git a/app/admin/models/line_user_profit_logs.go b/app/admin/models/line_user_profit_logs.go new file mode 100644 index 0000000..b1e8d4b --- /dev/null +++ b/app/admin/models/line_user_profit_logs.go @@ -0,0 +1,32 @@ +package models + +import ( + "go-admin/common/models" +) + +type LineUserProfitLogs struct { + models.Model + + UserId int64 `json:"userId" gorm:"type:int unsigned;comment:line_user 表的id"` + ApiId int64 `json:"apiId" gorm:"type:int unsigned;comment:line_apiuser 表的id"` + PreOrderId int64 `json:"preOrderId" gorm:"type:int unsigned;comment:line_pre_order 止盈单id"` + Num string `json:"num" gorm:"type:decimal(18,8);comment:成交数量"` + Symbol string `json:"symbol" gorm:"type:varchar(255);comment:交易对"` + Rate string `json:"rate" gorm:"type:decimal(10,5);comment:盈利比例"` + Amount string `json:"amount" gorm:"type:decimal(18,5);comment:盈利金额(换算成U的价值)"` + models.ModelTime + models.ControlBy +} + +func (LineUserProfitLogs) TableName() string { + return "line_user_profit_logs" +} + +func (e *LineUserProfitLogs) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineUserProfitLogs) GetId() interface{} { + return e.Id +} diff --git a/app/admin/models/line_wallet.go b/app/admin/models/line_wallet.go new file mode 100644 index 0000000..3245d61 --- /dev/null +++ b/app/admin/models/line_wallet.go @@ -0,0 +1,31 @@ +package models + +import ( + "go-admin/common/models" +) + +type LineWallet struct { + models.Model + + UserId int64 `json:"userId" gorm:"type:int;comment:用户Id"` + CoinId int64 `json:"coinId" gorm:"type:int;comment:币种id"` + CoinCode string `json:"coinCode" gorm:"type:varchar(255);comment:标签"` + Tag string `json:"tag" gorm:"type:varchar(255);comment:标签"` + Address string `json:"address" gorm:"type:varchar(255);comment:地址"` + CoinNetworkId int `json:"coinNetworkId" gorm:"type:int;comment:币种主网id,useri+主网id做唯一"` + models.ModelTime + models.ControlBy +} + +func (LineWallet) TableName() string { + return "line_wallet" +} + +func (e *LineWallet) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *LineWallet) GetId() interface{} { + return e.Id +} diff --git a/app/admin/models/sys_api.go b/app/admin/models/sys_api.go new file mode 100644 index 0000000..0b78e49 --- /dev/null +++ b/app/admin/models/sys_api.go @@ -0,0 +1,91 @@ +package models + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "regexp" + "strings" + + "github.com/bitly/go-simplejson" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/sdk/runtime" + "github.com/go-admin-team/go-admin-core/storage" + + "go-admin/common/models" +) + +type SysApi struct { + Id int `json:"id" gorm:"primaryKey;autoIncrement;comment:主键编码"` + Handle string `json:"handle" gorm:"size:128;comment:handle"` + Title string `json:"title" gorm:"size:128;comment:标题"` + Path string `json:"path" gorm:"size:128;comment:地址"` + Action string `json:"action" gorm:"size:16;comment:请求类型"` + Type string `json:"type" gorm:"size:16;comment:接口类型"` + models.ModelTime + models.ControlBy +} + +func (*SysApi) TableName() string { + return "sys_api" +} + +func (e *SysApi) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysApi) GetId() interface{} { + return e.Id +} + +func SaveSysApi(message storage.Messager) (err error) { + var rb []byte + rb, err = json.Marshal(message.GetValues()) + if err != nil { + err = fmt.Errorf("json Marshal error, %v", err.Error()) + return err + } + + var l runtime.Routers + err = json.Unmarshal(rb, &l) + if err != nil { + err = fmt.Errorf("json Unmarshal error, %s", err.Error()) + return err + } + dbList := sdk.Runtime.GetDb() + for _, d := range dbList { + for _, v := range l.List { + if v.HttpMethod != "HEAD" || + strings.Contains(v.RelativePath, "/swagger/") || + strings.Contains(v.RelativePath, "/static/") || + strings.Contains(v.RelativePath, "/form-generator/") || + strings.Contains(v.RelativePath, "/sys/tables") { + + // 根据接口方法注释里的@Summary填充接口名称,适用于代码生成器 + // 可在此处增加配置路径前缀的if判断,只对代码生成的自建应用进行定向的接口名称填充 + jsonFile, _ := ioutil.ReadFile("docs/swagger.json") + jsonData, _ := simplejson.NewFromReader(bytes.NewReader(jsonFile)) + urlPath := v.RelativePath + idPatten := "(.*)/:(\\w+)" // 正则替换,把:id换成{id} + reg, _ := regexp.Compile(idPatten) + if reg.MatchString(urlPath) { + urlPath = reg.ReplaceAllString(v.RelativePath, "${1}/{${2}}") // 把:id换成{id} + } + apiTitle, _ := jsonData.Get("paths").Get(urlPath).Get(strings.ToLower(v.HttpMethod)).Get("summary").String() + + err := d.Debug().Where(SysApi{Path: v.RelativePath, Action: v.HttpMethod}). + Attrs(SysApi{Handle: v.Handler, Title: apiTitle}). + FirstOrCreate(&SysApi{}). + //Update("handle", v.Handler). + Error + if err != nil { + err := fmt.Errorf("Models SaveSysApi error: %s \r\n ", err.Error()) + return err + } + } + } + } + return nil +} diff --git a/app/admin/models/sys_config.go b/app/admin/models/sys_config.go new file mode 100644 index 0000000..2024bbb --- /dev/null +++ b/app/admin/models/sys_config.go @@ -0,0 +1,30 @@ +package models + +import ( + "go-admin/common/models" +) + +type SysConfig struct { + models.Model + ConfigName string `json:"configName" gorm:"size:128;comment:ConfigName"` // + ConfigKey string `json:"configKey" gorm:"size:128;comment:ConfigKey"` // + ConfigValue string `json:"configValue" gorm:"size:255;comment:ConfigValue"` // + ConfigType string `json:"configType" gorm:"size:64;comment:ConfigType"` + IsFrontend string `json:"isFrontend" gorm:"size:64;comment:是否前台"` // + Remark string `json:"remark" gorm:"size:128;comment:Remark"` // + models.ControlBy + models.ModelTime +} + +func (*SysConfig) TableName() string { + return "sys_config" +} + +func (e *SysConfig) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysConfig) GetId() interface{} { + return e.Id +} diff --git a/app/admin/models/sys_dept.go b/app/admin/models/sys_dept.go new file mode 100644 index 0000000..e4400de --- /dev/null +++ b/app/admin/models/sys_dept.go @@ -0,0 +1,33 @@ +package models + +import "go-admin/common/models" + +type SysDept struct { + DeptId int `json:"deptId" gorm:"primaryKey;autoIncrement;"` //部门编码 + ParentId int `json:"parentId" gorm:""` //上级部门 + DeptPath string `json:"deptPath" gorm:"size:255;"` // + DeptName string `json:"deptName" gorm:"size:128;"` //部门名称 + Sort int `json:"sort" gorm:"size:4;"` //排序 + Leader string `json:"leader" gorm:"size:128;"` //负责人 + Phone string `json:"phone" gorm:"size:11;"` //手机 + Email string `json:"email" gorm:"size:64;"` //邮箱 + Status int `json:"status" gorm:"size:4;"` //状态 + models.ControlBy + models.ModelTime + DataScope string `json:"dataScope" gorm:"-"` + Params string `json:"params" gorm:"-"` + Children []SysDept `json:"children" gorm:"-"` +} + +func (*SysDept) TableName() string { + return "sys_dept" +} + +func (e *SysDept) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysDept) GetId() interface{} { + return e.DeptId +} diff --git a/app/admin/models/sys_dict_data.go b/app/admin/models/sys_dict_data.go new file mode 100644 index 0000000..15abf89 --- /dev/null +++ b/app/admin/models/sys_dict_data.go @@ -0,0 +1,34 @@ +package models + +import ( + "go-admin/common/models" +) + +type SysDictData struct { + DictCode int `json:"dictCode" gorm:"primaryKey;column:dict_code;autoIncrement;comment:主键编码"` + DictSort int `json:"dictSort" gorm:"size:20;comment:DictSort"` + DictLabel string `json:"dictLabel" gorm:"size:128;comment:DictLabel"` + DictValue string `json:"dictValue" gorm:"size:255;comment:DictValue"` + DictType string `json:"dictType" gorm:"size:64;comment:DictType"` + CssClass string `json:"cssClass" gorm:"size:128;comment:CssClass"` + ListClass string `json:"listClass" gorm:"size:128;comment:ListClass"` + IsDefault string `json:"isDefault" gorm:"size:8;comment:IsDefault"` + Status int `json:"status" gorm:"size:4;comment:Status"` + Default string `json:"default" gorm:"size:8;comment:Default"` + Remark string `json:"remark" gorm:"size:255;comment:Remark"` + models.ControlBy + models.ModelTime +} + +func (*SysDictData) TableName() string { + return "sys_dict_data" +} + +func (e *SysDictData) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysDictData) GetId() interface{} { + return e.DictCode +} diff --git a/app/admin/models/sys_dict_type.go b/app/admin/models/sys_dict_type.go new file mode 100644 index 0000000..49f7ebe --- /dev/null +++ b/app/admin/models/sys_dict_type.go @@ -0,0 +1,28 @@ +package models + +import ( + "go-admin/common/models" +) + +type SysDictType struct { + ID int `json:"id" gorm:"primaryKey;column:dict_id;autoIncrement;comment:主键编码"` + DictName string `json:"dictName" gorm:"size:128;comment:DictName"` + DictType string `json:"dictType" gorm:"size:128;comment:DictType"` + Status int `json:"status" gorm:"size:4;comment:Status"` + Remark string `json:"remark" gorm:"size:255;comment:Remark"` + models.ControlBy + models.ModelTime +} + +func (*SysDictType) TableName() string { + return "sys_dict_type" +} + +func (e *SysDictType) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysDictType) GetId() interface{} { + return e.ID +} diff --git a/app/admin/models/sys_login_log.go b/app/admin/models/sys_login_log.go new file mode 100644 index 0000000..98873e8 --- /dev/null +++ b/app/admin/models/sys_login_log.go @@ -0,0 +1,72 @@ +package models + +import ( + "encoding/json" + "errors" + "time" + + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/storage" + + "go-admin/common/models" +) + +type SysLoginLog struct { + models.Model + Username string `json:"username" gorm:"size:128;comment:用户名"` + Status string `json:"status" gorm:"size:4;comment:状态"` + Ipaddr string `json:"ipaddr" gorm:"size:255;comment:ip地址"` + LoginLocation string `json:"loginLocation" gorm:"size:255;comment:归属地"` + Browser string `json:"browser" gorm:"size:255;comment:浏览器"` + Os string `json:"os" gorm:"size:255;comment:系统"` + Platform string `json:"platform" gorm:"size:255;comment:固件"` + LoginTime time.Time `json:"loginTime" gorm:"comment:登录时间"` + Remark string `json:"remark" gorm:"size:255;comment:备注"` + Msg string `json:"msg" gorm:"size:255;comment:信息"` + CreatedAt time.Time `json:"createdAt" gorm:"comment:创建时间"` + UpdatedAt time.Time `json:"updatedAt" gorm:"comment:最后更新时间"` + models.ControlBy +} + +func (*SysLoginLog) TableName() string { + return "sys_login_log" +} + +func (e *SysLoginLog) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysLoginLog) GetId() interface{} { + return e.Id +} + +// SaveLoginLog 从队列中获取登录日志 +func SaveLoginLog(message storage.Messager) (err error) { + //准备db + db := sdk.Runtime.GetDbByKey(message.GetPrefix()) + if db == nil { + err = errors.New("db not exist") + log.Errorf("host[%s]'s %s", message.GetPrefix(), err.Error()) + return err + } + var rb []byte + rb, err = json.Marshal(message.GetValues()) + if err != nil { + log.Errorf("json Marshal error, %s", err.Error()) + return err + } + var l SysLoginLog + err = json.Unmarshal(rb, &l) + if err != nil { + log.Errorf("json Unmarshal error, %s", err.Error()) + return err + } + err = db.Create(&l).Error + if err != nil { + log.Errorf("db create error, %s", err.Error()) + return err + } + return nil +} diff --git a/app/admin/models/sys_menu.go b/app/admin/models/sys_menu.go new file mode 100644 index 0000000..ea7e669 --- /dev/null +++ b/app/admin/models/sys_menu.go @@ -0,0 +1,50 @@ +package models + +import "go-admin/common/models" + +type SysMenu struct { + MenuId int `json:"menuId" gorm:"primaryKey;autoIncrement"` + MenuName string `json:"menuName" gorm:"size:128;"` + Title string `json:"title" gorm:"size:128;"` + Icon string `json:"icon" gorm:"size:128;"` + Path string `json:"path" gorm:"size:128;"` + Paths string `json:"paths" gorm:"size:128;"` + MenuType string `json:"menuType" gorm:"size:1;"` + Action string `json:"action" gorm:"size:16;"` + Permission string `json:"permission" gorm:"size:255;"` + ParentId int `json:"parentId" gorm:"size:11;"` + NoCache bool `json:"noCache" gorm:"size:8;"` + Breadcrumb string `json:"breadcrumb" gorm:"size:255;"` + Component string `json:"component" gorm:"size:255;"` + Sort int `json:"sort" gorm:"size:4;"` + Visible string `json:"visible" gorm:"size:1;"` + IsFrame string `json:"isFrame" gorm:"size:1;DEFAULT:0;"` + SysApi []SysApi `json:"sysApi" gorm:"many2many:sys_menu_api_rule"` + Apis []int `json:"apis" gorm:"-"` + DataScope string `json:"dataScope" gorm:"-"` + Params string `json:"params" gorm:"-"` + RoleId int `gorm:"-"` + Children []SysMenu `json:"children,omitempty" gorm:"-"` + IsSelect bool `json:"is_select" gorm:"-"` + models.ControlBy + models.ModelTime +} + +type SysMenuSlice []SysMenu + +func (x SysMenuSlice) Len() int { return len(x) } +func (x SysMenuSlice) Less(i, j int) bool { return x[i].Sort < x[j].Sort } +func (x SysMenuSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (*SysMenu) TableName() string { + return "sys_menu" +} + +func (e *SysMenu) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysMenu) GetId() interface{} { + return e.MenuId +} diff --git a/app/admin/models/sys_opera_log.go b/app/admin/models/sys_opera_log.go new file mode 100644 index 0000000..e29a54d --- /dev/null +++ b/app/admin/models/sys_opera_log.go @@ -0,0 +1,88 @@ +package models + +import ( + "encoding/json" + "errors" + "time" + + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/storage" + + "go-admin/common/models" +) + +type SysOperaLog struct { + models.Model + Title string `json:"title" gorm:"size:255;comment:操作模块"` + BusinessType string `json:"businessType" gorm:"size:128;comment:操作类型"` + BusinessTypes string `json:"businessTypes" gorm:"size:128;comment:BusinessTypes"` + Method string `json:"method" gorm:"size:128;comment:函数"` + RequestMethod string `json:"requestMethod" gorm:"size:128;comment:请求方式 GET POST PUT DELETE"` + OperatorType string `json:"operatorType" gorm:"size:128;comment:操作类型"` + OperName string `json:"operName" gorm:"size:128;comment:操作者"` + DeptName string `json:"deptName" gorm:"size:128;comment:部门名称"` + OperUrl string `json:"operUrl" gorm:"size:255;comment:访问地址"` + OperIp string `json:"operIp" gorm:"size:128;comment:客户端ip"` + OperLocation string `json:"operLocation" gorm:"size:128;comment:访问位置"` + OperParam string `json:"operParam" gorm:"text;comment:请求参数"` + Status string `json:"status" gorm:"size:4;comment:操作状态 1:正常 2:关闭"` + OperTime time.Time `json:"operTime" gorm:"comment:操作时间"` + JsonResult string `json:"jsonResult" gorm:"size:255;comment:返回数据"` + Remark string `json:"remark" gorm:"size:255;comment:备注"` + LatencyTime string `json:"latencyTime" gorm:"size:128;comment:耗时"` + UserAgent string `json:"userAgent" gorm:"size:255;comment:ua"` + CreatedAt time.Time `json:"createdAt" gorm:"comment:创建时间"` + UpdatedAt time.Time `json:"updatedAt" gorm:"comment:最后更新时间"` + models.ControlBy +} + +func (*SysOperaLog) TableName() string { + return "sys_opera_log" +} + +func (e *SysOperaLog) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysOperaLog) GetId() interface{} { + return e.Id +} + +// SaveOperaLog 从队列中获取操作日志 +func SaveOperaLog(message storage.Messager) (err error) { + //准备db + db := sdk.Runtime.GetDbByKey(message.GetPrefix()) + if db == nil { + err = errors.New("db not exist") + log.Errorf("host[%s]'s %s", message.GetPrefix(), err.Error()) + // Log writing to the database ignores error + return nil + } + var rb []byte + rb, err = json.Marshal(message.GetValues()) + if err != nil { + log.Errorf("json Marshal error, %s", err.Error()) + // Log writing to the database ignores error + return nil + } + var l SysOperaLog + err = json.Unmarshal(rb, &l) + if err != nil { + log.Errorf("json Unmarshal error, %s", err.Error()) + // Log writing to the database ignores error + return nil + } + // 超出100个字符返回值截断 + if len(l.JsonResult) > 100 { + l.JsonResult = l.JsonResult[:100] + } + err = db.Create(&l).Error + if err != nil { + log.Errorf("db create error, %s", err.Error()) + // Log writing to the database ignores error + return nil + } + return nil +} diff --git a/app/admin/models/sys_post.go b/app/admin/models/sys_post.go new file mode 100644 index 0000000..705318a --- /dev/null +++ b/app/admin/models/sys_post.go @@ -0,0 +1,30 @@ +package models + +import "go-admin/common/models" + +type SysPost struct { + PostId int `gorm:"primaryKey;autoIncrement" json:"postId"` //岗位编号 + PostName string `gorm:"size:128;" json:"postName"` //岗位名称 + PostCode string `gorm:"size:128;" json:"postCode"` //岗位代码 + Sort int `gorm:"size:4;" json:"sort"` //岗位排序 + Status int `gorm:"size:4;" json:"status"` //状态 + Remark string `gorm:"size:255;" json:"remark"` //描述 + models.ControlBy + models.ModelTime + + DataScope string `gorm:"-" json:"dataScope"` + Params string `gorm:"-" json:"params"` +} + +func (*SysPost) TableName() string { + return "sys_post" +} + +func (e *SysPost) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysPost) GetId() interface{} { + return e.PostId +} diff --git a/app/admin/models/sys_role.go b/app/admin/models/sys_role.go new file mode 100644 index 0000000..d23fb17 --- /dev/null +++ b/app/admin/models/sys_role.go @@ -0,0 +1,35 @@ +package models + +import "go-admin/common/models" + +type SysRole struct { + RoleId int `json:"roleId" gorm:"primaryKey;autoIncrement"` // 角色编码 + RoleName string `json:"roleName" gorm:"size:128;"` // 角色名称 + Status string `json:"status" gorm:"size:4;"` // 状态 1禁用 2正常 + RoleKey string `json:"roleKey" gorm:"size:128;"` //角色代码 + RoleSort int `json:"roleSort" gorm:""` //角色排序 + Flag string `json:"flag" gorm:"size:128;"` // + Remark string `json:"remark" gorm:"size:255;"` //备注 + Admin bool `json:"admin" gorm:"size:4;"` + DataScope string `json:"dataScope" gorm:"size:128;"` + Params string `json:"params" gorm:"-"` + MenuIds []int `json:"menuIds" gorm:"-"` + DeptIds []int `json:"deptIds" gorm:"-"` + SysDept []SysDept `json:"sysDept" gorm:"many2many:sys_role_dept;foreignKey:RoleId;joinForeignKey:role_id;references:DeptId;joinReferences:dept_id;"` + SysMenu *[]SysMenu `json:"sysMenu" gorm:"many2many:sys_role_menu;foreignKey:RoleId;joinForeignKey:role_id;references:MenuId;joinReferences:menu_id;"` + models.ControlBy + models.ModelTime +} + +func (*SysRole) TableName() string { + return "sys_role" +} + +func (e *SysRole) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysRole) GetId() interface{} { + return e.RoleId +} diff --git a/app/admin/models/sys_user.go b/app/admin/models/sys_user.go new file mode 100644 index 0000000..eab2cad --- /dev/null +++ b/app/admin/models/sys_user.go @@ -0,0 +1,77 @@ +package models + +import ( + "go-admin/common/models" + "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" +) + +type SysUser struct { + UserId int `gorm:"primaryKey;autoIncrement;comment:编码" json:"userId"` + Username string `json:"username" gorm:"size:64;comment:用户名"` + Password string `json:"-" gorm:"size:128;comment:密码"` + NickName string `json:"nickName" gorm:"size:128;comment:昵称"` + Phone string `json:"phone" gorm:"size:11;comment:手机号"` + RoleId int `json:"roleId" gorm:"size:20;comment:角色ID"` + Salt string `json:"-" gorm:"size:255;comment:加盐"` + Avatar string `json:"avatar" gorm:"size:255;comment:头像"` + Sex string `json:"sex" gorm:"size:255;comment:性别"` + Email string `json:"email" gorm:"size:128;comment:邮箱"` + DeptId int `json:"deptId" gorm:"size:20;comment:部门"` + PostId int `json:"postId" gorm:"size:20;comment:岗位"` + Remark string `json:"remark" gorm:"size:255;comment:备注"` + Status string `json:"status" gorm:"size:4;comment:状态"` + DeptIds []int `json:"deptIds" gorm:"-"` + PostIds []int `json:"postIds" gorm:"-"` + RoleIds []int `json:"roleIds" gorm:"-"` + Dept *SysDept `json:"dept"` + models.ControlBy + models.ModelTime +} + +func (*SysUser) TableName() string { + return "sys_user" +} + +func (e *SysUser) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysUser) GetId() interface{} { + return e.UserId +} + +// Encrypt 加密 +func (e *SysUser) Encrypt() (err error) { + if e.Password == "" { + return + } + + var hash []byte + if hash, err = bcrypt.GenerateFromPassword([]byte(e.Password), bcrypt.DefaultCost); err != nil { + return + } else { + e.Password = string(hash) + return + } +} + +func (e *SysUser) BeforeCreate(_ *gorm.DB) error { + return e.Encrypt() +} + +func (e *SysUser) BeforeUpdate(_ *gorm.DB) error { + var err error + if e.Password != "" { + err = e.Encrypt() + } + return err +} + +func (e *SysUser) AfterFind(_ *gorm.DB) error { + e.DeptIds = []int{e.DeptId} + e.PostIds = []int{e.PostId} + e.RoleIds = []int{e.RoleId} + return nil +} diff --git a/app/admin/models/sysmodel/article.go b/app/admin/models/sysmodel/article.go new file mode 100644 index 0000000..7913f8b --- /dev/null +++ b/app/admin/models/sysmodel/article.go @@ -0,0 +1,72 @@ +package sysmodel + +import "time" + +// 文章中心 +type GetArticleListReq struct { + TypeID int `json:"category_id" query:"category_id"` // 类别 ID + Language string `json:"-"` // +} + +type ArticleCenterList struct { + Type int `json:"type"` // 类型:1 文章类别,2 文章 + ID int `json:"id"` + ParentID int `json:"parent_id"` + Icon string `json:"icon"` + Title string `json:"title"` + JumpRoute string `json:"jump_route"` // 自助服务的跳转路由 +} + +type GetArticleCenterResp struct { + Type int `json:"type"` // 类型:1 文章类别,2 文章 + ID int `json:"id"` + Icon string `json:"icon"` + Title string `json:"title"` + JumpRoute string `json:"jump_route"` + Child []GetArticleCenterResp `json:"child"` +} + +// 文章类别 +type GetArticleCategoryResp struct { + ID int `json:"id"` + ParentID int `json:"parent_id"` + Title string `json:"title"` + Child []GetArticleCategoryResp `json:"child"` +} + +// 文章列表 +type GetArticleListResp struct { + ID int `json:"id"` + CategoryID int `json:"category_id"` + Title string `json:"title"` + Content string `json:"content"` + CreateTime int64 `json:"create_time"` +} + +// 文章详情 +type GetArticleDetailResp struct { + ID int `json:"id"` + Title string `json:"title"` + Content string `json:"content"` + CreateTime time.Time `json:"-"` // + UpdateTime time.Time `json:"-"` // + CreateTimeResp int64 `json:"create_time"` +} + +// 文章搜索 +type GetArticleSearchList struct { + ID int `json:"id"` + TypeID int `json:"category_id"` + Path string `json:"path"` + Title string `json:"title"` + Content string `json:"content"` + CreateTime time.Time `json:"-"` + CreateTimeResp int64 `json:"create_time"` + AllParent []CategoryList `json:"all_parent"` +} + +type CategoryList struct { + ID int `json:"id"` + ParentID int `json:"parent_id"` + Title string `json:"title"` +} diff --git a/app/admin/models/sysmodel/authentication.go b/app/admin/models/sysmodel/authentication.go new file mode 100644 index 0000000..f5cde8c --- /dev/null +++ b/app/admin/models/sysmodel/authentication.go @@ -0,0 +1,384 @@ +package sysmodel + +import ( + statuscode "go-admin/common/status_code" + "go-admin/pkg/cryptohelper/md5helper" + "go-admin/pkg/emailhelper" + + log "github.com/go-admin-team/go-admin-core/logger" + + "go-admin/pkg/utility" +) + +type FrontedUserRegisterReq struct { + Account string `json:"account"` // 账号 + RegisterType int `json:"register_type"` // 注册类型 1: 手机 2: 邮箱 + PhoneAreaCode string `json:"phone_area_code"` // 区域电话代码 + Phone string `json:"phone"` // 手机号码 + Captcha string `json:"captcha"` // 验证码:携带验证码注册 + Email string `json:"email"` // 邮箱地址 + Password string `json:"password"` // 密码 + CheckPassword string `json:"check_password"` // 确认密码 + InviteCode string `json:"invite_code"` // 邀请码 + IP string `json:"-"` // IP + Pid int `json:"-"` // 推荐人ID + +} + +// CheckParams 校验邮箱参数 +func (i *FrontedUserRegisterReq) CheckParams() int { + if i.RegisterType == TSmsCode { + if i.Phone == "" { + return statuscode.PhoneRequired + } + if i.PhoneAreaCode == "+86" { + if !utility.IsMobileChina(i.Phone) { + return statuscode.PhoneFormatInvalid + } + } + i.Account = i.Phone + + } else if i.RegisterType == TEmailCode { + // 邮箱校验 + if i.Email == "" { + return statuscode.EmailRequired + } + if !emailhelper.CheckIsEmail(i.Email) { + return statuscode.EmailFormatInvalid + } + i.Account = i.Email + } else { + log.Error("only support email and phone register") + return statuscode.ParameterInvalid + } + return statuscode.OK +} + +/** + * 身份认证模型 + */ + +// UserRegisterReq 用户注册 +type UserRegisterReq struct { + Account string `json:"account"` // 账号 + RegisterType int `json:"register_type"` // 注册类型 1: 手机 2: 邮箱 + PhoneAreaCode string `json:"phone_area_code"` // 区域电话代码 + Phone string `json:"phone"` // 手机号码 + Email string `json:"email"` // 邮箱地址 + Password string `json:"password"` // 密码 + Captcha string `json:"captcha"` // 验证码:1-不带验证码注册,仅验证注册信息是否合法;2-携带验证码注册,成功后返回 token + InviteCode string `json:"invite_code"` // 邀请码 + UserAddress string `json:"user_address"` // 地址 + Receive string `json:"-"` // 验证码接收方 + PhoneBindState int `json:"-"` // 手机绑定状态 + EmailBindState int `json:"-"` // 邮箱绑定状态 + IP string `json:"-"` // IP + Source int `json:"-"` // 来源:0-未知,1-Android,2-iOS,3-PC + AgentCode string `json:"agent_code"` // 代理邀请码 + AreaId int `json:"areaid"` // 地区序号 +} + +func (i *UserRegisterReq) Valid() int { + // 注册类型 + if i.RegisterType == TSmsCode { + // 手机校验 + if i.PhoneAreaCode == "" { + return statuscode.PleaseSelectThePhoneAreaCode + } + if i.Phone == "" { + return statuscode.PhoneRequired + } + if i.PhoneAreaCode == "86" { + if !utility.IsMobileChina(i.Phone) { + return statuscode.PhoneFormatInvalid + } + } /*else { + if !utility.ValidatePhoneNumber(i.Phone) { + return statuscode.Differentarea + } + }*/ + + i.Account = i.Phone + i.Receive = i.Phone + i.PhoneBindState = AuthBoundOpen + + } else if i.RegisterType == TEmailCode { + // 邮箱校验 + if i.Email == "" { + return statuscode.EmailRequired + } + if !emailhelper.CheckIsEmail(i.Email) { + return statuscode.EmailFormatInvalid + } + + i.Account = i.Email + i.Receive = i.Email + i.EmailBindState = AuthBoundOpen + + } else { + log.Error("only support email and phone register") + return statuscode.ParameterInvalid + } + + // 密码校验 + if i.Password == "" { + return statuscode.PasswordRequired + } + if !utility.CheckPasswordOk(8, 32, i.Password) { + return statuscode.PasswordRules + } + // 加密密码 + i.Password = md5helper.MD52(i.Password) + + return statuscode.OK +} + +// TokenResp 注册成功,返回 token +type TokenResp struct { + JwtToken string `json:"jwt_token"` + JwtExpire string `json:"jwt_expire"` +} + +// UserAccountPwdLoginReq 用户账号密码登录 +type UserAccountPwdLoginReq struct { + LoginType int `json:"login_type"` // 1 手机,2 邮箱 + PhoneAreaCode string `json:"phone_area_code"` // 区域电话代码 + Phone string `json:"phone"` // 手机号码 + Email string `json:"email"` // 邮箱 + Password string `json:"password"` // 密码 + CaptchaEmbedded // 嵌入验证码 + LoginIP string `json:"-"` // IP + Source int `json:"-"` // 来源:0-未知,1-Android,2-iOS,3-PC + DeviceID string `json:"-"` // 设备ID + TimeStamp string `json:"sessionId"` //时间戳 +} +type UserRefreshTokenReq struct { + RefreshKey string `json:"refresh_key"` // 刷新token的key + RefreshTime string `json:"refresh_time"` // 刷新时间戳 +} + +func (l *UserAccountPwdLoginReq) Valid() int { + // 登录类型 + if l.LoginType == TSmsCode { + // 手机校验 + if l.PhoneAreaCode == "86" { + if !utility.IsMobileChina(l.Phone) { + return statuscode.PhoneFormatInvalid + } + } /*else { + if !utility.ValidatePhoneNumber(l.Phone) { + return statuscode.Differentarea + } + }*/ + } else if l.LoginType == TEmailCode { + // 邮箱校验 + if !emailhelper.CheckIsEmail(l.Email) { + return statuscode.EmailFormatInvalid + } + } else { + return statuscode.ParameterInvalid + } + + // 密码校验 + if len(l.Password) == 0 { + return statuscode.PasswordRequired + } + l.Password = md5helper.MD52(l.Password) + + return statuscode.OK +} + +// UserLoginResp 用户登录响应 +type UserLoginResp struct { + UserAccount string `json:"user_account"` // 账号 + AreaPhoneCode string `json:"area_phone_code"` // 区域电话代码 + PhoneNumber string `json:"phone_number"` // 手机 + EmailAddr string `json:"email_addr"` // 邮箱 + PhonePass string `json:"phone_pass"` // 手机加密显示 + EmailPass string `json:"email_pass"` // 邮箱加密显示 + IsPhoneAuth int `json:"is_phone_auth"` // 是否开启手机验证 0否,1是 + IsEmailAuth int `json:"is_email_auth"` // 是否开启手机验证 0否,1是 + IsGoogleAuth int `json:"is_google_auth"` // 是否开启谷歌验证 0否,1是 + LoginType int `json:"login_type"` // 登录类型 1 手机 2 邮箱 + IdCardState int `json:"id_card_state"` // 实名认证审核状态:1未审核,2审核通过,3审核不通过4审核中 + JwtToken string `json:"jwt_token"` // 登录成功返回 token + JwtExpire string `json:"jwt_expire"` // token过期时间 +} + +// 扫码登录 - 秘钥 +type ScanLoginSecret struct { + Secret string `json:"secret"` // 秘钥 + Status int `json:"status"` // 状态:0 初始化,1 登录中,2 登录成功,-1 已过期 + Token string `json:"token"` // 登录成功返回 + IP string `json:"ip"` // 登录IP(PC) + Position string `json:"position"` // IP归属地 + DeviceID string `json:"device_id"` // 登录设备(PC) + Time int64 `json:"time"` // 秘钥生成时间 +} + +// 扫码登录 - APP扫描请求参数 +type ScanLoginScanReq struct { + Secret string `json:"secret"` // 二维码秘钥 + Confirm int `json:"confirm"` // 确认登录:0-查询登录状态,1-确认登录 +} + +// UserChangePwdReq 修改密码 +type UserChangePwdReq struct { + NewPassword string `json:"new_password"` // 密码 + OldPassword string `json:"old_password"` // 旧密码 + CaptchaEmbedded // 嵌入验证码 + UserId int `json:"-"` +} + +func (p *UserChangePwdReq) Valid() int { + if p.OldPassword == "" { + return statuscode.OriginalPasswordRequired + } + + if p.NewPassword == "" { + return statuscode.NewPasswordRequired + } + + if !utility.CheckPasswordOk(8, 32, p.NewPassword) { + return statuscode.PasswordRules + } + + p.OldPassword = md5helper.MD52(p.OldPassword) + p.NewPassword = md5helper.MD52(p.NewPassword) + + return statuscode.OK +} + +// ResetPwdReq 重置密码 +type ResetPwdReq struct { + RetrieveType int `json:"retrieve_type"` // 找回密码类型:1 手机,2 邮箱 + PhoneAreaCode string `json:"phone_area_code"` // 区域电话代码 + Phone string `json:"phone"` // 手机号码 + Email string `json:"email"` // 邮箱地址 + Password string `json:"password"` // 密码 + Credentials string `json:"credentials"` // 凭证 + UserID int `json:"-"` +} + +func (self *ResetPwdReq) Valid() int { + // 发送手机验证码 + if self.RetrieveType == TSmsCode { + // 区域电话代码不能为空 + if self.PhoneAreaCode == "" { + return statuscode.PleaseSelectThePhoneAreaCode + } + + // 电话号码不能为空 + if self.Phone == "" { + return statuscode.PhoneRequired + } + + // 校验手机号码是否合法 + if self.PhoneAreaCode == "86" { + if !utility.IsMobileChina(self.Phone) { + return statuscode.PhoneFormatInvalid + } + } /*else { + if !utility.ValidatePhoneNumber(self.Phone) { + return statuscode.Differentarea + } + }*/ + + } else if self.RetrieveType == TEmailCode { // 发送邮箱验证码 + // 邮箱不能为空 + if self.Email == "" { + return statuscode.EmailRequired + } + + // 校验邮箱是否合法 + if !emailhelper.CheckIsEmail(self.Email) { + return statuscode.EmailFormatInvalid + } + + } else { + + log.Error("only support phone and email auth") + return statuscode.ParameterInvalid + } + + // 传凭证时,才校验密码 + if len(self.Credentials) > 0 { + if self.Password == "" { + return statuscode.NewPasswordRequired + } + if !utility.CheckPasswordOk(8, 32, self.Password) { + return statuscode.PasswordRules + } + + self.Password = md5helper.MD52(self.Password) + } + + return statuscode.OK +} + +// ResetPwdResp 重置密码响应 +type ResetPwdResp struct { + RetrieveType int `json:"retrieve_type"` // 找回密码类型:1 手机,2 邮箱 + Account string `json:"account"` // 账号 + AreaPhoneCode string `json:"area_phone_code"` // 区域电话代码 + PhoneNumber string `json:"phone_number"` // 手机号码 + EmailAddr string `json:"email_addr"` // 邮箱地址 + PhonePass string `json:"phone_pass"` // 手机加密显示 + EmailPass string `json:"email_pass"` // 邮箱加密显示 + IsPhoneAuth int `json:"is_phone_auth"` // 是否绑定手机 0否,1是 + IsEmailAuth int `json:"is_email_auth"` // 是否绑定邮箱 0否,1是 + IsGoogleAuth int `json:"is_google_auth"` // 是否绑定谷歌验证器 +} + +// 重置密码安全校验 +type ResetPwdCheck struct { + RetrieveType int `json:"retrieve_type"` // 找回密码类型:1 手机,2 邮箱 + PhoneAreaCode string `json:"phone_area_code"` // 区域电话代码 + Phone string `json:"phone"` // 手机号码 + Email string `json:"email"` // 邮箱地址 + CaptchaEmbedded // 嵌入验证码 +} + +func (self *ResetPwdCheck) Valid() int { + // 发送手机验证码 + if self.RetrieveType == TSmsCode { + // 区域电话代码不能为空 + if self.PhoneAreaCode == "" { + return statuscode.PleaseSelectThePhoneAreaCode + } + + // 电话号码不能为空 + if self.Phone == "" { + return statuscode.PhoneRequired + } + + // 校验手机号码是否合法 + if self.PhoneAreaCode == "86" { + if !utility.IsMobileChina(self.Phone) { + return statuscode.PhoneFormatInvalid + } + } /*else { + if !utility.ValidatePhoneNumber(self.Phone) { + return statuscode.Differentarea + } + }*/ + + } else if self.RetrieveType == TEmailCode { // 发送邮箱验证码 + // 邮箱不能为空 + if self.Email == "" { + return statuscode.EmailRequired + } + + // 校验邮箱是否合法 + if !emailhelper.CheckIsEmail(self.Email) { + return statuscode.EmailFormatInvalid + } + + } else { + + log.Error("only support phone and email auth") + return statuscode.ParameterInvalid + } + + return statuscode.OK +} diff --git a/app/admin/models/sysmodel/authenticator.go b/app/admin/models/sysmodel/authenticator.go new file mode 100644 index 0000000..61b216c --- /dev/null +++ b/app/admin/models/sysmodel/authenticator.go @@ -0,0 +1,124 @@ +package sysmodel + +import ( + "go-admin/common/const/enum/businesstype" + statuscode "go-admin/common/status_code" + "go-admin/pkg/emailhelper" + "go-admin/pkg/utility" +) + +/** + * 验证器模型 + */ + +// 验证器状态 +var AuthNotBound = 0 // 未绑定 +var AuthBoundOpen = 1 // 绑定开启 +var AuthBoundClose = 2 // 绑定未开启 + +// GetGoogleSecretResp 获取 Google 验证器秘钥 +type GetGoogleSecretResp struct { + Secret string `json:"secret"` + GoogleAuth string `json:"google_auth"` +} + +// UserAuthSwitchStatus 用户身份验证器开关状态 +type UserAuthSwitchStatus struct { + PhoneAuth int `db:"phoneauthisopen"` // 手机验证器:0 关,1 开 + EmailAuth int `db:"emailauthisopen"` // 邮箱验证器:0 关,1 开 + GoogleAuth int `db:"googleidcardisopen"` // 谷歌验证器:0 关,1 开 + GoogleSecret string `db:"googlesecret"` // 谷歌秘钥 +} + +// Authenticator 验证器-验证结构 +type Authenticator struct { + UserID int + PhoneAuth int // 手机验证器:0 关,1 开 + EmailAuth int // 邮箱验证器:0 关,1 开 + GoogleAuth int // 谷歌验证器:0 关,1 开 + Phone string // 手机号 + Email string // 邮箱 + GoogleSecret string // 谷歌秘钥 + SmsCaptcha string // 手机验证码 + EmailCaptcha string // 邮箱验证码 + GoogleCaptcha string // 谷歌验证码 + BusinessType businesstype.BusinessType // 业务类型 +} + +// AuthenticatorSwitch 身份验证开关【请求】结构 +type AuthenticatorSwitch struct { + OperationType int `json:"operation_type"` // 操作类型:1-开启,2-更改,3-关闭 + ValidatorType int `json:"validator_type"` // 验证器类型:1-手机,2-邮箱,3-Google + SmsCaptcha string `json:"sms_captcha"` // 手机验证码 + EmailCaptcha string `json:"email_captcha"` // 邮箱验证码 + GoogleCaptcha string `json:"google_captcha"` // 谷歌验证码 + NewPhoneArea string `json:"new_phone_area"` // 新区号:开启、更改手机验证器时必传 + NewPhone string `json:"new_phone"` // 新手机号:开启、更改手机验证器时必传 + NewPhoneCaptcha string `json:"new_phone_captcha"` // 新手机验证码 + NewEmail string `json:"new_email"` // 新邮箱:开启、更改邮箱验证器时必传 + NewEmailCaptcha string `json:"new_email_captcha"` // 新邮箱验证码:开启、更改邮箱验证器时必传 + NewGoogleCaptcha string `json:"new_google_captcha"` // 新邮箱验证码:开启、更改邮箱验证器时必传 + UserID int `json:"-"` // + BusinessType businesstype.BusinessType `json:"-"` // 业务类型:11-开启手机验证,21-更改手机验证,31-关闭手机验证,12-开启邮箱验证,22-更改邮箱验证,32-关闭邮箱验证,13-开启Google验证,23-更改Google验证,33-关闭Google验证 +} + +func (self *AuthenticatorSwitch) Valid() int { + // 计算业务类型 + self.BusinessType = businesstype.BusinessType(self.OperationType*10 + self.ValidatorType) + + switch self.BusinessType { + + case businesstype.OpenPhoneAuth, businesstype.ChangePhoneAuth: // 开启手机 & 更改手机 + if self.NewPhoneArea == "" { + return statuscode.PleaseSelectThePhoneAreaCode + } + if self.NewPhone == "" { + return statuscode.PhoneRequired + } + if self.NewPhoneCaptcha == "" { + return statuscode.PhoneCaptchaRequired + } + + if self.NewPhoneArea == "86" { + if !utility.IsMobileChina(self.NewPhone) { + return statuscode.PhoneFormatInvalid + } + } /*else { + if !utility.ValidatePhoneNumber(self.NewPhone) { + return enum.Differentarea + } + }*/ + + return statuscode.OK + + case businesstype.ClosePhoneAuth: // 关闭手机 + + case businesstype.OpenEmailAuth, businesstype.ChangeEmailAuth: // 开启邮箱 & 更改邮箱 + if self.NewEmail == "" { + return statuscode.EmailRequired + } + if self.NewEmailCaptcha == "" { + return statuscode.EmailCaptchaRequired + } + + if !emailhelper.CheckIsEmail(self.NewEmail) { + return statuscode.EmailFormatInvalid + } + + return statuscode.OK + + case businesstype.CloseEmailAuth: // 关闭邮箱 + + case businesstype.OpenGoogleAuth: // 开启 Google + if self.NewGoogleCaptcha == "" { + return statuscode.GoogleCaptchaRequired + } + case businesstype.ChangeGoogleAuth: // 更改 Google 实质就是关闭,但业务类型要区分开 + case businesstype.CloseGoogleAuth: // 关闭 Google + + default: + return statuscode.UnsupportedBusinessType + } + + return statuscode.OK +} diff --git a/app/admin/models/sysmodel/captcha.go b/app/admin/models/sysmodel/captcha.go new file mode 100644 index 0000000..3f4aaaa --- /dev/null +++ b/app/admin/models/sysmodel/captcha.go @@ -0,0 +1,116 @@ +package sysmodel + +import ( + statuscode "go-admin/common/status_code" + "go-admin/pkg/emailhelper" + "go-admin/pkg/utility" + "time" + + log "github.com/go-admin-team/go-admin-core/logger" +) + +/** + * 验证码模型 + */ + +var TSmsCode = 1 // 手机验证码 +var TEmailCode = 2 // 邮箱验证码 +var CodeValidTime = 5 * 60 * time.Second // 验证码有效期 5min + +// SendCaptchaReq 发送验证码 +type SendCaptchaReq struct { + BusinessType int `json:"business_type"` // 业务类型:1-注册,2-登录,3-找回密码,4-修改密码,11-开启手机验证,21-更改手机验证,31-关闭手机验证,12-开启邮箱验证,22-更改邮箱验证,32-关闭邮箱验证,13-开启Google验证,23-更改Google验证,33-关闭Google验证 100-手机验证码 101 + SendType int `json:"send_type"` // 发送验证码类型 1 手机 2 邮箱 + IsNew int `json:"is_new"` // 更改手机、邮箱验证时,新号码获取验证码需传 is_new: 1 + PhoneAreaCode string `json:"phone_area_code"` // 区域电话代码 + Phone string `json:"phone"` // 手机号码 + Email string `json:"email"` // 邮箱地址 + Uid int `json:"-"` // 存用户ID用于判断是否登录,从token中解析得到,而非传入 + IP string `json:"-"` // IP + Receive string `json:"-"` // 验证码接收方 +} + +func (self *SendCaptchaReq) Valid() int { + // 业务类型不能为空 + if self.BusinessType <= 0 { + log.Error("send verify code business info unknown") + return statuscode.ParameterInvalid + } + // 业务类型 1 注册 2 登录 3 找回密码 无须登录,其余业务须登录 + if self.BusinessType > 3 && self.Uid == 0 { + return statuscode.NotLoggedIn + } + + // 发送手机验证码 + if self.SendType == TSmsCode { + // 区域电话代码不能为空 + if self.PhoneAreaCode == "" { + return statuscode.PleaseSelectThePhoneAreaCode + } + + // 电话号码不能为空 + if self.Phone == "" { + return statuscode.PhoneRequired + } + + // 校验手机号码是否合法 + if self.PhoneAreaCode == "86" { + if !utility.IsMobileChina(self.Phone) { + return statuscode.PhoneFormatInvalid + } + } /*else { + if !utility.ValidatePhoneNumber(self.Phone) { + return enum.Differentarea + } + }*/ + + // 接收方 + self.Receive = self.Phone + + } else if self.SendType == TEmailCode { // 发送邮箱验证码 + // 邮箱不能为空 + if self.Email == "" { + return statuscode.EmailRequired + } + + // 校验邮箱是否合法 + if !emailhelper.CheckIsEmail(self.Email) { + return statuscode.EmailFormatInvalid + } + + // 接收方 + self.Receive = self.Email + + } else { + log.Error("only support phone and email auth") + return statuscode.ParameterInvalid + } + + return statuscode.OK +} + +// CaptchaEmbedded 校验验证码的嵌入结构 +type CaptchaEmbedded struct { + SmsCaptcha string `json:"sms_captcha"` // 手机验证码 + EmailCaptcha string `json:"email_captcha"` // 邮箱验证码 + GoogleCaptcha string `json:"google_captcha"` // 谷歌验证码 +} + +// CheckCaptcha 校验验证码 +type CheckCaptcha struct { + BusinessType int `db:"businesstype"` // 业务类型 + Receive string `db:"phone"` // 接收方, 手机或邮箱 + Captcha string `db:"code"` // 验证码 + Flag int `db:"verifyflag"` // 标志:0-未验证,1-已验证 + Now time.Time `db:"now"` +} + +// Credential 验证码校验通过生成凭证 +type Credential struct { + BusinessType int // 业务类型 + UserID int // 用户ID + Phone string // 手机 + Email string // 邮箱 + Time int64 // 凭证生成时间戳 + Rand int64 // 随机种子 +} diff --git a/app/admin/models/sysmodel/sysstatuscode.go b/app/admin/models/sysmodel/sysstatuscode.go new file mode 100644 index 0000000..4af6a60 --- /dev/null +++ b/app/admin/models/sysmodel/sysstatuscode.go @@ -0,0 +1,13 @@ +package sysmodel + +type StatusCode struct { + Code int `db:"code" gorm:"code"` // 状态码 + Explain string `db:"explain" gorm:"explain"` // 描述 + ZhCN string `db:"zh_cn" gorm:"zh_cn"` // 简体中文 + ZhHK string `db:"zh_hk" gorm:"zh_hk"` // 繁体中文 + En string `db:"en" gorm:"en"` // 英文 +} + +func (StatusCode) TableName() string { + return "sys_status_code" +} diff --git a/app/admin/models/vts_recharge.go b/app/admin/models/vts_recharge.go new file mode 100644 index 0000000..97cc665 --- /dev/null +++ b/app/admin/models/vts_recharge.go @@ -0,0 +1,47 @@ +package models + +import ( + "github.com/shopspring/decimal" + "time" + + "go-admin/common/models" +) + +type VtsRecharge struct { + models.Model + + CoinCode string `json:"coinCode" gorm:"type:varchar(20);comment:币种"` + TranType int `json:"tranType" gorm:"type:int;comment:类型:1-线上 2-内部"` + OrderNo string `json:"orderNo" gorm:"type:varchar(255);comment:订单号"` + AdUserId int `json:"adUserId" gorm:"type:bigint;comment:用户id"` + Amount decimal.Decimal `json:"amount" gorm:"type:decimal(32,8);comment:订单价格"` + PriceCurrency string `json:"priceCurrency" gorm:"type:varchar(20);comment:CoinGate结算货币代码"` + ReceiveAmount decimal.Decimal `json:"receiveAmount" gorm:"type:decimal(32,8);comment:实收数量"` + PayAmount decimal.Decimal `json:"payAmount" gorm:"type:decimal(32,8);comment:实际支付数量"` + PayCurrency string `json:"payCurrency" gorm:"type:varchar(255);comment:实际支付的加密货币"` + UnderpaidAmount decimal.Decimal `json:"underpaidAmount" gorm:"type:decimal(32,8);comment:买家少付数量"` + OverpaidAmount decimal.Decimal `json:"overpaidAmount" gorm:"type:decimal(32,8);comment:买家多付数量"` + IsRefundable int `json:"isRefundable" gorm:"type:tinyint;comment:指示购物者是否可以请求发票退款"` + WalletCreateAt time.Time `json:"walletCreateAt" gorm:"type:datetime;comment:第三方创建时间"` + WalletId int `json:"walletId" gorm:"type:int;comment:第三方钱包订单"` + Fee decimal.Decimal `json:"fee" gorm:"type:decimal(32,8);comment:手续费"` + CallbackAt time.Time `json:"callbackAt" gorm:"type:datetime;comment:回调时间"` + Status string `json:"status" gorm:"type:varchar(20);comment:状态(字典 vts_recharge_status)"` + ConfirmStatus string `json:"confirmStatus" gorm:"type:varchar(20);comment:确认状态(字典 vts_confirm_status)"` + Remark string `json:"remark" gorm:"type:varchar(500);comment:备注"` + models.ModelTime + models.ControlBy +} + +func (VtsRecharge) TableName() string { + return "vts_recharge" +} + +func (e *VtsRecharge) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *VtsRecharge) GetId() interface{} { + return e.Id +} diff --git a/app/admin/router/init_router.go b/app/admin/router/init_router.go new file mode 100644 index 0000000..61fb978 --- /dev/null +++ b/app/admin/router/init_router.go @@ -0,0 +1,40 @@ +package router + +import ( + "os" + + "github.com/gin-gonic/gin" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk" + common "go-admin/common/middleware" +) + +// InitRouter 路由初始化,不要怀疑,这里用到了 +func InitRouter() { + var r *gin.Engine + h := sdk.Runtime.GetEngine() + if h == nil { + log.Fatal("not found engine...") + os.Exit(-1) + } + switch h.(type) { + case *gin.Engine: + r = h.(*gin.Engine) + default: + log.Fatal("not support other engine") + os.Exit(-1) + } + + // the jwt middleware + authMiddleware, err := common.AuthInit() + if err != nil { + log.Fatalf("JWT Init Error, %s", err.Error()) + } + + // 注册系统路由 + InitSysRouter(r, authMiddleware) + + // 注册业务路由 + // TODO: 这里可存放业务路由,里边并无实际路由只有演示代码 + InitExamplesRouter(r, authMiddleware) +} diff --git a/app/admin/router/line_account_setting.go b/app/admin/router/line_account_setting.go new file mode 100644 index 0000000..6597462 --- /dev/null +++ b/app/admin/router/line_account_setting.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineAccountSettingRouter) +} + +// registerLineAccountSettingRouter +func registerLineAccountSettingRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineAccountSetting{} + r := v1.Group("/line-account-setting").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/line_api_group.go b/app/admin/router/line_api_group.go new file mode 100644 index 0000000..04da850 --- /dev/null +++ b/app/admin/router/line_api_group.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineApiGroupRouter) +} + +// registerLineApiGroupRouter +func registerLineApiGroupRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineApiGroup{} + r := v1.Group("/line-api-group").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/line_api_user.go b/app/admin/router/line_api_user.go new file mode 100644 index 0000000..ae2c66e --- /dev/null +++ b/app/admin/router/line_api_user.go @@ -0,0 +1,31 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/actions" + "go-admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineApiUserRouter) +} + +// registerLineApiUserRouter +func registerLineApiUserRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineApiUser{} + r := v1.Group("/line-api-user").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + + r.POST("bind", api.Bind) //绑定从属关系 + r.POST("getUser", api.GetUser) //获取未绑定的用户 + r.POST("getMainUser", api.GetMainUser) //获取获取主账号的用户 + } +} diff --git a/app/admin/router/line_coinnetwork.go b/app/admin/router/line_coinnetwork.go new file mode 100644 index 0000000..391f428 --- /dev/null +++ b/app/admin/router/line_coinnetwork.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineCoinnetworkRouter) +} + +// registerLineCoinnetworkRouter +func registerLineCoinnetworkRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineCoinnetwork{} + r := v1.Group("/line-coinnetwork").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/line_cointonetwork.go b/app/admin/router/line_cointonetwork.go new file mode 100644 index 0000000..806176c --- /dev/null +++ b/app/admin/router/line_cointonetwork.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineCointonetworkRouter) +} + +// registerLineCointonetworkRouter +func registerLineCointonetworkRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineCointonetwork{} + r := v1.Group("/line-cointonetwork").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/line_direction.go b/app/admin/router/line_direction.go new file mode 100644 index 0000000..ce84a42 --- /dev/null +++ b/app/admin/router/line_direction.go @@ -0,0 +1,39 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/actions" + "go-admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineDirectionRouter) + routerNoVersion = append(routerNoVersion, registerLineDirectionNoVersionRouter) +} + +func registerLineDirectionNoVersionRouter(v1 *gin.RouterGroup) { + api := apis.LineDirection{} + + group := v1.Group("/Direction") + { + group.POST("addDirection", api.AddDirection) // 新增预估方向 + } +} + +// registerLineDirectionRouter +func registerLineDirectionRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineDirection{} + r := v1.Group("/line-direction").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + + r.PUT("/reload-group", api.ReloadGroupData) + } +} diff --git a/app/admin/router/line_order_template_logs.go b/app/admin/router/line_order_template_logs.go new file mode 100644 index 0000000..3bd90c6 --- /dev/null +++ b/app/admin/router/line_order_template_logs.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineOrderTemplateLogsRouter) +} + +// registerLineOrderTemplateLogsRouter +func registerLineOrderTemplateLogsRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineOrderTemplateLogs{} + r := v1.Group("/line-order-template-logs").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/line_pre_order.go b/app/admin/router/line_pre_order.go new file mode 100644 index 0000000..5753dcb --- /dev/null +++ b/app/admin/router/line_pre_order.go @@ -0,0 +1,49 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/actions" + "go-admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLinePreOrderRouter) + routerNoCheckRole = append(routerNoCheckRole, unRegisterLinePreOrderRouter) +} + +// registerLinePreOrderRouter +func registerLinePreOrderRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LinePreOrder{} + r := v1.Group("/line-pre-order").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + + r.POST("addOrder", actions.PermissionAction(), api.AddPreOrder) //添加订单 + r.POST("batchAddOrder", actions.PermissionAction(), api.BatchAddOrder) //批量添加订单 + r.POST("quickAddPreOrder", actions.PermissionAction(), api.QuickAddPreOrder) //快捷下单 + r.POST("lever", actions.PermissionAction(), api.Lever) //设置杠杆 + r.POST("marginType", actions.PermissionAction(), api.MarginType) //设置仓位模式 + r.POST("cancelOpenOrder", actions.PermissionAction(), api.CancelOpenOrder) //取消委托 + r.POST("clearAll", actions.PermissionAction(), api.ClearAll) // 一键清除数据 + r.POST("getChildOrder", actions.PermissionAction(), api.GetChildOrderList) // 获取子订单 + r.POST("manuallyCover", actions.PermissionAction(), api.ManuallyCover) // 手动加仓 + r.POST("closePosition", actions.PermissionAction(), api.ClosePosition) // 平仓 + r.GET("getOrderPage", actions.PermissionAction(), api.GetOrderPage) //订单列表 + r.POST("clearUnTriggered", actions.PermissionAction(), api.ClearUnTriggered) // 清除待触发的交易对 + } +} + +func unRegisterLinePreOrderRouter(v1 *gin.RouterGroup) { + api := apis.LinePreOrder{} + r := v1.Group("/line-pre-order") + { + r.POST("queryOrder", api.QueryOrder) //查询订单 + } +} diff --git a/app/admin/router/line_pre_order_status.go b/app/admin/router/line_pre_order_status.go new file mode 100644 index 0000000..7510db4 --- /dev/null +++ b/app/admin/router/line_pre_order_status.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLinePreOrderStatusRouter) +} + +// registerLinePreOrderStatusRouter +func registerLinePreOrderStatusRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LinePreOrderStatus{} + r := v1.Group("/line-pre-order-status").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/line_pre_script.go b/app/admin/router/line_pre_script.go new file mode 100644 index 0000000..7438572 --- /dev/null +++ b/app/admin/router/line_pre_script.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLinePreScriptRouter) +} + +// registerLinePreScriptRouter +func registerLinePreScriptRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LinePreScript{} + r := v1.Group("/line-pre-script").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/line_price_limit.go b/app/admin/router/line_price_limit.go new file mode 100644 index 0000000..d51233f --- /dev/null +++ b/app/admin/router/line_price_limit.go @@ -0,0 +1,28 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/actions" + "go-admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLinePriceLimitRouter) +} + +// registerLinePriceLimitRouter +func registerLinePriceLimitRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LinePriceLimit{} + r := v1.Group("/line-price-limit").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + r.POST("upRange", api.UpRange) //更新涨跌幅 + } +} diff --git a/app/admin/router/line_recharge.go b/app/admin/router/line_recharge.go new file mode 100644 index 0000000..af7560b --- /dev/null +++ b/app/admin/router/line_recharge.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineRechargeRouter) +} + +// registerLineRechargeRouter +func registerLineRechargeRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineRecharge{} + r := v1.Group("/line-recharge").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/line_symbol.go b/app/admin/router/line_symbol.go new file mode 100644 index 0000000..fef0e5c --- /dev/null +++ b/app/admin/router/line_symbol.go @@ -0,0 +1,32 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/actions" + "go-admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineSymbolRouter) +} + +// registerLineSymbolRouter +func registerLineSymbolRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineSymbol{} + r := v1.Group("/line-symbol").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("getSameSymbol", actions.PermissionAction(), api.GetSameSymbol) //获取现货和合约都有的交易对 + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + + r.POST("syncSpotSymbol", api.SyncSpotSymbol) //同步现货交易对 + r.POST("syncFutSymbol", api.SyncFutSymbol) //同步合约交易对 + r.POST("getSymbol", api.GetSymbol) //获取现货和合约都有的交易对 + } +} diff --git a/app/admin/router/line_symbol_black.go b/app/admin/router/line_symbol_black.go new file mode 100644 index 0000000..681a0b7 --- /dev/null +++ b/app/admin/router/line_symbol_black.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineSymbolBlackRouter) +} + +// registerLineSymbolBlackRouter +func registerLineSymbolBlackRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineSymbolBlack{} + r := v1.Group("/line-symbol-black").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/line_symbol_group.go b/app/admin/router/line_symbol_group.go new file mode 100644 index 0000000..46c47cc --- /dev/null +++ b/app/admin/router/line_symbol_group.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineSymbolGroupRouter) +} + +// registerLineSymbolGroupRouter +func registerLineSymbolGroupRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineSymbolGroup{} + r := v1.Group("/line-symbol-group").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/line_system_setting.go b/app/admin/router/line_system_setting.go new file mode 100644 index 0000000..dc89b47 --- /dev/null +++ b/app/admin/router/line_system_setting.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineSystemSettingRouter) +} + +// registerLineSystemSettingRouter +func registerLineSystemSettingRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineSystemSetting{} + r := v1.Group("/line-system-setting").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/line_uduncoin.go b/app/admin/router/line_uduncoin.go new file mode 100644 index 0000000..d8beb63 --- /dev/null +++ b/app/admin/router/line_uduncoin.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineUduncoinRouter) +} + +// registerLineUduncoinRouter +func registerLineUduncoinRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineUduncoin{} + r := v1.Group("/line-uduncoin").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/line_user.go b/app/admin/router/line_user.go new file mode 100644 index 0000000..35cafa0 --- /dev/null +++ b/app/admin/router/line_user.go @@ -0,0 +1,64 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "go-admin/app/admin/fronted" + + "go-admin/app/admin/apis" + "go-admin/common/actions" + "go-admin/common/middleware" +) + +func init() { + routerFrontedCheckRole = append(routerFrontedCheckRole, frontedRegisterLinUserRouter) + routerCheckRole = append(routerCheckRole, registerLineUserRouter) +} + +// registerLineUserRouter +func registerLineUserRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineUser{} + r := v1.Group("/line-user").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} + +func frontedRegisterLinUserRouter(v1 *gin.RouterGroup) { + api := fronted.LineUserApi{} + r := v1.Group("/line") + { + r.POST("/register", api.Register) //用户注册 + r.POST("/verifyEmail", api.VerifyEmail) //验证邮箱 + r.POST("/sendVerifyEmail", api.SendVerifyEmail) //发送验证邮箱 + r.POST("/sendRegisterSms", api.SendRegisterSms) //发送注册短信 + r.POST("/login", api.Login) //登录 + } + + //需要token鉴权 + r.POST("/center", middleware.FrontedAuth, api.Info) //用户中心 + r.POST("/getIp", middleware.FrontedAuth, api.GetWhiteIp) //用户手动获取ApiKey白名单ip + r.POST("/addApiAuth", middleware.FrontedAuth, api.AddApiKey) //用户手动添加Apikey + r.POST("/updateApiAuth", middleware.FrontedAuth, api.UpdateApiKey) //用户手动修改Apikey + r.POST("/opStatus", middleware.FrontedAuth, api.OpenStatus) //开启或者关闭状态 + + //充值 + r.POST("/notify", api.Notify) //uDun回调 + r.POST("/rechargeNetworkList", middleware.FrontedAuth, api.RechargeNetworkList) //充值 通过充值币种选择主网络 + r.POST("/rechargeNetworkAddress", middleware.FrontedAuth, api.RechargeNetworkAddress) //充值 通过主网ID和用户ID获取交易地址 + r.POST("/fundingTrend", middleware.FrontedAuth, api.FundingTrend) //资金走势 + + //coinGate 充值 + r.POST("/callback", api.CallBack) //coinGate 回调地址 + r.POST("/preorder", middleware.FrontedAuth, api.PreOrder) //coinGate 充值 + +} + +func frontedUserCenterRouter(v1 *gin.RouterGroup) { + //api := fronted.LineUserApi{} + +} diff --git a/app/admin/router/line_user_funding_trend.go b/app/admin/router/line_user_funding_trend.go new file mode 100644 index 0000000..c8df210 --- /dev/null +++ b/app/admin/router/line_user_funding_trend.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineUserFundingTrendRouter) +} + +// registerLineUserFundingTrendRouter +func registerLineUserFundingTrendRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineUserFundingTrend{} + r := v1.Group("/line-user-funding-trend").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/line_user_profit_logs.go b/app/admin/router/line_user_profit_logs.go new file mode 100644 index 0000000..03ccccd --- /dev/null +++ b/app/admin/router/line_user_profit_logs.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineUserProfitLogsRouter) +} + +// registerLineUserProfitLogsRouter +func registerLineUserProfitLogsRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineUserProfitLogs{} + r := v1.Group("/line-user-profit-logs").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/line_wallet.go b/app/admin/router/line_wallet.go new file mode 100644 index 0000000..164b606 --- /dev/null +++ b/app/admin/router/line_wallet.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerLineWalletRouter) +} + +// registerLineWalletRouter +func registerLineWalletRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.LineWallet{} + r := v1.Group("/line-wallet").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/router.go b/app/admin/router/router.go new file mode 100644 index 0000000..45269e5 --- /dev/null +++ b/app/admin/router/router.go @@ -0,0 +1,65 @@ +package router + +import ( + "github.com/gin-gonic/gin" + _ "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" +) + +var ( + routerFrontedCheckRole = make([]func(*gin.RouterGroup), 0) + routerNoCheckRole = make([]func(*gin.RouterGroup), 0) + routerNoVersion = make([]func(*gin.RouterGroup), 0) + routerCheckRole = make([]func(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware), 0) +) + +func InitExamplesRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gin.Engine { + + // 无需认证的路由 + examplesNoCheckRoleRouter(r) + + examplesNoCheckAppRoleRouter(r) + + examplesNoVersionRouter(r) + + // 需要认证的路由 + examplesCheckRoleRouter(r, authMiddleware) + + return r +} + +func examplesNoVersionRouter(r *gin.Engine) { + // 可根据业务需求来设置接口版本 + v1 := r.Group("/api") + for _, f := range routerNoVersion { + f(v1) + } +} + +// 无需认证的路由示例 +func examplesNoCheckRoleRouter(r *gin.Engine) { + // 可根据业务需求来设置接口版本 + v1 := r.Group("/api/v1") + for _, f := range routerNoCheckRole { + f(v1) + } +} + +// 无需认证app的路由示例 +func examplesNoCheckAppRoleRouter(r *gin.Engine) { + // 可根据业务需求来设置接口版本 + v1 := r.Group("/api/v1") + for _, f := range routerFrontedCheckRole { + f(v1) + } +} + +// 需要认证的路由示例 +func examplesCheckRoleRouter(r *gin.Engine, authMiddleware *jwtauth.GinJWTMiddleware) { + // 可根据业务需求来设置接口版本 + v1 := r.Group("/api/v1") + for _, f := range routerCheckRole { + f(v1, authMiddleware) + } +} diff --git a/app/admin/router/sys_api.go b/app/admin/router/sys_api.go new file mode 100644 index 0000000..8272df3 --- /dev/null +++ b/app/admin/router/sys_api.go @@ -0,0 +1,24 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSysApiRouter) +} + +// registerSysApiRouter +func registerSysApiRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysApi{} + r := v1.Group("/sys-api").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", api.GetPage) + r.GET("/:id", api.Get) + r.PUT("/:id", api.Update) + } +} diff --git a/app/admin/router/sys_config.go b/app/admin/router/sys_config.go new file mode 100644 index 0000000..2dabd08 --- /dev/null +++ b/app/admin/router/sys_config.go @@ -0,0 +1,43 @@ +package router + +import ( + "go-admin/app/admin/apis" + "go-admin/common/middleware" + + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSysConfigRouter) +} + +// 需认证的路由代码 +func registerSysConfigRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysConfig{} + r := v1.Group("/config").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", api.GetPage) + r.GET("/:id", api.Get) + r.POST("", api.Insert) + r.PUT("/:id", api.Update) + r.DELETE("", api.Delete) + } + + r1 := v1.Group("/configKey").Use(authMiddleware.MiddlewareFunc()) + { + r1.GET("/:configKey", api.GetSysConfigByKEYForService) + } + + r2 := v1.Group("/app-config") + { + r2.GET("", api.Get2SysApp) + } + + r3 := v1.Group("/set-config").Use(authMiddleware.MiddlewareFunc()) + { + r3.PUT("", api.Update2Set) + r3.GET("", api.Get2Set) + } + +} diff --git a/app/admin/router/sys_dept.go b/app/admin/router/sys_dept.go new file mode 100644 index 0000000..f939374 --- /dev/null +++ b/app/admin/router/sys_dept.go @@ -0,0 +1,32 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "go-admin/app/admin/apis" + "go-admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSysDeptRouter) +} + +// 需认证的路由代码 +func registerSysDeptRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysDept{} + + r := v1.Group("/dept").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", api.GetPage) + r.GET("/:id", api.Get) + r.POST("", api.Insert) + r.PUT("/:id", api.Update) + r.DELETE("", api.Delete) + } + + r1 := v1.Group("").Use(authMiddleware.MiddlewareFunc()) + { + r1.GET("/deptTree", api.Get2Tree) + } + +} \ No newline at end of file diff --git a/app/admin/router/sys_dict.go b/app/admin/router/sys_dict.go new file mode 100644 index 0000000..ae9e0c4 --- /dev/null +++ b/app/admin/router/sys_dict.go @@ -0,0 +1,37 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "go-admin/app/admin/apis" + "go-admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerDictRouter) +} + +func registerDictRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + dictApi := apis.SysDictType{} + dataApi := apis.SysDictData{} + dicts := v1.Group("/dict").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + + dicts.GET("/data", dataApi.GetPage) + dicts.GET("/data/:dictCode", dataApi.Get) + dicts.POST("/data", dataApi.Insert) + dicts.PUT("/data/:dictCode", dataApi.Update) + dicts.DELETE("/data", dataApi.Delete) + + dicts.GET("/type-option-select", dictApi.GetAll) + dicts.GET("/type", dictApi.GetPage) + dicts.GET("/type/:id", dictApi.Get) + dicts.POST("/type", dictApi.Insert) + dicts.PUT("/type/:id", dictApi.Update) + dicts.DELETE("/type", dictApi.Delete) + } + opSelect := v1.Group("/dict-data").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + opSelect.GET("/option-select", dataApi.GetAll) + } +} diff --git a/app/admin/router/sys_login_log.go b/app/admin/router/sys_login_log.go new file mode 100644 index 0000000..61b4e83 --- /dev/null +++ b/app/admin/router/sys_login_log.go @@ -0,0 +1,24 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "go-admin/app/admin/apis" + "go-admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSysLoginLogRouter) +} + +// 需认证的路由代码 +func registerSysLoginLogRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysLoginLog{} + + r := v1.Group("/sys-login-log").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", api.GetPage) + r.GET("/:id", api.Get) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/sys_menu.go b/app/admin/router/sys_menu.go new file mode 100644 index 0000000..b5cad5a --- /dev/null +++ b/app/admin/router/sys_menu.go @@ -0,0 +1,33 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "go-admin/app/admin/apis" + "go-admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSysMenuRouter) +} + +// 需认证的路由代码 +func registerSysMenuRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysMenu{} + + r := v1.Group("/menu").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", api.GetPage) + r.GET("/:id", api.Get) + r.POST("", api.Insert) + r.PUT("/:id", api.Update) + r.DELETE("", api.Delete) + } + + r1 := v1.Group("").Use(authMiddleware.MiddlewareFunc()) + { + r1.GET("/menurole", api.GetMenuRole) + //r1.GET("/menuids", api.GetMenuIDS) + } + +} \ No newline at end of file diff --git a/app/admin/router/sys_opera_log.go b/app/admin/router/sys_opera_log.go new file mode 100644 index 0000000..d24d54f --- /dev/null +++ b/app/admin/router/sys_opera_log.go @@ -0,0 +1,23 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "go-admin/app/admin/apis" + "go-admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSysOperaLogRouter) +} + +// 需认证的路由代码 +func registerSysOperaLogRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysOperaLog{} + r := v1.Group("/sys-opera-log").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", api.GetPage) + r.GET("/:id", api.Get) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/sys_post.go b/app/admin/router/sys_post.go new file mode 100644 index 0000000..e299a5d --- /dev/null +++ b/app/admin/router/sys_post.go @@ -0,0 +1,25 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "go-admin/app/admin/apis" + "go-admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSyPostRouter) +} + +// 需认证的路由代码 +func registerSyPostRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysPost{} + r := v1.Group("/post").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", api.GetPage) + r.GET("/:id", api.Get) + r.POST("", api.Insert) + r.PUT("/:id", api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/sys_role.go b/app/admin/router/sys_role.go new file mode 100644 index 0000000..5bfdd2a --- /dev/null +++ b/app/admin/router/sys_role.go @@ -0,0 +1,31 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSysRoleRouter) +} + +// 需认证的路由代码 +func registerSysRoleRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysRole{} + r := v1.Group("/role").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", api.GetPage) + r.GET("/:id", api.Get) + r.POST("", api.Insert) + r.PUT("/:id", api.Update) + r.DELETE("", api.Delete) + } + r1 := v1.Group("").Use(authMiddleware.MiddlewareFunc()) + { + r1.PUT("/role-status", api.Update2Status) + r1.PUT("/roledatascope", api.Update2DataScope) + } +} diff --git a/app/admin/router/sys_router.go b/app/admin/router/sys_router.go new file mode 100644 index 0000000..b713e4b --- /dev/null +++ b/app/admin/router/sys_router.go @@ -0,0 +1,88 @@ +package router + +import ( + "go-admin/app/admin/apis" + "mime" + + "github.com/go-admin-team/go-admin-core/sdk/config" + + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "github.com/go-admin-team/go-admin-core/sdk/pkg/ws" + ginSwagger "github.com/swaggo/gin-swagger" + + swaggerfiles "github.com/swaggo/files" + + "go-admin/common/middleware" + "go-admin/common/middleware/handler" + _ "go-admin/docs/admin" +) + +func InitSysRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gin.RouterGroup { + g := r.Group("") + sysBaseRouter(g) + // 静态文件 + sysStaticFileRouter(g) + // swagger;注意:生产环境可以注释掉 + if config.ApplicationConfig.Mode != "prod" { + sysSwaggerRouter(g) + } + // 需要认证 + sysCheckRoleRouterInit(g, authMiddleware) + return g +} + +func sysBaseRouter(r *gin.RouterGroup) { + + go ws.WebsocketManager.Start() + go ws.WebsocketManager.SendService() + go ws.WebsocketManager.SendAllService() + + if config.ApplicationConfig.Mode != "prod" { + r.GET("/", apis.GoAdmin) + } + r.GET("/info", handler.Ping) +} + +func sysStaticFileRouter(r *gin.RouterGroup) { + err := mime.AddExtensionType(".js", "application/javascript") + if err != nil { + return + } + r.Static("/static", "./static") + if config.ApplicationConfig.Mode != "prod" { + r.Static("/form-generator", "./static/form-generator") + } +} + +func sysSwaggerRouter(r *gin.RouterGroup) { + r.GET("/swagger/admin/*any", ginSwagger.WrapHandler(swaggerfiles.NewHandler(), ginSwagger.InstanceName("admin"))) +} + +func sysCheckRoleRouterInit(r *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + wss := r.Group("").Use(authMiddleware.MiddlewareFunc()) + { + wss.GET("/ws/:id/:channel", ws.WebsocketManager.WsClient) + wss.GET("/wslogout/:id/:channel", ws.WebsocketManager.UnWsClient) + } + + v1 := r.Group("/api/v1") + { + v1.POST("/login", authMiddleware.LoginHandler) + // Refresh time can be longer than token timeout + v1.GET("/refresh_token", authMiddleware.RefreshHandler) + } + registerBaseRouter(v1, authMiddleware) +} + +func registerBaseRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysMenu{} + api2 := apis.SysDept{} + v1auth := v1.Group("").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + v1auth.GET("/roleMenuTreeselect/:roleId", api.GetMenuTreeSelect) + //v1.GET("/menuTreeselect", api.GetMenuTreeSelect) + v1auth.GET("/roleDeptTreeselect/:roleId", api2.GetDeptTreeRoleSelect) + v1auth.POST("/logout", handler.LogOut) + } +} diff --git a/app/admin/router/sys_user.go b/app/admin/router/sys_user.go new file mode 100644 index 0000000..4a545a6 --- /dev/null +++ b/app/admin/router/sys_user.go @@ -0,0 +1,39 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "go-admin/app/admin/apis" + "go-admin/common/actions" + "go-admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSysUserRouter) +} + +// 需认证的路由代码 +func registerSysUserRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysUser{} + r := v1.Group("/sys-user").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()).Use(actions.PermissionAction()) + { + r.GET("", api.GetPage) + r.GET("/:id", api.Get) + r.POST("", api.Insert) + r.PUT("", api.Update) + r.DELETE("", api.Delete) + } + + user := v1.Group("/user").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()).Use(actions.PermissionAction()) + { + user.GET("/profile", api.GetProfile) + user.POST("/avatar", api.InsetAvatar) + user.PUT("/pwd/set", api.UpdatePwd) + user.PUT("/pwd/reset", api.ResetPwd) + user.PUT("/status", api.UpdateStatus) + } + v1auth := v1.Group("").Use(authMiddleware.MiddlewareFunc()) + { + v1auth.GET("/getinfo", api.GetInfo) + } +} \ No newline at end of file diff --git a/app/admin/router/vts_recharge.go b/app/admin/router/vts_recharge.go new file mode 100644 index 0000000..5ad89c3 --- /dev/null +++ b/app/admin/router/vts_recharge.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerVtsRechargeRouter) +} + +// registerVtsRechargeRouter +func registerVtsRechargeRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.VtsRecharge{} + r := v1.Group("/vts-recharge").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/service/aduserdb/ad_identification.go b/app/admin/service/aduserdb/ad_identification.go new file mode 100644 index 0000000..349b2fc --- /dev/null +++ b/app/admin/service/aduserdb/ad_identification.go @@ -0,0 +1,198 @@ +package aduserdb + +// +//import ( +// "errors" +// "go-admin/app/admin/models" +// "go-admin/app/admin/service/dto" +// +// "go.uber.org/zap" +// "gorm.io/gorm" +// +// log "github.com/go-admin-team/go-admin-core/logger" +//) +// +//// AddJunior 初级认证 +//func AddJunior(orm *gorm.DB, ident models.AdIdentification) error { +// err := orm.Model(ident).Create(ident).Error +// // err := dbhelper.MasterPgdb.InsertTx("ad_identification", &ident, tx) +// if err != nil { +// log.Error("register Junior", zap.Error(err)) +// return err +// } +// return nil +//} +// +//// ValidIdCard 验证证件信息是否已存在 +//func ValidIdCard(orm *gorm.DB, idcardtype int, idcard string) (resp models.AdIdentification) { +// // sql := `SELECT +// // id, userid, name, surname, middlename, birthdate, countryid, city, address, zipcode, idcardtype, idcard, +// // idcardimage1, idcardimage2, idcardvideo, verifyfile, idcardlevel, idcardstate, verifytype, +// // createtime, verifytime, reason, videocode, faceimage1, faceimage2, accountid, workid, +// // idcreditails, selfiecredential, facemapcredential +// // FROM ad_identification WHERE idcardtype = $1 AND idcard = $2 ` +// // err := dbhelper.MasterPgdb.Get(&resp, sql, idcardtype, idcard) +// err := orm.Model(resp).Where("idcard_type =? AND idcard=?", idcardtype, idcard).Error +// +// if err != nil { +// log.Error("ValidIdCard : ", zap.Error(err)) +// return +// } +// return resp +//} +// +//// UpdateJunior 更新初级认证 +//func UpdateJunior(orm *gorm.DB, ident dto.IdentificationJuniorReq) error { +// // updateSql := `UPDATE +// // ad_identification +// // SET +// // name = :name,surname = :surname,middlename = :middlename, +// // idcardlevel = 1,idcardstate = 1,birthdate = :birthdate, +// // countryid = :countryid,idcardtype = :idcardtype,idcard = :idcard +// // WHERE userid = :userid` +// +// //dbhelper.MasterPgdb.NamedExecTx(updateSql, &ident, tx); +// if err := orm.Model(models.AdIdentification{}).Where("user_id =?", ident.UserId).Updates(map[string]interface{}{ +// "name": ident.Name, "surname": ident.Surname, "middle_name": ident.MiddleName, +// "idcard_level": 1, "idcard_state": 1, "birth_date": ident.BirthDate, "country_id": ident.CountryId, +// "idcard_type": ident.IdCardType, "idcard": ident.IdCard, +// }).Error; err != nil { +// log.Error("UpdateJunior", zap.Error(err)) +// return err +// } +// return nil +//} +// +//// UpdateMiddle 中级认证 +//func UpdateMiddle(orm *gorm.DB, ident dto.IdentificationMiddleReq) error { +// // updateSql := `UPDATE +// // ad_identification +// // SET +// // idcardimage1 = :idcardimage1,idcardimage2 = :idcardimage2,idcardvideo = :idcardvideo, +// // idcardlevel = 2,idcardstate = :idcardstate,verifytype = :verifytype,videocode= :videocode, +// // reason = :reason,verifytime = :verifytime,faceimage1 = :faceimage1,faceimage2 = :faceimage2, +// // accountid = :accountid,workid = :workid,idcreditails = :idcreditails, +// // selfiecredential = :selfiecredential,facemapcredential = :facemapcredential +// // WHERE userid = :userid` +// // dbhelper.MasterPgdb.NamedExecTx(updateSql, &ident, tx) +// +// if err := orm.Model(models.AdIdentification{}).Where("user_id=?", ident.UserId).Updates(map[string]interface{}{ +// "idcard_image1": ident.IdCardImage1, "idcard_image2": ident.IdCardImage2, +// "idcard_video": ident.IdCardVideo, "idcard_level": 2, "idcard_state": ident.IdCardState, +// "verify_type": ident.VerifyType, "video_code": ident.VideoCode, "reason": ident.Reason, +// "verify_time": ident.VerifyTime, "face_image1": ident.FaceImage1, "face_image2": ident.FaceImage2, +// "account_id": ident.AccountId, "work_id": ident.WorkFlowExecutionId, "id_creditails": ident.IdCreditails, +// "selfile_credential": ident.SelfieCredential, "facemap_credential": ident.FacemapCredential, +// }).Error; err != nil { +// log.Error("UpdateMiddle", zap.Error(err)) +// return err +// } +// return nil +//} +// +//// UpdateMiddleByThird d +//func UpdateMiddleByThird(orm *gorm.DB, ident dto.IdentificationMiddleReq) error { +// // updateSql := `UPDATE +// // ad_identification +// // SET +// // idcardimage1 = :idcardimage1,idcardimage2 = :idcardimage2,idcardstate = :idcardstate, +// // reason = :reason,verifytime = :verifytime,faceimage1 = :faceimage1,faceimage2 = :faceimage2 +// +// // WHERE userid = :userid` +// +// // dbhelper.MasterPgdb.NamedExecTx(updateSql, &ident, tx) +// +// if err := orm.Model(models.AdIdentification{}).Where("user_id=?", ident.UserId).Updates(map[string]interface{}{ +// "idcard_image1": ident.IdCardImage1, "idcard_image2": ident.IdCardImage2, "idcard_state": ident.IdCardState, +// "reason": ident.Reason, "verify_time": ident.VerifyTime, "face_image1": ident.FaceImage1, "face_image2": ident.FaceImage2, +// }).Error; err != nil { +// log.Error("UpdateMiddleByThird", zap.Error(err)) +// return err +// } +// return nil +//} +// +//// UpdateSenior 用户高级认证 +//func UpdateSenior(orm *gorm.DB, userId int) error { +// // updateSql := `UPDATE ad_identification SET idcardlevel = 3,idcardstate = 1 WHERE userid = $1` +// // dbhelper.MasterPgdb.ExecTx(updateSql, tx, userId) +// +// if err := orm.Model(models.AdIdentification{}).Where("user_id=?", userId).Updates(map[string]interface{}{ +// "idcard_level": 3, "idcard_state": 1, +// }).Error; err != nil { +// log.Error("UpdateSenior", zap.Error(err)) +// return err +// } +// return nil +//} +// +//// GetIdentification 获取认证信息 +//func GetIdentification(orm *gorm.DB, userId int) (models.AdIdentification, error) { +// // sqlInfo := `SELECT id, userid, name, surname, middlename, birthdate, countryid, city, +// // address, zipcode, idcardtype, idcard, idcardimage1, idcardimage2, idcardvideo, +// // verifyfile, idcardlevel, idcardstate, verifytype,createtime,verifytime,reason, +// // accountid, workid , +// // idcreditails , +// // selfiecredential , +// // facemapcredential +// // FROM ad_identification WHERE userid = $1` +// +// //dbhelper.MasterPgdb.Get(&ident, sqlInfo, userId) +// var ident models.AdIdentification +// err := orm.Model(ident).Where("user_id =?", userId).First(&ident).Error +// +// if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { +// log.Error("get Identification", zap.Error(err)) +// return ident, err +// } +// return ident, nil +//} +// +//// UpdateJuniorMidAuth 标准(中级)认证审核 +//// func UpdateJuniorMidAuth(req dto.IdentJuniorMiddleAuthReq, tx *sqlx.Tx) error { +//// updateSql := `UPDATE +//// ad_identification +//// SET +//// idcardstate = :idcardstate,reason = :reason,verifytime = :verifytime +//// WHERE userid = :userid` +//// if _, err := dbhelper.MasterPgdb.NamedExecTx(updateSql, &req, tx); err != nil { +//// log.Error("UpdateJuniorMidAuth", zap.Error(err)) +//// return err +//// } +//// return nil +//// } +// +//// UpdateSeniorAuth 高级认证审核 +//// func UpdateSeniorAuth(req dto.IdentificationSeniorReq, tx *sqlx.Tx) error { +//// updateSql := `UPDATE +//// ad_identification +//// SET +//// city = :city,address = :address,zipcode = :zipcode, +//// idcardlevel = 3,idcardstate = :idcardstate,verifytype = 2, +//// verifyfile = :verifyfile,reason = :reason,verifytime = :verifytime +//// WHERE userid = :userid` +//// if _, err := dbhelper.MasterPgdb.NamedExecTx(updateSql, &req, tx); err != nil { +//// log.Error("UpdateSeniorAuth", zap.Error(err)) +//// return err +//// } +//// return nil +//// } +// +//// IdentDetail 认证详情 +//// func IdentDetail(userid int) (detail dto.IdentDetailResp, err error) { +//// sql := `SELECT +//// userid,name,surname,middlename,idcardtype,idcard,idcardimage1, +//// idcardimage2,birthdate,idcardvideo,address,city,zipcode, +//// verifyfile,reason,videocode,faceimage1,faceimage2,ad_user.phone, +//// ad_user.useraccount, to_char(ad_user.createtime,'yyyy-MM-dd hh24:MI:ss') as createtime,ad_user.useremail, ad_user.area,ad_country.namezh +//// FROM +//// ad_identification +//// JOIN ad_user ON ad_user.id = ad_identification.userid +//// JOIN ad_country ON ad_country.id = ad_identification.countryid +//// WHERE ad_identification.userid = $1` +//// err = dbhelper.MasterPgdb.Get(&detail, sql, userid) +//// if err != nil { +//// log.Error("IdentDetail", zap.Error(err)) +//// } +//// return +//// } diff --git a/app/admin/service/aduserdb/ad_user_db.go b/app/admin/service/aduserdb/ad_user_db.go new file mode 100644 index 0000000..ad799e1 --- /dev/null +++ b/app/admin/service/aduserdb/ad_user_db.go @@ -0,0 +1,552 @@ +package aduserdb + +import ( + "errors" + log "github.com/go-admin-team/go-admin-core/logger" + "go-admin/app/admin/models" + statuscode "go-admin/common/status_code" + + "go.uber.org/zap" + "gorm.io/gorm" +) + +//func SetUserStatus(orm *gorm.DB, req adminmodel.UserForbidReq) error { +// // sql := `UPDATE ad_user SET +// // status = :status, +// // c2cadvertstatus = :c2cadvertstatus, +// // vtsstatus = :vtsstatus, +// // chargestatus = :chargestatus, +// // withdrawalstatus = :withdrawalstatus, +// // futstatus = :futstatus, +// // transferstatus = :transferstatus, +// // c2ctradestatus = :c2ctradestatus +// // WHERE id = :id` +// err := orm.Model(&models.AdUser{}).Where("id =?", req.ID).Updates(map[string]interface{}{"status": req.Status, "c2c_advert_status": req.C2cAdvertStatus, +// "vts_status": req.VtsStatus, "charge_status": req.ChargeStatus, "with_drawal_status": req.WithdrawalStatus, +// "fut_status": req.FutStatus, "transfer_status": req.TransferStatus, "c2c_trade_status": req.C2cTradeStatus}).Error +// +// if err != nil { +// log.Error("SetUserStatus", err) +// } +// return err +//} +//func SetUserInfoStopRate(orm *gorm.DB, userId int, stopRate int, openratetime time.Time) error { +// // sql := `UPDATE ad_userinfo SET +// // stoprate = $1, +// // openratetime = $2 +// // WHERE userid = $3` +// err := orm.Model(&models.AdUserInfo{}).Where("user_id=?", userId).Updates(map[string]interface{}{"stoprate": stopRate, "open_rate_time": openratetime}).Error +// +// if err != nil { +// log.Error("SetUserInfoStopRate", zap.Error(err)) +// } +// return err +//} +//func GetUserStatusByID(orm *gorm.DB, id int) (data adminmodel.UserForbidUserResp, err error) { +// // sql := `SELECT id,nickname,status,c2cadvertstatus,vtsstatus,chargestatus,withdrawalstatus,futstatus,transferstatus,c2ctradestatus FROM ad_user WHERE id=$1` +// +// err = orm.Model(&models.AdUser{}).Where("id=?", id).First(&data).Error +// if err != nil { +// log.Error("GetUserStatusByID", zap.Error(err)) +// } +// return +//} + +// GetUserById 通过id获取用户信息 +func GetUserById(orm *gorm.DB, id int) (data models.LineUser, err error) { + // sql := "SELECT " + userFile + " FROM ad_user WHERE id= $1" //`SELECT "" FROM ad_user where id= $1` + err = orm.Model(&data).Where("id =?", id).First(&data).Error + if err != nil { + log.Error("GetUserById", zap.Error(err)) + } + return +} + +// GetUserByInviteCode 根据邀请码获取用户信息 +func GetUserByInviteCode(orm *gorm.DB, inviteCode string) (user models.LineUser, err error) { + if err = orm.Model(&user).Where("invite_code = ?", inviteCode).First(&user).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + log.Error("GetUserByInviteCode ", zap.Error(err)) + return + } + err = nil + return +} + +// GetUserStatusById 通过id获取用户基础状态 +func GetUserStatusById(orm *gorm.DB, id int) (data models.LineUser) { + // sql := "SELECT id,futstatus,transferstatus,withdrawalstatus,vtsstatus,chargestatus FROM ad_user WHERE id=$1" //`SELECT "" FROM ad_user where id= $1` + err := orm.Model(&data).Where("id =?", id).First(&data).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + log.Error("GetUserStatusById", zap.Error(err)) + } + err = nil + return +} + +func GetUserByEmail(orm *gorm.DB, userEmail string) (user models.LineUser, err error) { + // sql := sqlUser + ` AND useremail = $1` + if err = orm.Model(&user).Where("email = ?", userEmail).First(&user).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + log.Error("GetUserByEmail ", zap.Error(err)) + return + } + err = nil + return +} + +func GetUserByPhone(orm *gorm.DB, area, phone string) (user models.LineUser, err error) { + // sql := sqlUser + ` AND area = $1 AND phone = $2` + if err = orm.Model(&user).Where("area =? AND mobile =?", area, phone).First(&user).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + log.Error("GetUserByPhone ", zap.Error(err)) + return + } + err = nil + return +} + +// AddUser 添加用户 +func AddUser(orm *gorm.DB, user *models.LineUser) (int, error) { + err := orm.Model(user).Create(user).Error + // userId, err := dbhelper.MasterPgdb.InsertReIdTx("ad_user", &user, tx) + if err != nil { + log.Error("AddUser err", zap.Error(err)) + return user.Id, err + } + return user.Id, nil +} + +func UpdateUserStatus(orm *gorm.DB, phone string) (int, error) { + err := orm.Model(&models.LineUser{}).Update("status", "normal").Error + // userId, err := dbhelper.MasterPgdb.InsertReIdTx("ad_user", &user, tx) + if err != nil { + log.Error("UpdateUserStatus err", zap.Error(err)) + return statuscode.ServerError, err + } + return 0, nil +} + +// AddUserInfo 添加用户信息 +//func AddUserInfo(orm *gorm.DB, userInfo models.AdUserInfo) error { +// layout := "2006-01-02 15:04:05" // Go 的日期格式化布局 +// parsedTime, _ := time.Parse(layout, "1970-01-01 00:00:01") +// userInfo.SecurityUpdateTime = parsedTime +// userInfo.OpenRateTime = parsedTime +// +// if err := orm.Model(&userInfo).Create(&userInfo).Error; err != nil { +// log.Error("AddUserInfo error ", zap.Error(err)) +// return err +// } +// return nil +//} + +// UpdateUserRecommend 更新用户推荐人数 +func UpdateUserRecommend(orm *gorm.DB, userId int) error { + updateSql := `UPDATE ad_user_info SET total_num = total_num + 1 WHERE user_id = ?` + if err := orm.Exec(updateSql, userId).Error; err != nil { + log.Error("update user recommend total failed", zap.Error(err)) + return err + } + return nil +} + +// +//// UpdateUserPushCid 第三方离线推送clientid,绑定该cid-用户 +//func UpdateUserPushCid(orm *gorm.DB, userId int, pushCId string) error { +// //先清除之前绑定该cid的用户, +// // updateCidSql := `UPDATE ad_userinfo SET pushcid='' WHERE pushcid=$1` +// err := orm.Model(&models.AdUserInfo{}).Where("push_cid =?", pushCId).Update("push_cid", pushCId).Error +// if err != nil { +// log.Error("update user UpdateUserPushCid pushcid failed", zap.Error(err)) +// return err +// } +// //再绑定新用户给cid +// // updateSql := `UPDATE ad_userinfo SET pushcid = $1 WHERE userid = $2 ` +// if err = orm.Model(&models.AdUserInfo{}).Where("user_id =?", userId).Update("push_cid", pushCId).Error; err != nil { +// log.Error("update user UpdateUserPushCid failed", zap.Error(err)) +// return err +// } +// +// return nil +//} +// +//// UpdateUserAuth 更新认证状态 +//func UpdateUserAuth(orm *gorm.DB, userId, idcardstate, authlevel int) error { +// // updateSql := `UPDATE ad_user SET idcardstate = $1,authlevel = $2 WHERE id = $3` +// if err := orm.Model(&models.AdUser{}).Where("id=?", userId).Updates(map[string]interface{}{"id_card_state": idcardstate, +// "auth_level": authlevel}).Error; err != nil { +// log.Error("UpdateUserAuth", zap.Error(err)) +// return err +// } +// return nil +//} +// +//// GetUserInfo 获取用户信息 +//func GetUserInfo(orm *gorm.DB, userId int) (models.AdUserInfo, error) { +// var user models.AdUserInfo +// +// if err := orm.Model(&user).Where("user_id =?", userId).First(&user).Error; err != nil { +// log.Error("get userinfo", zap.Error(err)) +// return user, err +// } +// return user, nil +//} +// +//// GetUserCId 获取用户信息cid +//func GetUserCId(orm *gorm.DB, userId int) models.AdUserInfo { +// var user models.AdUserInfo +// if err := orm.Model(&user).Where("user_id =?", userId).First(&user).Error; err != nil { +// log.Error("GetUserCId", zap.Error(err)) +// return user +// } +// return user +//} +// +//// UpdateUserPwd 更新密码 +//func UpdateUserPwd(orm *gorm.DB, userId int, password string) error { +// // var updateSql = `UPDATE ad_user SET userpassword = $1 WHERE id = $2` +// +// if err := orm.Model(&models.AdUser{}).Where("id =?", userId).Update("user_password", password).Error; err != nil { +// log.Error("update user password failed", zap.Error(err)) +// return err +// } +// return nil +//} +// +//// UpdateUserInfo 更新 +//func UpdateUserInfo(orm *gorm.DB, userinfo dto.UpdateUserInfo) error { +// // sql := `UPDATE ad_user SET ` +// // set := make([]string, 0, 2) +// set := make(map[string]interface{}, 0) +// if len(userinfo.NickName) > 0 { +// // set = append(set, " nickname = :nickname ") +// set["nick_name"] = userinfo.NickName +// } +// if len(userinfo.HeadImage) > 0 { +// // set = append(set, " image = :headimage ") +// set["image"] = userinfo.HeadImage +// } +// +// if len(set) == 0 { +// return errors.New("No changes") +// } +// +// if err := orm.Model(models.AdUser{}).Where("id =?", userinfo.UserID).Updates(set).Error; err != nil { +// log.Error("UpdateUserInfo", zap.Error(err)) +// return err +// } +// +// return nil +//} +// +//// SetRateCountry 设置目前用户使用的汇率(基础货币) +//func SetRateCountry(orm *gorm.DB, userId int, RateCountry string) error { +// // sql := `UPDATE ad_userinfo SET ratecountry = $1 WHERE userid = $2` +// data := models.AdUserInfo{} +// +// if err := orm.Model(data).Where("user_id =?", userId).Update("rate_country", RateCountry).Error; err != nil { +// log.Error("update ad_userinfo ratecountry failed", zap.Error(err)) +// return err +// } +// return nil +//} +// +//// GetUserAuthSwitch 获取用户验证器开启状态 +//func GetUserAuthSwitch(orm *gorm.DB, userID int) (auth sysmodel.UserAuthSwitchStatus, err error) { +// // sql := `SELECT phoneauthisopen, emailauthisopen, googleidcardisopen, googlesecret FROM ad_userinfo WHERE userid = $1 LIMIT 1` +// data := models.AdUserInfo{} +// +// err = orm.Model(data).Where("user_id =?", userID).First(&data).Error +// +// if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { +// log.Error("GetUserAuthSwitch", zap.Error(err)) +// return +// } +// +// err = nil +// auth.EmailAuth = data.EmailAuthIsOpen +// auth.GoogleAuth = data.GoogleIdcardIsOpen +// auth.GoogleSecret = data.GoogleSecret +// auth.PhoneAuth = data.PhoneAuthIsOpen +// +// return +//} +// +//// BindPhoneAuth 绑定手机验证 +//func BindPhoneAuth(orm *gorm.DB, userId int, area string, phone string) error { +// // updateUser := `UPDATE ad_user SET area = $1, phone = $2 WHERE id = $3` +// // updateUserinfo := `UPDATE ad_userinfo SET phoneauthisopen = $1 WHERE userid = $2` +// user := models.AdUser{} +// userInfo := models.AdUserInfo{} +// user.Id = userId +// user.Area = area +// user.Phone = phone +// +// //事务 +// err := orm.Transaction(func(tx *gorm.DB) error { +// err := tx.Model(user).Updates(map[string]interface{}{"area": area, "phone": phone}).Error +// +// if err != nil { +// return err +// } +// +// err = tx.Model(userInfo).Where("user_id =?", userId).Update("phone_auth_is_open", sysmodel.AuthBoundOpen).Error +// +// if err != nil { +// return err +// } +// +// return nil +// }) +// +// if err != nil { +// log.Error("BindPhoneAuth Commit Error:", zap.Error(err)) +// return err +// } +// +// return nil +//} +// +//// ChangePhoneAuth 更改手机验证 +//func ChangePhoneAuth(orm *gorm.DB, userId int, area string, phone string) error { +// // updateUser := `UPDATE ad_user SET area = $1, phone = $2 WHERE id = $3` +// // updateUserinfo := `UPDATE ad_userinfo SET securityupdatetime = $1 WHERE userid = $2` +// user := models.AdUser{} +// user.Id = userId +// +// err := orm.Transaction(func(tx *gorm.DB) error { +// err := orm.Model(user).Updates(map[string]interface{}{"area": area, "phone": phone}).Error +// +// if err != nil { +// return err +// } +// +// err = orm.Model(models.AdUserInfo{}).Where("user_id =?", userId).Update("security_update_time", time.Now()).Error +// +// if err != nil { +// return err +// } +// +// return nil +// }) +// +// if err != nil { +// log.Error("Commit Error:", zap.Error(err)) +// return err +// } +// +// return nil +//} +// +//// ClosePhoneAuth 关闭手机验证 +//func ClosePhoneAuth(orm *gorm.DB, userId int) error { +// +// // updateSql := `UPDATE ad_userinfo SET phoneauthisopen = $1, securityupdatetime = $2 WHERE userid = $3` +// user := models.AdUser{} +// user.Id = userId +// +// if err := orm.Model(user).Updates(map[string]interface{}{"phone_auth_is_open": sysmodel.AuthBoundClose, "security_update_time": time.Now()}).Error; err != nil { +// log.Error("ClosePhoneAuth failed", zap.Error(err)) +// return err +// } +// +// return nil +//} +// +//// BindEmailAuth 绑定邮箱验证 +//func BindEmailAuth(orm *gorm.DB, userId int, email string) error { +// // updateUser := `UPDATE ad_user SET useremail = $1 WHERE id = $2` +// // updateUserinfo := `UPDATE ad_userinfo SET emailauthisopen = $1 WHERE userid = $2` +// user := models.LineUser{} +// user.Id = userId +// +// err := orm.Transaction(func(tx *gorm.DB) error { +// err := orm.Model(user).Updates(map[string]interface{}{"user_email": email}).Error +// +// if err != nil { +// return err +// } +// +// err = orm.Model(models.AdUserInfo{}).Where("user_id =?", userId).Update("email_auth_is_open", sysmodel.AuthBoundOpen).Error +// +// if err != nil { +// return err +// } +// +// return nil +// }) +// +// if err != nil { +// log.Error("BindEmailAuth Commit Error:", zap.Error(err)) +// return err +// } +// +// return nil +//} +// +//// ChangeEmailAuth 更改邮箱验证 +//func ChangeEmailAuth(orm *gorm.DB, userId int, email string) error { +// // updateUser := `UPDATE ad_user SET useremail = $1 WHERE id = $2` +// // updateUserinfo := `UPDATE ad_userinfo SET securityupdatetime = $1 WHERE userid = $2` +// user := models.AdUser{} +// user.Id = userId +// +// err := orm.Transaction(func(tx *gorm.DB) error { +// err := orm.Model(user).Update("user_email", email).Error +// +// if err != nil { +// return err +// } +// +// err = orm.Model(models.AdUserInfo{}).Where("user_id =?", userId).Update("security_update_time", time.Now()).Error +// +// if err != nil { +// return err +// } +// +// return nil +// }) +// +// if err != nil { +// log.Error("ChangeEmailAuth Commit Error:", zap.Error(err)) +// return err +// } +// +// return nil +//} +// +//// CloseEmailAuth 关闭邮箱验证 +//func CloseEmailAuth(orm *gorm.DB, userId int) error { +// userInfo := models.AdUserInfo{} +// +// // updateSql := `UPDATE ad_userinfo SET emailauthisopen = $1, securityupdatetime = $2 WHERE userid = $3` +// if err := orm.Model(userInfo).Where("user_id =?", userId).Updates(map[string]interface{}{ +// "email_auth_is_open": sysmodel.AuthBoundClose, "security_update_time": time.Now()}).Error; err != nil { +// log.Error("CloseEmailAuth failed", zap.Error(err)) +// return err +// } +// +// return nil +//} +// +//// OpenGoogleAuth 开启 Google 验证 +//func OpenGoogleAuth(orm *gorm.DB, userId int) error { +// updateSql := `UPDATE ad_user_info SET google_idcard_is_open = ?, google_secret = googlesecret1 WHERE user_id = ?` +// if err := orm.Exec(updateSql, sysmodel.AuthBoundOpen, userId).Error; err != nil { +// log.Error("OpenGoogleAuth failed", zap.Error(err)) +// return err +// } +// +// return nil +//} +// +////// ChangeGoogleAuth 更改 Google 验证 +////func ChangeGoogleAuth(userId int) (err error) { +//// +//// updateSql := `UPDATE ad_userinfo SET googlesecret = googlesecret1 WHERE userid = $1` +//// if _, err = dbhelper.MasterPgdb.Exec(updateSql, userId); err != nil { +//// log.Error("ChangeGoogleAuth failed", zap.Error(err)) +//// } +//// +//// return err +////} +// +//// CloseGoogleAuth 关闭 Google 验证 +//func CloseGoogleAuth(orm *gorm.DB, userId int) error { +// +// // updateSql := `UPDATE ad_userinfo SET googleidcardisopen = $1, securityupdatetime = $2 WHERE userid = $3` +// if err := orm.Model(models.AdUserInfo{}).Where("user_id=?", userId).Updates(map[string]interface{}{ +// "google_idcard_is_open": sysmodel.AuthBoundClose, "security_update_time": time.Now()}).Error; err != nil { +// log.Error("CloseGoogleAuth failed", zap.Error(err)) +// return err +// } +// +// return nil +//} +// +//// GetNewGoogleSecret 获取新谷歌秘钥 +//func GetNewGoogleSecret(orm *gorm.DB, userID int) (string, error) { +// secret := struct { +// NewSecret string `gorm:"googlesecret1"` +// }{} +// +// err := orm.Model(models.AdUserInfo{}).Where("user_id=?", userID).First(secret).Error +// +// if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { +// log.Error("CloseGoogleAuth failed", zap.Error(err)) +// return "", err +// } +// +// return secret.NewSecret, nil +//} +// +//// TemporaryStorageSecret 临时存储更改的 Secret +//func TemporaryStorageSecret(orm *gorm.DB, userId int, secret string) error { +// +// // sql := `UPDATE ad_userinfo SET googlesecret1 = $1 WHERE userid = $2` +// if err := orm.Model(models.AdUserInfo{}).Where("user_id=?", userId).Update("googlesecret1", secret).Error; err != nil { +// log.Error("TemporaryStorageSecret failed", zap.Error(err)) +// return err +// } +// +// return nil +//} +// +//// ALLInvite 累计邀请人数 +//func ALLInvite(orm *gorm.DB, userId int) (count int) { +// // sql := `SELECT totalnum FROM ad_userinfo WHERE userid = $1` +// if err := orm.Model(models.AdUserInfo{}).Where("user_id=?", userId).Select("total_num").First(&count).Error; err != nil { +// log.Error("get userinfo", zap.Error(err)) +// return count +// } +// return +//} +// +//// ThisMonthInvite 本月邀请 +//func ThisMonthInvite(orm *gorm.DB, userId int) (count int) { +// // sql := `SELECT count(*) FROM ad_userinfo WHERE recommenid = $1 AND createtime BETWEEN (now() - INTERVAL '30 days') AND now()` +// var total int64 +// +// if err := orm.Model(models.AdUserInfo{}).Where("recommen_id=? AND created_at BETWEEN (now() - INTERVAL '30 days') AND now()").Count(&total).Error; err != nil { +// log.Error("get userinfo", zap.Error(err)) +// return count +// } +// +// count = int(total) +// return +//} +// +//// InviteNum 总邀请人数 +//func InviteNum(orm *gorm.DB, userId int) (count int) { +// // sql := `SELECT count(*) FROM ad_userinfo WHERE recommenid = $1` +// var total int64 +// +// if err := orm.Model(models.AdUserInfo{}).Where("recommen_id=?", userId).Count(&total).Error; err != nil { +// log.Error("get userinfo", zap.Error(err)) +// return count +// } +// +// count = int(total) +// return +//} +// +//// UpdateUserTransferOpen 更新内部转账开关 +//func UpdateUserTransferOpen(orm *gorm.DB, userId, transferOpen int) error { +// // updateSql := `UPDATE ad_userinfo SET transferopen = $1 WHERE userid = $2` +// if err := orm.Model(models.AdUserInfo{}).Where("user_id=?", userId).Update("transfer_open", transferOpen).Error; err != nil { +// log.Error("UpdateUserTransferOpen failed", zap.Error(err)) +// return err +// } +// return nil +//} +// +//// UpdateUserTransferStatus 更新内部划转开关 +//func UpdateUserTransferStatus(orm *gorm.DB, userid, transferStatus int) error { +// // sql := `UPDATE ad_user SET transferstatus = $1 WHERE id = $2` +// user := models.AdUser{} +// user.Id = userid +// +// if err := orm.Model(user).Update("transfer_status", transferStatus).Error; err != nil { +// log.Error("UpdateUserTransferStatus failed", zap.Error(err)) +// return err +// } +// return nil +//} diff --git a/app/admin/service/aduserdb/ad_verifycode.go b/app/admin/service/aduserdb/ad_verifycode.go new file mode 100644 index 0000000..424dedd --- /dev/null +++ b/app/admin/service/aduserdb/ad_verifycode.go @@ -0,0 +1,138 @@ +package aduserdb + +// +//import ( +// "errors" +// "go-admin/app/admin/models" +// "go-admin/app/admin/models/sysmodel" +// "time" +// +// "go.uber.org/zap" +// "gorm.io/gorm" +// +// log "github.com/go-admin-team/go-admin-core/logger" +//) +// +//// 是否重复发送 +//// ∵ now - send < 60 时,不能重复发送 +//// ∴ send > now - 60 时,不能重复发送 +//func IsRepeatSend(orm *gorm.DB, receive string, businessType int) (verifyCode models.AdVerifyCode, err error) { +// // sqlSe := ` +// // SELECT id, sendtime, verifytime +// // FROM ad_verifycode +// // WHERE businesstype = $1 AND phone = $2 AND sendtime > $3 AND verifyflag = $4 +// // LIMIT 1 +// // ` +// // Now - 60 +// nowSub60 := time.Now().Add(-time.Minute) +// err = orm.Model(verifyCode).Where("business_type =? AND phone =? AND send_time >? AND verify_flag =?", businessType, receive, nowSub60, 0). +// First(&verifyCode). +// Error +// +// if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { +// log.Error("IsRepeatSend", zap.Error(err)) +// return +// } +// +// return +//} +// +//// GetVerifyCode 获取验证码 +//func GetVerifyCode(orm *gorm.DB, req sysmodel.CheckCaptcha) (verifyCode models.AdVerifyCode) { +// req.Now = time.Now() +// req.Flag = 0 +// +// //sql := ` +// // SELECT id FROM ad_verifycode +// // WHERE phone = :phone AND code = :code AND sendtime < :now +// // AND verifytime > :now AND businesstype = :businesstype AND verifyflag = :verifyflag +// //` +// // 查询符合业务的未被验证的验证码 +// //if err = dbhelper.MasterPgdb.GetNameQuery(&verifyCode, sql, req); err != nil { +// // log.Error("GetVerifyCode", zap.Error(err)) +// // return +// //} +// +// // sqlSe := `SELECT id,phone,code FROM ad_verifycode WHERE phone=$1 AND code=$2 AND businesstype=$3 AND verifyflag=0 AND sendtime<$4 AND verifytime>$4 ORDER BY id DESC LIMIT 1` +// // err := dbhelper.MasterPgdb.Get(&verifyCode, sqlSe, req.Receive, req.Captcha, req.BusinessType, req.Now) +// err := orm.Model(verifyCode).Where("phone =? AND code=? AND business_type=? AND verify_flag=0 AND send_time ?", +// req.Receive, req.Captcha, req.BusinessType, req.Now, req.Now). +// Order("id desc"). +// First(&verifyCode). +// Error +// if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { +// log.Error("GetVerifyCode", zap.Error(err)) +// } +// return +//} +// +//// // UpdateVerifyCode 更新验证码状态 +//// func UpdateVerifyCode(cc sysmodel.CheckCaptcha, tx *sqlx.Tx) error { +//// cc.Flag = 1 +//// cc.Now = time.Now() +// +//// sqlUp := ` +//// UPDATE ad_verifycode +//// SET validtime = :now, verifyflag = :verifyflag +//// WHERE phone = :phone AND code = :code AND sendtime < :now AND verifytime > :now AND businesstype = :businesstype AND verifyflag = 0 +//// ` +//// var re sql.Result +//// var err error +//// if tx == nil { +//// re, err = dbhelper.MasterPgdb.NamedExec(sqlUp, cc) +//// } else { +//// // 更新该验证码为已验证 +//// re, err = dbhelper.MasterPgdb.NamedExecTx(sqlUp, cc, tx) +//// } +//// if err != nil { +//// log.Error("UpdateVerifyCode", zap.Error(err)) +//// return err +//// } +//// if row, _ := re.RowsAffected(); row == 0 { +//// return errors.New("验证码无效") +//// } +// +//// return nil +//// } +// +//// UpdateVerifyFlag 更新验证码状态,改为已经使用过该验证码 +//func UpdateVerifyFlag(orm *gorm.DB, id int) error { +// // sqlUp := `UPDATE ad_verifycode SET validtime =$1, verifyflag =1 WHERE id=$2` +// verifyCode := models.AdVerifyCode{} +// verifyCode.Id = id +// +// res := orm.Model(verifyCode).Updates(map[string]interface{}{"valid_time": time.Now(), "verify_flag": 1}) +// // re, err := dbhelper.MasterPgdb.ExecTx(sqlUp, tx, time.Now(), id) +// if res.Error != nil { +// log.Error("UpdateVerifyFlag", zap.Error(res.Error)) +// return res.Error +// } +// if res.RowsAffected == 0 { +// return errors.New("验证码无效") +// } +// return nil +// +// //cc.Flag = 1 +// //cc.Now = time.Now() +// //sqlUp := ` +// // UPDATE ad_verifycode +// // SET validtime = :now, verifyflag = :verifyflag +// // WHERE phone = :phone AND code = :code AND sendtime < :now AND verifytime > :now AND businesstype = :businesstype AND verifyflag = 0 +// //` +// //var re sql.Result +// //var err error +// //if tx == nil { +// // re, err = dbhelper.MasterPgdb.NamedExec(sqlUp, cc) +// //} else { +// // // 更新该验证码为已验证 +// // re, err = dbhelper.MasterPgdb.NamedExecTx(sqlUp, cc, tx) +// //} +// //if err != nil { +// // log.Error("UpdateVerifyCode", zap.Error(err)) +// // return err +// //} +// //if row, _ := re.RowsAffected(); row == 0 { +// // return errors.New("验证码无效") +// //} +// //return nil +//} diff --git a/app/admin/service/binance_account.go b/app/admin/service/binance_account.go new file mode 100644 index 0000000..dfc0b58 --- /dev/null +++ b/app/admin/service/binance_account.go @@ -0,0 +1,64 @@ +package service + +import ( + "errors" + "fmt" + "github.com/bytedance/sonic" + "github.com/go-admin-team/go-admin-core/sdk/service" + "go-admin/app/admin/models" + "go-admin/common/helper" + ext "go-admin/config" + "net/http" +) + +const ProxyType = "socks5" + +type BinanceAccount struct { + service.Service +} + +// GetFundingAsset 查询资金账户 +func (e *BinanceAccount) GetFundingAsset(userId int) (resp []models.FundingAsset, err error) { + var apiUser models.LineApiUser + err = e.Orm.Where(&models.LineApiUser{}).Where("user_id = ?", userId).Find(&apiUser).Error + if err != nil { + e.Log.Errorf("Service LineApiUser error:%s \r\n", err) + return []models.FundingAsset{}, err + } + if apiUser.Id <= 0 { + e.Log.Errorf("Service LineApiUser error:%s \r\n", "用户未找到") + return []models.FundingAsset{}, err + } + var proxyUrl, proxyType string + if ext.ExtConfig.ProxyUrl != "" { + proxyUrl = "127.0.0.1:7890" + proxyType = "http" + } + + if apiUser.IpAddress != "" && apiUser.UserPass != "" { + proxyUrl = fmt.Sprintf("%s:%s", apiUser.IpAddress, apiUser.UserPass) + proxyType = ProxyType + } + clinet, err := helper.NewBinanceClient(apiUser.ApiKey, apiUser.ApiSecret, proxyType, proxyUrl) + if err != nil { + e.Log.Errorf("Service NewBinanceClient error:%s \r\n", err) + return []models.FundingAsset{}, err + } + req := map[string]int64{ + "recvWindow": 10000, + } + + httpResp, statusCode, err := clinet.SendSpotAuth("/sapi/v1/asset/get-funding-asset", "POST", req) + if err != nil { + e.Log.Errorf("Service SendSpotAuth error:%s \r\n", err) + return []models.FundingAsset{}, err + } + + if statusCode != http.StatusOK { + e.Log.Errorf("Service 请求失败 error:%s \r\n", err) + return []models.FundingAsset{}, errors.New("请求失败") + } + sonic.Unmarshal(httpResp, &resp) + + return resp, nil +} diff --git a/app/admin/service/common/base.go b/app/admin/service/common/base.go new file mode 100644 index 0000000..c1825ec --- /dev/null +++ b/app/admin/service/common/base.go @@ -0,0 +1,103 @@ +package common + +import ( + "errors" + "go-admin/pkg/cryptohelper/jwthelper" + "net" + "strconv" + + "github.com/gin-gonic/gin" +) + +// 通过原生 token 获取登录用户 ID,判断是否登录。 +// 注:该方法未经过登录中间件,从原始 token 中解析出登录信息,而非从已解析的 token 串中获取登录信息。 +func GetUserIdByToken(ctx *gin.Context) int { + token := ctx.GetHeader("token") + if len(token) == 0 { + return 0 + } + // 解析token + flag, rew := jwthelper.MidValidToken(token, 3) + if flag < 0 || len(rew) == 0 { + return 0 + } + + loginUser := jwthelper.GetLoginUserJwt(rew) + + return loginUser.UserID +} + +// 获取操作系统:1-Android,2-IOS,3-PC +func GetOS(ctx *gin.Context) int { + osStr := ctx.GetHeader("os") // 获取请求头中的"os"字段 + osInt, err := strconv.Atoi(osStr) // 转换为整数 + if err != nil { + return 0 // 如果转换失败,返回0 + } + return osInt +} + +// 获取 设备ID +func GetDeviceID(ctx *gin.Context) string { + device := ctx.GetHeader("device_id") // 获取请求头中的"os"字段 + + return device +} + +// 获取 language,默认语言:zh-CN +// 英语 en +// 日本语 jp +// 韩语 kr +// 马来西亚语 my +// 泰国语 th +// 越南语 vn +// 简体中文 zh-CN +// 繁体中文 zh-HK +func GetLanguage(ctx *gin.Context) string { + lang := "" + + language := ctx.GetHeader("Accept-Language") + if language == "" { + + val, exits := ctx.Get("language") + + if !exits { + lang = "zh-CN" + } else { + lang = val.(string) + } + + } else { + lang = language + } + return lang +} + +// 根据token获取当前userid +func GetUserId(ctx *gin.Context) int { + token := ctx.GetHeader("ParseToken") // .Request.Header.Peek("token") + // loghelper.Debug("token: " + token) + if len(token) == 0 { + return 0 + } + tokenItem := jwthelper.GetLoginUserJwt(token) + return tokenItem.UserID +} + +// GetClientIp 获取本机ip +func GetClientIp() (string, error) { + adders, err := net.InterfaceAddrs() + if err != nil { + return "", err + } + for _, address := range adders { + // 检查ip地址判断是否回环地址 + if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil { + return ipnet.IP.String(), nil + } + + } + } + return "", errors.New("Can not find the client ip address!") +} diff --git a/app/admin/service/dto/line_account_setting.go b/app/admin/service/dto/line_account_setting.go new file mode 100644 index 0000000..f928226 --- /dev/null +++ b/app/admin/service/dto/line_account_setting.go @@ -0,0 +1,86 @@ +package dto + +import ( + + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type LineAccountSettingGetPageReq struct { + dto.Pagination `search:"-"` + LineAccountSettingOrder +} + +type LineAccountSettingOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_account_setting"` + UserName string `form:"userNameOrder" search:"type:order;column:user_name;table:line_account_setting"` + Password string `form:"passwordOrder" search:"type:order;column:password;table:line_account_setting"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_account_setting"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_account_setting"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_account_setting"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_account_setting"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_account_setting"` + +} + +func (m *LineAccountSettingGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineAccountSettingInsertReq struct { + Id int `json:"-" comment:"id"` // id + UserName string `json:"userName" comment:"用户"` + Password string `json:"password" comment:"密码"` + common.ControlBy +} + +func (s *LineAccountSettingInsertReq) Generate(model *models.LineAccountSetting) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.UserName = s.UserName + model.Password = s.Password + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *LineAccountSettingInsertReq) GetId() interface{} { + return s.Id +} + +type LineAccountSettingUpdateReq struct { + Id int `uri:"id" comment:"id"` // id + UserName string `json:"userName" comment:"用户"` + Password string `json:"password" comment:"密码"` + common.ControlBy +} + +func (s *LineAccountSettingUpdateReq) Generate(model *models.LineAccountSetting) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.UserName = s.UserName + model.Password = s.Password + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *LineAccountSettingUpdateReq) GetId() interface{} { + return s.Id +} + +// LineAccountSettingGetReq 功能获取请求参数 +type LineAccountSettingGetReq struct { + Id int `uri:"id"` +} +func (s *LineAccountSettingGetReq) GetId() interface{} { + return s.Id +} + +// LineAccountSettingDeleteReq 功能删除请求参数 +type LineAccountSettingDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineAccountSettingDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/line_api_group.go b/app/admin/service/dto/line_api_group.go new file mode 100644 index 0000000..f47eaf3 --- /dev/null +++ b/app/admin/service/dto/line_api_group.go @@ -0,0 +1,87 @@ +package dto + +import ( + + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type LineApiGroupGetPageReq struct { + dto.Pagination `search:"-"` + GroupName string `form:"groupName" search:"type:exact;column:group_name;table:line_api_group" comment:"用户组名称"` + LineApiGroupOrder +} + +type LineApiGroupOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_api_group"` + GroupName string `form:"groupNameOrder" search:"type:order;column:group_name;table:line_api_group"` + ApiUserId string `form:"apiUserIdOrder" search:"type:order;column:api_user_id;table:line_api_group"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_api_group"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_api_group"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_api_group"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_api_group"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_api_group"` + +} + +func (m *LineApiGroupGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineApiGroupInsertReq struct { + Id int `json:"-" comment:""` // + GroupName string `json:"groupName" comment:"用户组名称"` + ApiUserId string `json:"apiUserId" comment:"绑定的api账户id"` + common.ControlBy +} + +func (s *LineApiGroupInsertReq) Generate(model *models.LineApiGroup) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.GroupName = s.GroupName + model.ApiUserId = s.ApiUserId + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *LineApiGroupInsertReq) GetId() interface{} { + return s.Id +} + +type LineApiGroupUpdateReq struct { + Id int `uri:"id" comment:""` // + GroupName string `json:"groupName" comment:"用户组名称"` + ApiUserId string `json:"apiUserId" comment:"绑定的api账户id"` + common.ControlBy +} + +func (s *LineApiGroupUpdateReq) Generate(model *models.LineApiGroup) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.GroupName = s.GroupName + model.ApiUserId = s.ApiUserId + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *LineApiGroupUpdateReq) GetId() interface{} { + return s.Id +} + +// LineApiGroupGetReq 功能获取请求参数 +type LineApiGroupGetReq struct { + Id int `uri:"id"` +} +func (s *LineApiGroupGetReq) GetId() interface{} { + return s.Id +} + +// LineApiGroupDeleteReq 功能删除请求参数 +type LineApiGroupDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineApiGroupDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/line_api_user.go b/app/admin/service/dto/line_api_user.go new file mode 100644 index 0000000..a9a1b62 --- /dev/null +++ b/app/admin/service/dto/line_api_user.go @@ -0,0 +1,150 @@ +package dto + +import ( + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type LineApiUserGetPageReq struct { + dto.Pagination `search:"-"` + LineApiUserOrder +} + +type LineApiUserOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_api_user"` + UserId string `form:"userIdOrder" search:"type:order;column:user_id;table:line_api_user"` + JysId string `form:"jysIdOrder" search:"type:order;column:jys_id;table:line_api_user"` + ApiName string `form:"apiNameOrder" search:"type:order;column:api_name;table:line_api_user"` + ApiKey string `form:"apiKeyOrder" search:"type:order;column:api_key;table:line_api_user"` + ApiSecret string `form:"apiSecretOrder" search:"type:order;column:api_secret;table:line_api_user"` + IpAddress string `form:"ipAddressOrder" search:"type:order;column:ip_address;table:line_api_user"` + UserPass string `form:"userPassOrder" search:"type:order;column:user_pass;table:line_api_user"` + AdminId string `form:"adminIdOrder" search:"type:order;column:admin_id;table:line_api_user"` + Affiliation string `form:"affiliationOrder" search:"type:order;column:affiliation;table:line_api_user"` + AdminShow string `form:"adminShowOrder" search:"type:order;column:admin_show;table:line_api_user"` + Site string `form:"siteOrder" search:"type:order;column:site;table:line_api_user"` + Subordinate string `form:"subordinateOrder" search:"type:order;column:subordinate;table:line_api_user"` + GroupId string `form:"groupIdOrder" search:"type:order;column:group_id;table:line_api_user"` + OpenStatus string `form:"openStatusOrder" search:"type:order;column:open_status;table:line_api_user"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_api_user"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_api_user"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_api_user"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_api_user"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_api_user"` +} + +func (m *LineApiUserGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineApiUserInsertReq struct { + Id int `json:"-" comment:"id"` // id + UserId int64 `json:"userId" comment:"用户id"` + JysId int64 `json:"jysId" comment:"关联交易所账号id"` + ApiName string `json:"apiName" comment:"api用户名"` + ApiKey string `json:"apiKey" comment:"apiKey"` + ApiSecret string `json:"apiSecret" comment:"apiSecret"` + IpAddress string `json:"ipAddress" comment:"代理地址"` + UserPass string `json:"userPass" comment:"代码账号密码"` + AdminId int64 `json:"adminId" comment:"管理员id"` + Affiliation int64 `json:"affiliation" comment:"归属:1=现货,2=合约,3=现货合约"` + AdminShow int64 `json:"adminShow" comment:"是否超管可见:1=是,0=否"` + Site string `json:"site" comment:"允许下单的方向:1=多;2=空;3=多空"` + Subordinate string `json:"subordinate" comment:"从属关系:0=未绑定关系;1=主账号;2=副帐号"` + GroupId int64 `json:"groupId" comment:"所属组id"` + OpenStatus int64 `json:"openStatus" comment:"开启状态 0=关闭 1=开启"` + common.ControlBy +} + +func (s *LineApiUserInsertReq) Generate(model *models.LineApiUser) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.UserId = s.UserId + model.JysId = s.JysId + model.ApiName = s.ApiName + model.ApiKey = s.ApiKey + model.ApiSecret = s.ApiSecret + model.IpAddress = s.IpAddress + model.UserPass = s.UserPass + model.AdminId = s.AdminId + model.Affiliation = s.Affiliation + model.AdminShow = s.AdminShow + model.Site = s.Site + model.Subordinate = s.Subordinate + model.GroupId = s.GroupId + model.OpenStatus = s.OpenStatus + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *LineApiUserInsertReq) GetId() interface{} { + return s.Id +} + +type LineApiUserUpdateReq struct { + Id int `uri:"id" comment:"id"` // id + UserId int64 `json:"userId" comment:"用户id"` + JysId int64 `json:"jysId" comment:"关联交易所账号id"` + ApiName string `json:"apiName" comment:"api用户名"` + ApiKey string `json:"apiKey" comment:"apiKey"` + ApiSecret string `json:"apiSecret" comment:"apiSecret"` + IpAddress string `json:"ipAddress" comment:"代理地址"` + UserPass string `json:"userPass" comment:"代码账号密码"` + AdminId int64 `json:"adminId" comment:"管理员id"` + Affiliation int64 `json:"affiliation" comment:"归属:1=现货,2=合约,3=现货合约"` + AdminShow int64 `json:"adminShow" comment:"是否超管可见:1=是,0=否"` + Site string `json:"site" comment:"允许下单的方向:1=多;2=空;3=多空"` + Subordinate string `json:"subordinate" comment:"从属关系:0=未绑定关系;1=主账号;2=副帐号"` + GroupId int64 `json:"groupId" comment:"所属组id"` + OpenStatus int64 `json:"openStatus" comment:"开启状态 0=关闭 1=开启"` + common.ControlBy +} + +func (s *LineApiUserUpdateReq) Generate(model *models.LineApiUser) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.UserId = s.UserId + model.JysId = s.JysId + model.ApiName = s.ApiName + model.ApiKey = s.ApiKey + model.ApiSecret = s.ApiSecret + model.IpAddress = s.IpAddress + model.UserPass = s.UserPass + model.AdminId = s.AdminId + model.Affiliation = s.Affiliation + model.AdminShow = s.AdminShow + model.Site = s.Site + model.Subordinate = s.Subordinate + model.GroupId = s.GroupId + model.OpenStatus = s.OpenStatus + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *LineApiUserUpdateReq) GetId() interface{} { + return s.Id +} + +// LineApiUserGetReq 功能获取请求参数 +type LineApiUserGetReq struct { + Id int `uri:"id"` +} + +func (s *LineApiUserGetReq) GetId() interface{} { + return s.Id +} + +// LineApiUserDeleteReq 功能删除请求参数 +type LineApiUserDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineApiUserDeleteReq) GetId() interface{} { + return s.Ids +} + +type LineApiUserBindSubordinateReq struct { + UserIdStr string `json:"user_id_str"` + GroupName string `json:"group_name"` +} diff --git a/app/admin/service/dto/line_coinnetwork.go b/app/admin/service/dto/line_coinnetwork.go new file mode 100644 index 0000000..4d7b8ef --- /dev/null +++ b/app/admin/service/dto/line_coinnetwork.go @@ -0,0 +1,106 @@ +package dto + +import ( + + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type LineCoinnetworkGetPageReq struct { + dto.Pagination `search:"-"` + LineCoinnetworkOrder +} + +type LineCoinnetworkOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_coinnetwork"` + NetworkName string `form:"networkNameOrder" search:"type:order;column:network_name;table:line_coinnetwork"` + TokenName string `form:"tokenNameOrder" search:"type:order;column:token_name;table:line_coinnetwork"` + ArrivalNum string `form:"arrivalNumOrder" search:"type:order;column:arrival_num;table:line_coinnetwork"` + UnlockNum string `form:"unlockNumOrder" search:"type:order;column:unlock_num;table:line_coinnetwork"` + UnlockTime string `form:"unlockTimeOrder" search:"type:order;column:unlock_time;table:line_coinnetwork"` + Fee string `form:"feeOrder" search:"type:order;column:fee;table:line_coinnetwork"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_coinnetwork"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_coinnetwork"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_coinnetwork"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_coinnetwork"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_coinnetwork"` + +} + +func (m *LineCoinnetworkGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineCoinnetworkInsertReq struct { + Id int `json:"-" comment:"主键ID"` // 主键ID + NetworkName string `json:"networkName" comment:"网络名称"` + TokenName string `json:"tokenName" comment:"网络token名称"` + ArrivalNum int64 `json:"arrivalNum" comment:"充值区块确认数"` + UnlockNum int64 `json:"unlockNum" comment:"提现解锁确认数"` + UnlockTime int64 `json:"unlockTime" comment:"提现确认平均时间,单位分钟"` + Fee string `json:"fee" comment:"网络手续费,该字段是动态的,后面会有服务定时更新该字段"` + common.ControlBy +} + +func (s *LineCoinnetworkInsertReq) Generate(model *models.LineCoinnetwork) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.NetworkName = s.NetworkName + model.TokenName = s.TokenName + model.ArrivalNum = s.ArrivalNum + model.UnlockNum = s.UnlockNum + model.UnlockTime = s.UnlockTime + model.Fee = s.Fee + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *LineCoinnetworkInsertReq) GetId() interface{} { + return s.Id +} + +type LineCoinnetworkUpdateReq struct { + Id int `uri:"id" comment:"主键ID"` // 主键ID + NetworkName string `json:"networkName" comment:"网络名称"` + TokenName string `json:"tokenName" comment:"网络token名称"` + ArrivalNum int64 `json:"arrivalNum" comment:"充值区块确认数"` + UnlockNum int64 `json:"unlockNum" comment:"提现解锁确认数"` + UnlockTime int64 `json:"unlockTime" comment:"提现确认平均时间,单位分钟"` + Fee string `json:"fee" comment:"网络手续费,该字段是动态的,后面会有服务定时更新该字段"` + common.ControlBy +} + +func (s *LineCoinnetworkUpdateReq) Generate(model *models.LineCoinnetwork) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.NetworkName = s.NetworkName + model.TokenName = s.TokenName + model.ArrivalNum = s.ArrivalNum + model.UnlockNum = s.UnlockNum + model.UnlockTime = s.UnlockTime + model.Fee = s.Fee + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *LineCoinnetworkUpdateReq) GetId() interface{} { + return s.Id +} + +// LineCoinnetworkGetReq 功能获取请求参数 +type LineCoinnetworkGetReq struct { + Id int `uri:"id"` +} +func (s *LineCoinnetworkGetReq) GetId() interface{} { + return s.Id +} + +// LineCoinnetworkDeleteReq 功能删除请求参数 +type LineCoinnetworkDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineCoinnetworkDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/line_cointonetwork.go b/app/admin/service/dto/line_cointonetwork.go new file mode 100644 index 0000000..f2b5ec4 --- /dev/null +++ b/app/admin/service/dto/line_cointonetwork.go @@ -0,0 +1,197 @@ +package dto + +import ( + "time" + + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type LineCointonetworkGetPageReq struct { + dto.Pagination `search:"-"` + LineCointonetworkOrder +} + +type LineCointonetworkOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_cointonetwork"` + CoinId string `form:"coinIdOrder" search:"type:order;column:coin_id;table:line_cointonetwork"` + NetworkId string `form:"networkIdOrder" search:"type:order;column:network_id;table:line_cointonetwork"` + IsMain string `form:"isMainOrder" search:"type:order;column:is_main;table:line_cointonetwork"` + IsDeposit string `form:"isDepositOrder" search:"type:order;column:is_deposit;table:line_cointonetwork"` + IsWithdraw string `form:"isWithdrawOrder" search:"type:order;column:is_withdraw;table:line_cointonetwork"` + CoinCode string `form:"coinCodeOrder" search:"type:order;column:coin_code;table:line_cointonetwork"` + Token string `form:"tokenOrder" search:"type:order;column:token;table:line_cointonetwork"` + MinChargeNum string `form:"minChargeNumOrder" search:"type:order;column:min_charge_num;table:line_cointonetwork"` + MinOutNum string `form:"minOutNumOrder" search:"type:order;column:min_out_num;table:line_cointonetwork"` + MaxOutNum string `form:"maxOutNumOrder" search:"type:order;column:max_out_num;table:line_cointonetwork"` + TransferFee string `form:"transferFeeOrder" search:"type:order;column:transfer_fee;table:line_cointonetwork"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_cointonetwork"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_cointonetwork"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_cointonetwork"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_cointonetwork"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_cointonetwork"` + DetailCode string `form:"detailCodeOrder" search:"type:order;column:detail_code;table:line_cointonetwork"` + NetworkName string `form:"networkNameOrder" search:"type:order;column:network_name;table:line_cointonetwork"` + TokenName string `form:"tokenNameOrder" search:"type:order;column:token_name;table:line_cointonetwork"` + ChargeType string `form:"chargeTypeOrder" search:"type:order;column:charge_type;table:line_cointonetwork"` + RechargeSwitchTime string `form:"rechargeSwitchTimeOrder" search:"type:order;column:recharge_switch_time;table:line_cointonetwork"` + WithdrawSwitchTime string `form:"withdrawSwitchTimeOrder" search:"type:order;column:withdraw_switch_time;table:line_cointonetwork"` + IsoutsideWithdrawVerify string `form:"isoutsideWithdrawVerifyOrder" search:"type:order;column:isoutside_withdraw_verify;table:line_cointonetwork"` + OutsideWithdrawVerifyNum string `form:"outsideWithdrawVerifyNumOrder" search:"type:order;column:outside_withdraw_verify_num;table:line_cointonetwork"` + IsinsideTransferVerify string `form:"isinsideTransferVerifyOrder" search:"type:order;column:isinside_transfer_verify;table:line_cointonetwork"` + InsidetransferVerifyNum string `form:"insidetransferVerifyNumOrder" search:"type:order;column:insidetransfer_verify_num;table:line_cointonetwork"` + EverydaymaxWithdrawNum string `form:"everydaymaxWithdrawNumOrder" search:"type:order;column:everydaymax_withdraw_num;table:line_cointonetwork"` + EverydaymaxVerifyNum string `form:"everydaymaxVerifyNumOrder" search:"type:order;column:everydaymax_verify_num;table:line_cointonetwork"` + Isinsidetransferfee string `form:"isinsidetransferfeeOrder" search:"type:order;column:isinsidetransferfee;table:line_cointonetwork"` + +} + +func (m *LineCointonetworkGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineCointonetworkInsertReq struct { + Id int `json:"-" comment:""` // + CoinId int64 `json:"coinId" comment:"币种id"` + NetworkId int64 `json:"networkId" comment:"公链网络id"` + IsMain int64 `json:"isMain" comment:"是否主网--1否,3是.比如BTC在BTC网络中就属于主网"` + IsDeposit int64 `json:"isDeposit" comment:"是否开启充值:1==否,3==是"` + IsWithdraw int64 `json:"isWithdraw" comment:"是否开启提现:1==否,3==是"` + CoinCode string `json:"coinCode" comment:"币种代号"` + Token string `json:"token" comment:"代币token"` + MinChargeNum string `json:"minChargeNum" comment:"最小充值数量"` + MinOutNum string `json:"minOutNum" comment:"单笔最小提币数量"` + MaxOutNum string `json:"maxOutNum" comment:"单笔最大提币数量"` + TransferFee string `json:"transferFee" comment:"提币手续费"` + DetailCode string `json:"detailCode" comment:"币种全称"` + NetworkName string `json:"networkName" comment:"公链网络简称"` + TokenName string `json:"tokenName" comment:"公链网络全称"` + ChargeType int64 `json:"chargeType" comment:"手续费类型 1==固定 3==百分比"` + RechargeSwitchTime time.Time `json:"rechargeSwitchTime" comment:"充值开关时间"` + WithdrawSwitchTime time.Time `json:"withdrawSwitchTime" comment:"提币开关时间"` + IsoutsideWithdrawVerify int64 `json:"isoutsideWithdrawVerify" comment:"是否开启外部提币免审1==否3==是"` + OutsideWithdrawVerifyNum string `json:"outsideWithdrawVerifyNum" comment:"外部提币免审阈值"` + IsinsideTransferVerify int64 `json:"isinsideTransferVerify" comment:"是否开启内部转账免审1==否3==是"` + InsidetransferVerifyNum string `json:"insidetransferVerifyNum" comment:"内部转账免审阈值"` + EverydaymaxWithdrawNum string `json:"everydaymaxWithdrawNum" comment:"每日最大累计提币数量"` + EverydaymaxVerifyNum string `json:"everydaymaxVerifyNum" comment:"每日最大免审累计数量"` + Isinsidetransferfee int64 `json:"isinsidetransferfee" comment:"是否开启内部转账免手续费1==否3==是"` + common.ControlBy +} + +func (s *LineCointonetworkInsertReq) Generate(model *models.LineCointonetwork) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.CoinId = s.CoinId + model.NetworkId = s.NetworkId + model.IsMain = s.IsMain + model.IsDeposit = s.IsDeposit + model.IsWithdraw = s.IsWithdraw + model.CoinCode = s.CoinCode + model.Token = s.Token + model.MinChargeNum = s.MinChargeNum + model.MinOutNum = s.MinOutNum + model.MaxOutNum = s.MaxOutNum + model.TransferFee = s.TransferFee + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 + model.DetailCode = s.DetailCode + model.NetworkName = s.NetworkName + model.TokenName = s.TokenName + model.ChargeType = s.ChargeType + model.RechargeSwitchTime = s.RechargeSwitchTime + model.WithdrawSwitchTime = s.WithdrawSwitchTime + model.IsoutsideWithdrawVerify = s.IsoutsideWithdrawVerify + model.OutsideWithdrawVerifyNum = s.OutsideWithdrawVerifyNum + model.IsinsideTransferVerify = s.IsinsideTransferVerify + model.InsidetransferVerifyNum = s.InsidetransferVerifyNum + model.EverydaymaxWithdrawNum = s.EverydaymaxWithdrawNum + model.EverydaymaxVerifyNum = s.EverydaymaxVerifyNum + model.Isinsidetransferfee = s.Isinsidetransferfee +} + +func (s *LineCointonetworkInsertReq) GetId() interface{} { + return s.Id +} + +type LineCointonetworkUpdateReq struct { + Id int `uri:"id" comment:""` // + CoinId int64 `json:"coinId" comment:"币种id"` + NetworkId int64 `json:"networkId" comment:"公链网络id"` + IsMain int64 `json:"isMain" comment:"是否主网--1否,3是.比如BTC在BTC网络中就属于主网"` + IsDeposit int64 `json:"isDeposit" comment:"是否开启充值:1==否,3==是"` + IsWithdraw int64 `json:"isWithdraw" comment:"是否开启提现:1==否,3==是"` + CoinCode string `json:"coinCode" comment:"币种代号"` + Token string `json:"token" comment:"代币token"` + MinChargeNum string `json:"minChargeNum" comment:"最小充值数量"` + MinOutNum string `json:"minOutNum" comment:"单笔最小提币数量"` + MaxOutNum string `json:"maxOutNum" comment:"单笔最大提币数量"` + TransferFee string `json:"transferFee" comment:"提币手续费"` + DetailCode string `json:"detailCode" comment:"币种全称"` + NetworkName string `json:"networkName" comment:"公链网络简称"` + TokenName string `json:"tokenName" comment:"公链网络全称"` + ChargeType int64 `json:"chargeType" comment:"手续费类型 1==固定 3==百分比"` + RechargeSwitchTime time.Time `json:"rechargeSwitchTime" comment:"充值开关时间"` + WithdrawSwitchTime time.Time `json:"withdrawSwitchTime" comment:"提币开关时间"` + IsoutsideWithdrawVerify int64 `json:"isoutsideWithdrawVerify" comment:"是否开启外部提币免审1==否3==是"` + OutsideWithdrawVerifyNum string `json:"outsideWithdrawVerifyNum" comment:"外部提币免审阈值"` + IsinsideTransferVerify int64 `json:"isinsideTransferVerify" comment:"是否开启内部转账免审1==否3==是"` + InsidetransferVerifyNum string `json:"insidetransferVerifyNum" comment:"内部转账免审阈值"` + EverydaymaxWithdrawNum string `json:"everydaymaxWithdrawNum" comment:"每日最大累计提币数量"` + EverydaymaxVerifyNum string `json:"everydaymaxVerifyNum" comment:"每日最大免审累计数量"` + Isinsidetransferfee int64 `json:"isinsidetransferfee" comment:"是否开启内部转账免手续费1==否3==是"` + common.ControlBy +} + +func (s *LineCointonetworkUpdateReq) Generate(model *models.LineCointonetwork) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.CoinId = s.CoinId + model.NetworkId = s.NetworkId + model.IsMain = s.IsMain + model.IsDeposit = s.IsDeposit + model.IsWithdraw = s.IsWithdraw + model.CoinCode = s.CoinCode + model.Token = s.Token + model.MinChargeNum = s.MinChargeNum + model.MinOutNum = s.MinOutNum + model.MaxOutNum = s.MaxOutNum + model.TransferFee = s.TransferFee + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 + model.DetailCode = s.DetailCode + model.NetworkName = s.NetworkName + model.TokenName = s.TokenName + model.ChargeType = s.ChargeType + model.RechargeSwitchTime = s.RechargeSwitchTime + model.WithdrawSwitchTime = s.WithdrawSwitchTime + model.IsoutsideWithdrawVerify = s.IsoutsideWithdrawVerify + model.OutsideWithdrawVerifyNum = s.OutsideWithdrawVerifyNum + model.IsinsideTransferVerify = s.IsinsideTransferVerify + model.InsidetransferVerifyNum = s.InsidetransferVerifyNum + model.EverydaymaxWithdrawNum = s.EverydaymaxWithdrawNum + model.EverydaymaxVerifyNum = s.EverydaymaxVerifyNum + model.Isinsidetransferfee = s.Isinsidetransferfee +} + +func (s *LineCointonetworkUpdateReq) GetId() interface{} { + return s.Id +} + +// LineCointonetworkGetReq 功能获取请求参数 +type LineCointonetworkGetReq struct { + Id int `uri:"id"` +} +func (s *LineCointonetworkGetReq) GetId() interface{} { + return s.Id +} + +// LineCointonetworkDeleteReq 功能删除请求参数 +type LineCointonetworkDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineCointonetworkDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/line_direction.go b/app/admin/service/dto/line_direction.go new file mode 100644 index 0000000..310446b --- /dev/null +++ b/app/admin/service/dto/line_direction.go @@ -0,0 +1,170 @@ +package dto + +import ( + "errors" + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" + "go-admin/pkg/utility" + "strings" +) + +type LineDirectionGetPageReq struct { + dto.Pagination `search:"-"` + Symbol string `form:"symbol" search:"type:contains;column:symbol;table:line_direction" comment:"交易对"` + Type int64 `form:"type" search:"type:exact;column:type;table:line_direction" comment:"交易对类型:1=现货,2=合约"` + Direction string `form:"direction" search:"type:contains;column:direction;table:line_direction" comment:"预估方向"` + LineDirectionOrder +} + +type LineDirectionOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_direction"` + Symbol string `form:"symbolOrder" search:"type:order;column:symbol;table:line_direction"` + Type string `form:"typeOrder" search:"type:order;column:type;table:line_direction"` + BuyPoint1 string `form:"buyPoint1Order" search:"type:order;column:buy_point1;table:line_direction"` + BuyPoint2 string `form:"buyPoint2Order" search:"type:order;column:buy_point2;table:line_direction"` + BuyPoint3 string `form:"buyPoint3Order" search:"type:order;column:buy_point3;table:line_direction"` + SellPoint1 string `form:"sellPoint1Order" search:"type:order;column:sell_point1;table:line_direction"` + SellPoint2 string `form:"sellPoint2Order" search:"type:order;column:sell_point2;table:line_direction"` + SellPoint3 string `form:"sellPoint3Order" search:"type:order;column:sell_point3;table:line_direction"` + Direction string `form:"directionOrder" search:"type:order;column:direction;table:line_direction"` + AiAnswer string `form:"aiAnswerOrder" search:"type:order;column:ai_answer;table:line_direction"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_direction"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_direction"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_direction"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_direction"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_direction"` +} + +func (m *LineDirectionGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineDirectionInsertReq struct { + Id int `json:"-" comment:""` // + Symbol string `json:"symbol" comment:"交易对"` + Type int64 `json:"type" comment:"交易对类型:1=现货,2=合约"` + BuyPoint1 string `json:"buyPoint1" comment:"买入点一"` + BuyPoint2 string `json:"buyPoint2" comment:"买入点二"` + BuyPoint3 string `json:"buyPoint3" comment:"买入点三"` + SellPoint1 string `json:"sellPoint1" comment:"卖出点一"` + SellPoint2 string `json:"sellPoint2" comment:"卖出点二"` + SellPoint3 string `json:"sellPoint3" comment:"卖出点三"` + Direction string `json:"direction" comment:"预估方向"` + AiAnswer string `json:"aiAnswer" comment:"AI智能分析"` + common.ControlBy +} + +func (s *LineDirectionInsertReq) Generate(model *models.LineDirection) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.Symbol = s.Symbol + model.Type = s.Type + model.BuyPoint1 = s.BuyPoint1 + model.BuyPoint2 = s.BuyPoint2 + model.BuyPoint3 = s.BuyPoint3 + model.SellPoint1 = s.SellPoint1 + model.SellPoint2 = s.SellPoint2 + model.SellPoint3 = s.SellPoint3 + model.Direction = s.Direction + model.AiAnswer = s.AiAnswer + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *LineDirectionInsertReq) GetId() interface{} { + return s.Id +} + +type LineDirectionUpdateReq struct { + Id int `uri:"id" comment:""` // + Symbol string `json:"symbol" comment:"交易对"` + Type int64 `json:"type" comment:"交易对类型:1=现货,2=合约"` + BuyPoint1 string `json:"buyPoint1" comment:"买入点一"` + BuyPoint2 string `json:"buyPoint2" comment:"买入点二"` + BuyPoint3 string `json:"buyPoint3" comment:"买入点三"` + SellPoint1 string `json:"sellPoint1" comment:"卖出点一"` + SellPoint2 string `json:"sellPoint2" comment:"卖出点二"` + SellPoint3 string `json:"sellPoint3" comment:"卖出点三"` + Direction string `json:"direction" comment:"预估方向"` + AiAnswer string `json:"aiAnswer" comment:"AI智能分析"` + common.ControlBy +} + +func (s *LineDirectionUpdateReq) Generate(model *models.LineDirection) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.Symbol = s.Symbol + model.Type = s.Type + model.BuyPoint1 = s.BuyPoint1 + model.BuyPoint2 = s.BuyPoint2 + model.BuyPoint3 = s.BuyPoint3 + model.SellPoint1 = s.SellPoint1 + model.SellPoint2 = s.SellPoint2 + model.SellPoint3 = s.SellPoint3 + model.Direction = s.Direction + model.AiAnswer = s.AiAnswer + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *LineDirectionUpdateReq) GetId() interface{} { + return s.Id +} + +// LineDirectionGetReq 功能获取请求参数 +type LineDirectionGetReq struct { + Id int `uri:"id"` +} + +func (s *LineDirectionGetReq) GetId() interface{} { + return s.Id +} + +// LineDirectionDeleteReq 功能删除请求参数 +type LineDirectionDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineDirectionDeleteReq) GetId() interface{} { + return s.Ids +} + +type AddDirectionReq struct { + AccountName string `json:"accountName" form:"accountName"` + Password string `json:"password" form:"password"` + Symbol string `json:"symbol" form:"symbol"` + Type string `json:"type" form:"type"` + BuyPoint1 string `json:"buy_point1" form:"buy_point1"` + BuyPoint2 string `json:"buy_point2" form:"buy_point2"` + BuyPoint3 string `json:"buy_point3" form:"buy_point3"` + SellPoint1 string `json:"sell_point1" form:"sell_point1"` + SellPoint2 string `json:"sell_point2" form:"sell_point2"` + SellPoint3 string `json:"sell_point3" form:"sell_point3"` + Direction string `json:"direction" form:"direction"` + AiAnswer string `json:"ai_answer" form:"ai_answer"` +} + +func (a *AddDirectionReq) CheckParams() error { + if a.AccountName == "" || a.Password == "" || a.Symbol == "" || a.Type == "" || a.BuyPoint1 == "" || + a.BuyPoint2 == "" || a.BuyPoint3 == "" || a.SellPoint1 == "" || + a.SellPoint2 == "" || a.SellPoint3 == "" || a.AiAnswer == "" { + return errors.New("参数校验失败 传了空值") + } + + a.BuyPoint1 = strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(a.BuyPoint1, ",", "."), "。", "."), " ", "") + a.BuyPoint2 = strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(a.BuyPoint2, ",", "."), "。", "."), " ", "") + a.BuyPoint3 = strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(a.BuyPoint3, ",", "."), "。", "."), " ", "") + a.SellPoint1 = strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(a.SellPoint1, ",", "."), "。", "."), " ", "") + a.SellPoint2 = strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(a.SellPoint2, ",", "."), "。", "."), " ", "") + a.SellPoint3 = strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(a.SellPoint3, ",", "."), "。", "."), " ", "") + + a.Symbol = utility.ReplaceSuffix(strings.ReplaceAll(a.Symbol, " ", ""), "4小时", "USDT") + a.Symbol = strings.ReplaceAll(a.Symbol, "1小时", "USDT") + a.Symbol = strings.ReplaceAll(a.Symbol, "1日", "USDT") + a.Symbol = strings.ReplaceAll(a.Symbol, "四小时", "USDT") + a.Symbol = strings.ReplaceAll(a.Symbol, "一小时", "USDT") + a.Symbol = strings.ReplaceAll(a.Symbol, "一日", "USDT") + + return nil +} diff --git a/app/admin/service/dto/line_order_template_logs.go b/app/admin/service/dto/line_order_template_logs.go new file mode 100644 index 0000000..9946d9f --- /dev/null +++ b/app/admin/service/dto/line_order_template_logs.go @@ -0,0 +1,104 @@ +package dto + +import ( + + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type LineOrderTemplateLogsGetPageReq struct { + dto.Pagination `search:"-"` + Name string `form:"name" search:"type:exact;column:name;table:line_order_template_logs" comment:"模板名称"` + UserId int64 `form:"userId" search:"type:exact;column:user_id;table:line_order_template_logs" comment:"用户id"` + Type int64 `form:"type" search:"type:exact;column:type;table:line_order_template_logs" comment:"模板类型:1=单独添加;2=批量添加"` + LineOrderTemplateLogsOrder +} + +type LineOrderTemplateLogsOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_order_template_logs"` + Name string `form:"nameOrder" search:"type:order;column:name;table:line_order_template_logs"` + UserId string `form:"userIdOrder" search:"type:order;column:user_id;table:line_order_template_logs"` + Params string `form:"paramsOrder" search:"type:order;column:params;table:line_order_template_logs"` + Type string `form:"typeOrder" search:"type:order;column:type;table:line_order_template_logs"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_order_template_logs"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_order_template_logs"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_order_template_logs"` + Switch string `form:"switchOrder" search:"type:order;column:switch;table:line_order_template_logs"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_order_template_logs"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_order_template_logs"` + +} + +func (m *LineOrderTemplateLogsGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineOrderTemplateLogsInsertReq struct { + Id int `json:"-" comment:"id"` // id + Name string `json:"name" comment:"模板名称"` + UserId int64 `json:"userId" comment:"用户id"` + Params string `json:"params" comment:"参数"` + Type int64 `json:"type" comment:"模板类型:1=单独添加;2=批量添加"` + Switch string `json:"switch" comment:"开关:0=关,1=开"` + common.ControlBy +} + +func (s *LineOrderTemplateLogsInsertReq) Generate(model *models.LineOrderTemplateLogs) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.Name = s.Name + model.UserId = s.UserId + model.Params = s.Params + model.Type = s.Type + model.Switch = s.Switch + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *LineOrderTemplateLogsInsertReq) GetId() interface{} { + return s.Id +} + +type LineOrderTemplateLogsUpdateReq struct { + Id int `uri:"id" comment:"id"` // id + Name string `json:"name" comment:"模板名称"` + UserId int64 `json:"userId" comment:"用户id"` + Params string `json:"params" comment:"参数"` + Type int64 `json:"type" comment:"模板类型:1=单独添加;2=批量添加"` + Switch string `json:"switch" comment:"开关:0=关,1=开"` + common.ControlBy +} + +func (s *LineOrderTemplateLogsUpdateReq) Generate(model *models.LineOrderTemplateLogs) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.Name = s.Name + model.UserId = s.UserId + model.Params = s.Params + model.Type = s.Type + model.Switch = s.Switch + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *LineOrderTemplateLogsUpdateReq) GetId() interface{} { + return s.Id +} + +// LineOrderTemplateLogsGetReq 功能获取请求参数 +type LineOrderTemplateLogsGetReq struct { + Id int `uri:"id"` +} +func (s *LineOrderTemplateLogsGetReq) GetId() interface{} { + return s.Id +} + +// LineOrderTemplateLogsDeleteReq 功能删除请求参数 +type LineOrderTemplateLogsDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineOrderTemplateLogsDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/line_pre_order.go b/app/admin/service/dto/line_pre_order.go new file mode 100644 index 0000000..f022b4d --- /dev/null +++ b/app/admin/service/dto/line_pre_order.go @@ -0,0 +1,486 @@ +package dto + +import ( + "errors" + "strconv" + "time" + + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" + + "github.com/shopspring/decimal" +) + +type LinePreOrderGetPageReq struct { + dto.Pagination `search:"-"` + ExchangeType string `json:"exchangeType" search:"type:exact;column:exchange_type;table:line_pre_order" comment:"交易所类型 字典exchange_type"` + ApiId string `form:"apiId" search:"type:exact;column:api_id;table:line_pre_order" comment:"api用户"` + Symbol string `form:"symbol" search:"type:exact;column:symbol;table:line_pre_order" comment:"交易对"` + QuoteSymbol string `form:"quoteSymbol" search:"type:exact;column:quote_symbol;table:line_pre_order" comment:"计较货币"` + SignPriceType string `form:"signPriceType" search:"type:exact;column:sign_price_type;table:line_pre_order" comment:"对标价类型: new=最新价格;tall=24小时最高;low=24小时最低;mixture=标记价;entrust=委托实价;add=补仓"` + Rate string `form:"rate" search:"type:exact;column:rate;table:line_pre_order" comment:"下单百分比"` + Site string `form:"site" search:"type:exact;column:site;table:line_pre_order" comment:"购买方向:BUY=买;SELL=卖"` + OrderSn string `form:"orderSn" search:"type:exact;column:order_sn;table:line_pre_order" comment:"订单号"` + Status string `form:"status" search:"type:exact;column:status;table:line_pre_order" comment:"是否被触发:0=待触发;1=已触发;2=下单失败;3=已记录;4=已取消;5=委托中;6=已平仓;7=已补单;8=补单失败;9=现货已成交;10=合约已补单;11=合约补单失败;12=合约已成交;13=已开仓"` + OrderType string `form:"orderType" search:"type:exact;column:order_type;table:line_pre_order" comment:"订单类型:1=现货;2=合约;3=合约止盈;4=合约止损;5=现货止盈;6=现货止损;7=止损补仓;8=现货加仓;9=现货平仓;10 = 合约止损补仓,11=合约加仓;12=合约平仓"` + AddPositionStatus int `form:"addPositionStatus" search:"-"` + HedgeStatus int `form:"hedgeStatus" search:"-"` + LinePreOrderOrder +} + +type LinePreOrderOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_pre_order"` + Pid string `form:"pidOrder" search:"type:order;column:pid;table:line_pre_order"` + ApiId string `form:"apiIdOrder" search:"type:order;column:api_id;table:line_pre_order"` + GroupId string `form:"groupIdOrder" search:"type:order;column:group_id;table:line_pre_order"` + Symbol string `form:"symbolOrder" search:"type:order;column:symbol;table:line_pre_order"` + QuoteSymbol string `form:"quoteSymbolOrder" search:"type:order;column:quote_symbol;table:line_pre_order"` + SignPrice string `form:"signPriceOrder" search:"type:order;column:sign_price;table:line_pre_order"` + SignPriceType string `form:"signPriceTypeOrder" search:"type:order;column:sign_price_type;table:line_pre_order"` + Rate string `form:"rateOrder" search:"type:order;column:rate;table:line_pre_order"` + Price string `form:"priceOrder" search:"type:order;column:price;table:line_pre_order"` + Num string `form:"numOrder" search:"type:order;column:num;table:line_pre_order"` + BuyPrice string `form:"buyPriceOrder" search:"type:order;column:buy_price;table:line_pre_order"` + Site string `form:"siteOrder" search:"type:order;column:site;table:line_pre_order"` + OrderSn string `form:"orderSnOrder" search:"type:order;column:order_sn;table:line_pre_order"` + OrderType string `form:"orderTypeOrder" search:"type:order;column:order_type;table:line_pre_order"` + CoverRate string `form:"coverRateOrder" search:"type:order;column:cover_rate;table:line_pre_order"` + Desc string `form:"descOrder" search:"type:order;column:desc;table:line_pre_order"` + Status string `form:"statusOrder" search:"type:order;column:status;table:line_pre_order"` + AdminId string `form:"adminIdOrder" search:"type:order;column:admin_id;table:line_pre_order"` + IsAutoScale string `form:"isAutoScaleOrder" search:"type:order;column:is_auto_scale;table:line_pre_order"` + ScaleType string `form:"scaleTypeOrder" search:"type:order;column:scale_type;table:line_pre_order"` + CloseType string `form:"closeTypeOrder" search:"type:order;column:close_type;table:line_pre_order"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_pre_order"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_pre_order"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_pre_order"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_pre_order"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_pre_order"` + Direction string `form:"directionOrder" search:"type:order;column:direction;table:line_pre_order"` + UpdateTime string `form:"updateTimeOrder" search:"type:order;column:update_time;table:line_pre_order"` +} + +func (m *LinePreOrderGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LinePreOrderInsertReq struct { + Id int `json:"-" comment:"id"` // id + ExchangeType string `json:"exchangeType" comment:"交易所类型 字典exchange_type"` + Pid string `json:"pid" comment:"pid"` + ApiId string `json:"apiId" comment:"api用户"` + GroupId string `json:"groupId" comment:"交易对组id"` + Symbol string `json:"symbol" comment:"交易对"` + QuoteSymbol string `json:"quoteSymbol" comment:"计较货币"` + SignPrice string `json:"signPrice" comment:"对标价"` + SignPriceType string `json:"signPriceType" comment:"对标价类型: new=最新价格;tall=24小时最高;low=24小时最低;mixture=标记价;entrust=委托实价;add=补仓"` + Rate string `json:"rate" comment:"下单百分比"` + Price string `json:"price" comment:"触发价格"` + Num string `json:"num" comment:"购买数量"` + BuyPrice string `json:"buyPrice" comment:"购买金额"` + Site string `json:"site" comment:"购买方向:BUY=买;SELL=卖"` + OrderSn string `json:"orderSn" comment:"订单号"` + OrderType int `json:"orderType" comment:"订单类型:0=主单;1=止盈;2=止损;3=平仓"` + Desc string `json:"desc" comment:"订单描述"` + Status int `json:"status" comment:"是否被触发:0=待触发;1=已触发;2=下单失败;4=已取消;5=委托中;6=已成交;9=已平仓"` + AdminId string `json:"adminId" comment:"操作管理员id"` + IsAutoScale int `json:"isAutoScale" comment:"订单类型:是否为自动加仓 1 = 是 2=手动"` + ScaleType int `json:"scaleType" comment:"是否为普通对冲 1 = 是 2 = 半自动补仓(会自动平B仓)"` + CloseType int `json:"closeType" comment:"平仓类型 是否为盈利平仓 1= 是 0 =否"` + Direction string `json:"direction" comment:"预估方向"` + common.ControlBy +} + +func (s *LinePreOrderInsertReq) Generate(model *models.LinePreOrder) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + pid, _ := strconv.Atoi(s.Pid) + model.Pid = pid + model.ApiId, _ = strconv.Atoi(s.ApiId) + model.GroupId = s.GroupId + model.Symbol = s.Symbol + model.QuoteSymbol = s.QuoteSymbol + model.SignPrice = s.SignPrice + model.SignPriceType = s.SignPriceType + model.Rate = s.Rate + model.Price = s.Price + model.Num = s.Num + model.BuyPrice = s.BuyPrice + model.Site = s.Site + model.OrderSn = s.OrderSn + model.OrderType = s.OrderType + model.Desc = s.Desc + model.Status = s.Status + model.AdminId = s.AdminId + model.CloseType = s.CloseType + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *LinePreOrderInsertReq) GetId() interface{} { + return s.Id +} + +type LinePreOrderUpdateReq struct { + Id int `uri:"id" comment:"id"` // id + ExchangeType string `json:"exchangeType" comment:"交易所类型 字典exchange_type"` + Pid string `json:"pid" comment:"pid"` + ApiId string `json:"apiId" comment:"api用户"` + GroupId string `json:"groupId" comment:"交易对组id"` + Symbol string `json:"symbol" comment:"交易对"` + QuoteSymbol string `json:"quoteSymbol" comment:"计较货币"` + SignPrice string `json:"signPrice" comment:"对标价"` + SignPriceType string `json:"signPriceType" comment:"对标价类型: new=最新价格;tall=24小时最高;low=24小时最低;mixture=标记价;entrust=委托实价;add=补仓"` + Rate string `json:"rate" comment:"下单百分比"` + Price string `json:"price" comment:"触发价格"` + Num string `json:"num" comment:"购买数量"` + BuyPrice string `json:"buyPrice" comment:"购买金额"` + Site string `json:"site" comment:"购买方向:BUY=买;SELL=卖"` + OrderSn string `json:"orderSn" comment:"订单号"` + OrderType int `json:"orderType" comment:"订单类型:0=主单;1=止盈;2=止损;3=平仓"` + Desc string `json:"desc" comment:"订单描述"` + Status int `json:"status" comment:"是否被触发:0=待触发;1=已触发;2=下单失败;4=已取消;5=委托中;6=已成交;9=已平仓"` + AdminId string `json:"adminId" comment:"操作管理员id"` + CloseType int `json:"closeType" comment:"平仓类型 是否为盈利平仓 1= 是 0 =否"` + UpdateTime time.Time `json:"updateTime" comment:"修改时间"` + common.ControlBy +} + +func (s *LinePreOrderUpdateReq) Generate(model *models.LinePreOrder) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + pid, _ := strconv.Atoi(s.Pid) + model.Pid = pid + model.ExchangeType = s.ExchangeType + model.ApiId, _ = strconv.Atoi(s.ApiId) + model.GroupId = s.GroupId + model.Symbol = s.Symbol + model.QuoteSymbol = s.QuoteSymbol + model.SignPrice = s.SignPrice + model.SignPriceType = s.SignPriceType + model.Rate = s.Rate + model.Price = s.Price + model.Num = s.Num + model.BuyPrice = s.BuyPrice + model.Site = s.Site + model.OrderSn = s.OrderSn + model.OrderType = s.OrderType + model.Desc = s.Desc + model.Status = s.Status + model.AdminId = s.AdminId + model.CloseType = s.CloseType + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *LinePreOrderUpdateReq) GetId() interface{} { + return s.Id +} + +// LinePreOrderGetReq 功能获取请求参数 +type LinePreOrderGetReq struct { + Id int `uri:"id"` +} + +func (s *LinePreOrderGetReq) GetId() interface{} { + return s.Id +} + +// LinePreOrderDeleteReq 功能删除请求参数 +type LinePreOrderDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LinePreOrderDeleteReq) GetId() interface{} { + return s.Ids +} + +type LineAddPreOrderReq struct { + ExchangeType string `json:"exchange_type"` //交易所类型 + OrderType int `json:"order_type"` //订单类型 + Symbol string `json:"symbol"` //交易对 + ApiUserId string `json:"api_id"` //下单用户 + Site string `json:"site"` //购买方向 + BuyPrice string `json:"buy_price"` //购买金额 U + PricePattern string `json:"price_pattern"` //价格模式 + Price string `json:"price"` //下单价百分比 + Profit string `json:"profit"` //止盈价 + StopPrice string `json:"stop_price"` //止损价 + PriceType string `json:"price_type"` //价格类型 + SaveTemplate string `json:"save_template"` //是否保存模板 + TemplateName string `json:"template_name"` //模板名字 + SymbolType int `json:"symbol_type"` //交易对类型 1-现货 2-合约 + CoverType int `json:"cover_type"` //对冲类型 1= 现货对合约 2=合约对合约 3 合约对现货 + ExpireHour int `json:"expire_hour"` // 过期时间 单位小时 + MainOrderType string `json:"main_order_type"` //主单类型:限价(LIMIT)或市价(MARKET) + HedgeOrderType string `json:"hedge_order_type"` //第二笔(对冲单)类型:限价(LIMIT)或市价(MARKET) +} + +func (req LineAddPreOrderReq) CheckParams() error { + if req.ExchangeType == "" { + return errors.New("请选择交易所") + } + + if req.ApiUserId == "" { + return errors.New("选择下单用户") + } + if req.Symbol == "" { + return errors.New("请选择交易对") + } + + if req.Price == "" { + return errors.New("输出下单价") + } + + if req.PricePattern == "" { + return errors.New("请选择价格类型") + } + + return nil +} + +// LineBatchAddPreOrderReq 批量添加订单请求参数 +type LineBatchAddPreOrderReq struct { + ExchangeType string `json:"exchange_type"` //交易所类型 字典exchange_type + SymbolType int `json:"symbolType"` //主单交易对类型 0-现货 1-合约 + OrderType int `json:"order_type"` //订单类型 + SymbolGroupId string `json:"symbol_group_id"` //交易对组id + Symbol string `json:"symbol"` //交易对 + ApiUserId string `json:"api_id"` //下单用户 + Site string `json:"site"` //购买方向 + BuyPrice string `json:"buy_price"` //购买金额 U + PricePattern string `json:"price_pattern"` //价格模式 + Price string `json:"price"` //下单价百分比 + Profit string `json:"profit"` //止盈价 + StopPrice string `json:"stop_price"` //止损价 + PriceType string `json:"price_type"` //价格类型 + SaveTemplate string `json:"save_template"` //是否保存模板 + TemplateName string `json:"template_name"` //模板名字 + OrderNum int `json:"order_num"` //脚本运行次数 + Script string `json:"script"` //是否是脚本运行 1 = 是 0= 否 + CoverType int `json:"cover_type"` //对冲类型 1= 现货对合约 2=合约对合约 3 合约对现货 + ExpireHour int `json:"expire_hour"` // 过期时间 单位小时 + MainOrderType string `json:"main_order_type"` //主单类型:限价(LIMIT)或市价(MARKET) + HedgeOrderType string `json:"hedge_order_type"` //第二笔(对冲单)类型:限价(LIMIT)或市价(MARKET) +} + +func (req LineBatchAddPreOrderReq) CheckParams() error { + if req.ExchangeType == "" { + return errors.New("请选择交易所") + } + + if req.ApiUserId == "" { + return errors.New("选择下单用户") + } + if req.SymbolGroupId == "" { + return errors.New("请选择交易对组") + } + + if req.Price == "" { + return errors.New("输出下单价") + } + + if req.PricePattern == "" { + return errors.New("请选择价格类型") + } + + return nil +} + +type Ticker struct { + Symbol string `json:"symbol"` + Price string `json:"price"` +} +type QuickAddPreOrderReq struct { + Ids string `json:"ids"` +} + +// LeverReq 设置杠杆请求 +type LeverReq struct { + ApiUserIds string `json:"api_user_ids"` + Symbol string `json:"symbol"` + Leverage int `json:"leverage"` + GroupId int `json:"group_id"` + IsAll int `json:"is_all"` // 1= 全部 0=不是全部 +} + +func (c LeverReq) CheckParams() error { + if c.Leverage > 125 || c.Leverage < 1 { + return errors.New("请输入1-125的杠杆倍数") + } + if c.ApiUserIds == "" { + return errors.New("请选择api用户") + } + return nil +} + +type MarginTypeReq struct { + ApiUserIds string `json:"api_user_ids"` + Symbol string `json:"symbol"` // 交易对 + MarginType string `json:"margin_type"` //全仓 CROSSED 逐仓 ISOLATED + GroupId int `json:"group_id"` // 交易对组id + IsAll int `json:"is_all"` // 1= 全部 0=不是全部 +} + +type CancelOpenOrderReq struct { + ApiId int `json:"api_id"` + Symbol string `json:"symbol"` + OrderSn string `json:"order_sn"` + OrderType int `json:"order_type"` +} + +type GetChildOrderReq struct { + Id int `json:"id"` //id +} + +func (c MarginTypeReq) CheckParams() error { + if c.Symbol == "" && c.GroupId == 0 { + return errors.New("请选择交易对或交易对组") + } + if c.MarginType == "" { + return errors.New("请选择仓位模式") + } + if c.ApiUserIds == "" { + return errors.New("请选择api用户") + } + return nil +} + +type PreOrderRedisList struct { + Id int `json:"id"` + Symbol string `json:"symbol"` + Price string `json:"price"` + Site string `json:"site"` + ApiId int `json:"api_id"` + OrderSn string `json:"order_sn"` + QuoteSymbol string `json:"quote_symbol"` +} + +type StopLossRedisList struct { + // Id int `json:"id"` + PId int `json:"pid"` + OrderTye string `json:"orderType"` + // Num decimal.Decimal `json:"num"` + + Symbol string `json:"symbol"` + Price decimal.Decimal `json:"price"` //触发价(根据主单价格触发) + Site string `json:"site"` + ApiId int `json:"api_id"` + Stoploss decimal.Decimal `json:"stoploss"` //亏损百分比对冲 +} + +// ManuallyCover 手动加仓请求体 +type ManuallyCover struct { + ApiId int `json:"api_id"` //主账号id + CoverAccount int `json:"cover_account"` // 1=加主账号 2=加付帐号 3=都加 + SymbolType int `json:"symbol_type"` //加仓交易对类型 1=现货 2=合约 + Symbols string `json:"symbols"` //加仓交易对 -> ETHUSDT,BTCUSDT + CoverAccountAType string `json:"cover_account_a_type"` //加仓A账号订单类型 MARKET=市价 LIMIT=限价 + CoverAccountARate string `json:"cover_account_a_rate"` //加仓A账号限价比例 限价才有比例 + CoverAccountBType string `json:"cover_account_b_type"` //加仓b账号订单类型 MARKET=市价 LIMIT=限价 + CoverAccountBRate string `json:"cover_account_b_rate"` //加仓b账号限价比例 限价才有比例 + CoverType int `json:"cover_type"` //1 = 百分比 2=金额 + Value string `json:"value"` //加仓数值 +} + +func (m *ManuallyCover) CheckParams() error { + if m.ApiId <= 0 { + return errors.New("请选择主账号") + } + + if m.CoverAccount <= 0 { + return errors.New("请选择加仓账号") + } + + if len(m.Symbols) == 0 { + return errors.New("请选择加仓交易对") + } + + if m.CoverType <= 0 { + return errors.New("请填写加仓类型") + } + if len(m.Value) == 0 { + return errors.New("请填写加仓数值") + } + return nil +} + +// ClosePosition 平仓请求 +type ClosePosition struct { + ApiId int `json:"api_id"` //api-user 用户id + Symbol string `json:"symbol"` // 交易对 + Rate string `json:"rate"` //限价平仓百分比 + CloseType int `json:"close_type"` //现货平仓=1 合约平仓=2 +} + +func (m *ClosePosition) CheckParams() error { + if m.ApiId <= 0 { + return errors.New("请选择账号") + } + + if m.CloseType <= 0 && m.CloseType != 1 && m.CloseType != 2 { + return errors.New("请选择平仓类型") + } + + return nil +} + +type QueryOrderReq struct { + OrderType int `json:"order_type"` // 订单类型 + ApiId int `json:"api_id"` //用户api_id + OrderSn string `json:"order_sn"` //订单号 + Symbol string `json:"symbol"` //交易对 +} + +type SpotQueryOrderResp struct { + Symbol string `json:"symbol"` + OrderId int `json:"orderId"` + OrderListId int `json:"orderListId"` + ClientOrderId string `json:"clientOrderId"` + Price string `json:"price"` + OrigQty string `json:"origQty"` + ExecutedQty string `json:"executedQty"` + OrigQuoteOrderQty string `json:"origQuoteOrderQty"` + CummulativeQuoteQty string `json:"cummulativeQuoteQty"` + Status string `json:"status"` + TimeInForce string `json:"timeInForce"` + Type string `json:"type"` + Side string `json:"side"` + StopPrice string `json:"stopPrice"` + IcebergQty string `json:"icebergQty"` + Time int64 `json:"time"` + UpdateTime int64 `json:"updateTime"` + IsWorking bool `json:"isWorking"` + WorkingTime int64 `json:"workingTime"` + SelfTradePreventionMode string `json:"selfTradePreventionMode"` +} + +type FutQueryOrderResp struct { + AvgPrice string `json:"avgPrice"` + ClientOrderId string `json:"clientOrderId"` + CumQuote string `json:"cumQuote"` + ExecutedQty string `json:"executedQty"` + OrderId int `json:"orderId"` + OrigQty string `json:"origQty"` + OrigType string `json:"origType"` + Price string `json:"price"` + ReduceOnly bool `json:"reduceOnly"` + Side string `json:"side"` + PositionSide string `json:"positionSide"` + Status string `json:"status"` + StopPrice string `json:"stopPrice"` + ClosePosition bool `json:"closePosition"` + Symbol string `json:"symbol"` + Time int64 `json:"time"` + TimeInForce string `json:"timeInForce"` + Type string `json:"type"` + ActivatePrice string `json:"activatePrice"` + PriceRate string `json:"priceRate"` + UpdateTime int64 `json:"updateTime"` + WorkingType string `json:"workingType"` + PriceProtect bool `json:"priceProtect"` + PriceMatch string `json:"priceMatch"` + SelfTradePreventionMode string `json:"selfTradePreventionMode"` + GoodTillDate int `json:"goodTillDate"` +} diff --git a/app/admin/service/dto/line_pre_order_status.go b/app/admin/service/dto/line_pre_order_status.go new file mode 100644 index 0000000..e45bcf9 --- /dev/null +++ b/app/admin/service/dto/line_pre_order_status.go @@ -0,0 +1,95 @@ +package dto + +import ( + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type LinePreOrderStatusGetPageReq struct { + dto.Pagination `search:"-"` + LinePreOrderStatusOrder +} + +type LinePreOrderStatusOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_pre_order_status"` + OrderId string `form:"orderIdOrder" search:"type:order;column:order_id;table:line_pre_order_status"` + OrderSn string `form:"orderSnOrder" search:"type:order;column:order_sn;table:line_pre_order_status"` + AddPositionStatus string `form:"addPositionStatusOrder" search:"type:order;column:add_position_status;table:line_pre_order_status"` + HedgeStatus string `form:"hedgeStatusOrder" search:"type:order;column:hedge_status;table:line_pre_order_status"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_pre_order_status"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_pre_order_status"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_pre_order_status"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_pre_order_status"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_pre_order_status"` +} + +func (m *LinePreOrderStatusGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LinePreOrderStatusInsertReq struct { + Id int `json:"-" comment:"主键"` // 主键 + OrderId int `json:"orderId" comment:"主订单id"` + OrderSn string `json:"orderSn" comment:"主订单号"` + AddPositionStatus int `json:"addPositionStatus" comment:"加仓状态 0-无 1-已加仓"` + HedgeStatus int `json:"hedgeStatus" comment:"对冲状态 0-无 1-已对冲"` + common.ControlBy +} + +func (s *LinePreOrderStatusInsertReq) Generate(model *models.LinePreOrderStatus) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.OrderId = s.OrderId + model.OrderSn = s.OrderSn + model.AddPositionStatus = s.AddPositionStatus + model.HedgeStatus = s.HedgeStatus + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *LinePreOrderStatusInsertReq) GetId() interface{} { + return s.Id +} + +type LinePreOrderStatusUpdateReq struct { + Id int `uri:"id" comment:"主键"` // 主键 + OrderId int `json:"orderId" comment:"主订单id"` + OrderSn string `json:"orderSn" comment:"主订单号"` + AddPositionStatus int `json:"addPositionStatus" comment:"加仓状态 0-无 1-已加仓"` + HedgeStatus int `json:"hedgeStatus" comment:"对冲状态 0-无 1-已对冲"` + common.ControlBy +} + +func (s *LinePreOrderStatusUpdateReq) Generate(model *models.LinePreOrderStatus) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.OrderId = s.OrderId + model.OrderSn = s.OrderSn + model.AddPositionStatus = s.AddPositionStatus + model.HedgeStatus = s.HedgeStatus + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *LinePreOrderStatusUpdateReq) GetId() interface{} { + return s.Id +} + +// LinePreOrderStatusGetReq 功能获取请求参数 +type LinePreOrderStatusGetReq struct { + Id int `uri:"id"` +} + +func (s *LinePreOrderStatusGetReq) GetId() interface{} { + return s.Id +} + +// LinePreOrderStatusDeleteReq 功能删除请求参数 +type LinePreOrderStatusDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LinePreOrderStatusDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/line_pre_script.go b/app/admin/service/dto/line_pre_script.go new file mode 100644 index 0000000..d30697a --- /dev/null +++ b/app/admin/service/dto/line_pre_script.go @@ -0,0 +1,108 @@ +package dto + +import ( + + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type LinePreScriptGetPageReq struct { + dto.Pagination `search:"-"` + ApiId int64 `form:"apiId" search:"type:exact;column:api_id;table:line_pre_script" comment:"api用户"` + Status string `form:"status" search:"type:exact;column:status;table:line_pre_script" comment:"执行状态:0=等待执行,1=执行中,2=执行结束"` + LinePreScriptOrder +} + +type LinePreScriptOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_pre_script"` + ApiId string `form:"apiIdOrder" search:"type:order;column:api_id;table:line_pre_script"` + ScriptNum string `form:"scriptNumOrder" search:"type:order;column:script_num;table:line_pre_script"` + ScriptParams string `form:"scriptParamsOrder" search:"type:order;column:script_params;table:line_pre_script"` + Status string `form:"statusOrder" search:"type:order;column:status;table:line_pre_script"` + Desc string `form:"descOrder" search:"type:order;column:desc;table:line_pre_script"` + AdminId string `form:"adminIdOrder" search:"type:order;column:admin_id;table:line_pre_script"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_pre_script"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_pre_script"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_pre_script"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_pre_script"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_pre_script"` + +} + +func (m *LinePreScriptGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LinePreScriptInsertReq struct { + Id int `json:"-" comment:""` // + ApiId int64 `json:"apiId" comment:"api用户"` + ScriptNum int64 `json:"scriptNum" comment:"脚本批次"` + ScriptParams string `json:"scriptParams" comment:"脚本参数"` + Status string `json:"status" comment:"执行状态:0=等待执行,1=执行中,2=执行结束"` + Desc string `json:"desc" comment:"运行备注"` + AdminId int64 `json:"adminId" comment:"管理员id"` + common.ControlBy +} + +func (s *LinePreScriptInsertReq) Generate(model *models.LinePreScript) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.ApiId = s.ApiId + model.ScriptNum = s.ScriptNum + model.ScriptParams = s.ScriptParams + model.Status = s.Status + model.Desc = s.Desc + model.AdminId = s.AdminId + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *LinePreScriptInsertReq) GetId() interface{} { + return s.Id +} + +type LinePreScriptUpdateReq struct { + Id int `uri:"id" comment:""` // + ApiId int64 `json:"apiId" comment:"api用户"` + ScriptNum int64 `json:"scriptNum" comment:"脚本批次"` + ScriptParams string `json:"scriptParams" comment:"脚本参数"` + Status string `json:"status" comment:"执行状态:0=等待执行,1=执行中,2=执行结束"` + Desc string `json:"desc" comment:"运行备注"` + AdminId int64 `json:"adminId" comment:"管理员id"` + common.ControlBy +} + +func (s *LinePreScriptUpdateReq) Generate(model *models.LinePreScript) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.ApiId = s.ApiId + model.ScriptNum = s.ScriptNum + model.ScriptParams = s.ScriptParams + model.Status = s.Status + model.Desc = s.Desc + model.AdminId = s.AdminId + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *LinePreScriptUpdateReq) GetId() interface{} { + return s.Id +} + +// LinePreScriptGetReq 功能获取请求参数 +type LinePreScriptGetReq struct { + Id int `uri:"id"` +} +func (s *LinePreScriptGetReq) GetId() interface{} { + return s.Id +} + +// LinePreScriptDeleteReq 功能删除请求参数 +type LinePreScriptDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LinePreScriptDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/line_price_limit.go b/app/admin/service/dto/line_price_limit.go new file mode 100644 index 0000000..a7a78fd --- /dev/null +++ b/app/admin/service/dto/line_price_limit.go @@ -0,0 +1,144 @@ +package dto + +import ( + "github.com/shopspring/decimal" + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type LinePriceLimitGetPageReq struct { + dto.Pagination `search:"-"` + Symbol string `form:"symbol" search:"type:exact;column:symbol;table:line_price_limit" comment:"交易对"` + Type string `form:"type" search:"type:exact;column:type;table:line_price_limit" comment:"类型:1=现货,2=合约"` + DirectionStatus string `form:"directionStatus" search:"type:exact;column:direction_status;table:line_price_limit" comment:"方向:1=涨,2=跌"` + Range string `form:"range" search:"type:exact;column:range;table:line_price_limit" comment:"幅度"` + StartRange string `form:"startRange" search:"-" comment:"幅度"` + EndRange string `form:"endRange" search:"-" comment:"幅度"` + LinePriceLimitOrder +} + +type LinePriceLimitOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_price_limit"` + Symbol string `form:"symbolOrder" search:"type:order;column:symbol;table:line_price_limit"` + Type string `form:"typeOrder" search:"type:order;column:type;table:line_price_limit"` + DirectionStatus string `form:"directionStatusOrder" search:"type:order;column:direction_status;table:line_price_limit"` + Range string `form:"rangeOrder" search:"type:order;column:range;table:line_price_limit"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_price_limit"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_price_limit"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_price_limit"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_price_limit"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_price_limit"` +} + +func (m *LinePriceLimitGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LinePriceLimitInsertReq struct { + Id int `json:"-" comment:"id"` // id + Symbol string `json:"symbol" comment:"交易对"` + Type string `json:"type" comment:"类型:1=现货,2=合约"` + DirectionStatus string `json:"directionStatus" comment:"方向:1=涨,2=跌"` + Range decimal.Decimal `json:"range" comment:"幅度"` + common.ControlBy +} + +func (s *LinePriceLimitInsertReq) Generate(model *models.LinePriceLimit) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.Symbol = s.Symbol + model.Type = s.Type + model.DirectionStatus = s.DirectionStatus + model.Range = s.Range + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *LinePriceLimitInsertReq) GetId() interface{} { + return s.Id +} + +type LinePriceLimitUpdateReq struct { + Id int `uri:"id" comment:"id"` // id + Symbol string `json:"symbol" comment:"交易对"` + Type string `json:"type" comment:"类型:1=现货,2=合约"` + DirectionStatus string `json:"directionStatus" comment:"方向:1=涨,2=跌"` + Range decimal.Decimal `json:"range" comment:"幅度"` + common.ControlBy +} + +func (s *LinePriceLimitUpdateReq) Generate(model *models.LinePriceLimit) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.Symbol = s.Symbol + model.Type = s.Type + model.DirectionStatus = s.DirectionStatus + model.Range = s.Range + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *LinePriceLimitUpdateReq) GetId() interface{} { + return s.Id +} + +// LinePriceLimitGetReq 功能获取请求参数 +type LinePriceLimitGetReq struct { + Id int `uri:"id"` +} + +func (s *LinePriceLimitGetReq) GetId() interface{} { + return s.Id +} + +// LinePriceLimitDeleteReq 功能删除请求参数 +type LinePriceLimitDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LinePriceLimitDeleteReq) GetId() interface{} { + return s.Ids +} + +type Ticker24Hr struct { + Symbol string `json:"symbol"` + PriceChange string `json:"priceChange"` + PriceChangePercent string `json:"priceChangePercent"` + WeightedAvgPrice string `json:"weightedAvgPrice"` + PrevClosePrice string `json:"prevClosePrice"` + LastPrice string `json:"lastPrice"` + LastQty string `json:"lastQty"` + BidPrice string `json:"bidPrice"` + BidQty string `json:"bidQty"` + AskPrice string `json:"askPrice"` + AskQty string `json:"askQty"` + OpenPrice string `json:"openPrice"` + HighPrice string `json:"highPrice"` + LowPrice string `json:"lowPrice"` + Volume string `json:"volume"` + QuoteVolume string `json:"quoteVolume"` + OpenTime int64 `json:"openTime"` + CloseTime int64 `json:"closeTime"` + FirstId int `json:"firstId"` + LastId int `json:"lastId"` + Count int `json:"count"` +} +type FutTicker24Hr struct { + Symbol string `json:"symbol"` + PriceChange string `json:"priceChange"` + PriceChangePercent string `json:"priceChangePercent"` + WeightedAvgPrice string `json:"weightedAvgPrice"` + LastPrice string `json:"lastPrice"` + LastQty string `json:"lastQty"` + OpenPrice string `json:"openPrice"` + HighPrice string `json:"highPrice"` + LowPrice string `json:"lowPrice"` + Volume string `json:"volume"` + QuoteVolume string `json:"quoteVolume"` + OpenTime int64 `json:"openTime"` + CloseTime int64 `json:"closeTime"` + FirstId int `json:"firstId"` + LastId int `json:"lastId"` + Count int `json:"count"` +} diff --git a/app/admin/service/dto/line_recharge.go b/app/admin/service/dto/line_recharge.go new file mode 100644 index 0000000..e245ab3 --- /dev/null +++ b/app/admin/service/dto/line_recharge.go @@ -0,0 +1,162 @@ +package dto + +import ( + "time" + + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type LineRechargeGetPageReq struct { + dto.Pagination `search:"-"` + LineRechargeOrder +} + +type LineRechargeOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_recharge"` + CoinId string `form:"coinIdOrder" search:"type:order;column:coin_id;table:line_recharge"` + UserId string `form:"userIdOrder" search:"type:order;column:user_id;table:line_recharge"` + Confirms string `form:"confirmsOrder" search:"type:order;column:confirms;table:line_recharge"` + TranType string `form:"tranTypeOrder" search:"type:order;column:tran_type;table:line_recharge"` + BlockIndex string `form:"blockIndexOrder" search:"type:order;column:block_index;table:line_recharge"` + Amount string `form:"amountOrder" search:"type:order;column:amount;table:line_recharge"` + Account string `form:"accountOrder" search:"type:order;column:account;table:line_recharge"` + Address string `form:"addressOrder" search:"type:order;column:address;table:line_recharge"` + Txid string `form:"txidOrder" search:"type:order;column:txid;table:line_recharge"` + BlockTime string `form:"blockTimeOrder" search:"type:order;column:block_time;table:line_recharge"` + TimeReceived string `form:"timeReceivedOrder" search:"type:order;column:time_received;table:line_recharge"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_recharge"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_recharge"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_recharge"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_recharge"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_recharge"` + MainCoin string `form:"mainCoinOrder" search:"type:order;column:main_coin;table:line_recharge"` + OrderNo string `form:"orderNoOrder" search:"type:order;column:order_no;table:line_recharge"` + Status string `form:"statusOrder" search:"type:order;column:status;table:line_recharge"` + State string `form:"stateOrder" search:"type:order;column:state;table:line_recharge"` + Fee string `form:"feeOrder" search:"type:order;column:fee;table:line_recharge"` + AddressFrom string `form:"addressFromOrder" search:"type:order;column:address_from;table:line_recharge"` + +} + +func (m *LineRechargeGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineRechargeInsertReq struct { + Id int `json:"-" comment:"主键"` // 主键 + CoinId int64 `json:"coinId" comment:"币种id"` + UserId int64 `json:"userId" comment:"用户Id"` + Confirms string `json:"confirms" comment:"区块确认数"` + TranType string `json:"tranType" comment:"类型:1线上,2内部"` + BlockIndex string `json:"blockIndex" comment:"区块高度"` + Amount string `json:"amount" comment:"数量"` + Account string `json:"account" comment:"账户"` + Address string `json:"address" comment:"地址"` + Txid string `json:"txid" comment:"交易id"` + BlockTime time.Time `json:"blockTime" comment:"同步时间"` + TimeReceived time.Time `json:"timeReceived" comment:"确认时间"` + MainCoin string `json:"mainCoin" comment:"充值网络"` + OrderNo string `json:"orderNo" comment:"订单号"` + Status string `json:"status" comment:"状态1==进行中暂时保留,2==成功,3==失败"` + State string `json:"state" comment:"来源状态 0 待審核 1 審核成功 2 審核駁回 3交易成功 4交易失敗"` + Fee string `json:"fee" comment:"手续费"` + AddressFrom string `json:"addressFrom" comment:"来源地址"` + common.ControlBy +} + +func (s *LineRechargeInsertReq) Generate(model *models.LineRecharge) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.CoinId = s.CoinId + model.UserId = s.UserId + model.Confirms = s.Confirms + model.TranType = s.TranType + model.BlockIndex = s.BlockIndex + model.Amount = s.Amount + model.Account = s.Account + model.Address = s.Address + model.Txid = s.Txid + model.BlockTime = s.BlockTime + model.TimeReceived = s.TimeReceived + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 + model.MainCoin = s.MainCoin + model.OrderNo = s.OrderNo + model.Status = s.Status + model.State = s.State + model.Fee = s.Fee + model.AddressFrom = s.AddressFrom +} + +func (s *LineRechargeInsertReq) GetId() interface{} { + return s.Id +} + +type LineRechargeUpdateReq struct { + Id int `uri:"id" comment:"主键"` // 主键 + CoinId int64 `json:"coinId" comment:"币种id"` + UserId int64 `json:"userId" comment:"用户Id"` + Confirms string `json:"confirms" comment:"区块确认数"` + TranType string `json:"tranType" comment:"类型:1线上,2内部"` + BlockIndex string `json:"blockIndex" comment:"区块高度"` + Amount string `json:"amount" comment:"数量"` + Account string `json:"account" comment:"账户"` + Address string `json:"address" comment:"地址"` + Txid string `json:"txid" comment:"交易id"` + BlockTime time.Time `json:"blockTime" comment:"同步时间"` + TimeReceived time.Time `json:"timeReceived" comment:"确认时间"` + MainCoin string `json:"mainCoin" comment:"充值网络"` + OrderNo string `json:"orderNo" comment:"订单号"` + Status string `json:"status" comment:"状态1==进行中暂时保留,2==成功,3==失败"` + State string `json:"state" comment:"来源状态 0 待審核 1 審核成功 2 審核駁回 3交易成功 4交易失敗"` + Fee string `json:"fee" comment:"手续费"` + AddressFrom string `json:"addressFrom" comment:"来源地址"` + common.ControlBy +} + +func (s *LineRechargeUpdateReq) Generate(model *models.LineRecharge) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.CoinId = s.CoinId + model.UserId = s.UserId + model.Confirms = s.Confirms + model.TranType = s.TranType + model.BlockIndex = s.BlockIndex + model.Amount = s.Amount + model.Account = s.Account + model.Address = s.Address + model.Txid = s.Txid + model.BlockTime = s.BlockTime + model.TimeReceived = s.TimeReceived + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 + model.MainCoin = s.MainCoin + model.OrderNo = s.OrderNo + model.Status = s.Status + model.State = s.State + model.Fee = s.Fee + model.AddressFrom = s.AddressFrom +} + +func (s *LineRechargeUpdateReq) GetId() interface{} { + return s.Id +} + +// LineRechargeGetReq 功能获取请求参数 +type LineRechargeGetReq struct { + Id int `uri:"id"` +} +func (s *LineRechargeGetReq) GetId() interface{} { + return s.Id +} + +// LineRechargeDeleteReq 功能删除请求参数 +type LineRechargeDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineRechargeDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/line_symbol.go b/app/admin/service/dto/line_symbol.go new file mode 100644 index 0000000..757a16e --- /dev/null +++ b/app/admin/service/dto/line_symbol.go @@ -0,0 +1,109 @@ +package dto + +import ( + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type LineSymbolGetPageReq struct { + dto.Pagination `search:"-"` + Symbol string `form:"symbol" search:"type:contains;column:symbol;table:line_symbol" comment:"交易对"` + BaseAsset string `form:"baseAsset" search:"type:contains;column:base_asset;table:line_symbol" comment:"基础货币"` + QuoteAsset string `form:"quoteAsset" search:"type:exact;column:quote_asset;table:line_symbol" comment:"计价货币"` + Type string `form:"type" search:"type:exact;column:type;table:line_symbol" comment:"交易对类型"` + LineSymbolOrder +} + +type LineSymbolOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_symbol"` + ApiId string `form:"apiIdOrder" search:"type:order;column:api_id;table:line_symbol"` + Symbol string `form:"symbolOrder" search:"type:order;column:symbol;table:line_symbol"` + BaseAsset string `form:"baseAssetOrder" search:"type:order;column:base_asset;table:line_symbol"` + QuoteAsset string `form:"quoteAssetOrder" search:"type:order;column:quote_asset;table:line_symbol"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_symbol"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_symbol"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_symbol"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_symbol"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_symbol"` + Switch string `form:"switchOrder" search:"type:order;column:switch;table:line_symbol"` + Type string `form:"typeOrder" search:"type:order;column:type;table:line_symbol"` +} + +func (m *LineSymbolGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineSymbolInsertReq struct { + Id int `json:"-" comment:"id"` // id + ApiId string `json:"apiId" comment:"api账户id"` + Symbol string `json:"symbol" comment:"交易对"` + BaseAsset string `json:"baseAsset" comment:"基础货币"` + QuoteAsset string `json:"quoteAsset" comment:"计价货币"` + Switch string `json:"switch" comment:"状态"` + Type string `json:"type" comment:"交易对类型"` + common.ControlBy +} + +func (s *LineSymbolInsertReq) Generate(model *models.LineSymbol) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.ApiId = s.ApiId + model.Symbol = s.Symbol + model.BaseAsset = s.BaseAsset + model.QuoteAsset = s.QuoteAsset + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 + model.Switch = s.Switch + model.Type = s.Type +} + +func (s *LineSymbolInsertReq) GetId() interface{} { + return s.Id +} + +type LineSymbolUpdateReq struct { + Id int `uri:"id" comment:"id"` // id + ApiId string `json:"apiId" comment:"api账户id"` + Symbol string `json:"symbol" comment:"交易对"` + BaseAsset string `json:"baseAsset" comment:"基础货币"` + QuoteAsset string `json:"quoteAsset" comment:"计价货币"` + Switch string `json:"switch" comment:"状态"` + Type string `json:"type" comment:"交易对类型"` + common.ControlBy +} + +func (s *LineSymbolUpdateReq) Generate(model *models.LineSymbol) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.ApiId = s.ApiId + model.Symbol = s.Symbol + model.BaseAsset = s.BaseAsset + model.QuoteAsset = s.QuoteAsset + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 + model.Switch = s.Switch + model.Type = s.Type +} + +func (s *LineSymbolUpdateReq) GetId() interface{} { + return s.Id +} + +// LineSymbolGetReq 功能获取请求参数 +type LineSymbolGetReq struct { + Id int `uri:"id"` +} + +func (s *LineSymbolGetReq) GetId() interface{} { + return s.Id +} + +// LineSymbolDeleteReq 功能删除请求参数 +type LineSymbolDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineSymbolDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/line_symbol_black.go b/app/admin/service/dto/line_symbol_black.go new file mode 100644 index 0000000..04ce1b9 --- /dev/null +++ b/app/admin/service/dto/line_symbol_black.go @@ -0,0 +1,88 @@ +package dto + +import ( + + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type LineSymbolBlackGetPageReq struct { + dto.Pagination `search:"-"` + Symbol string `form:"symbol" search:"type:exact;column:symbol;table:line_symbol_black" comment:"交易对"` + Type string `form:"type" search:"type:exact;column:type;table:line_symbol_black" comment:"类型:1=现货,2=合约"` + LineSymbolBlackOrder +} + +type LineSymbolBlackOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_symbol_black"` + Symbol string `form:"symbolOrder" search:"type:order;column:symbol;table:line_symbol_black"` + Type string `form:"typeOrder" search:"type:order;column:type;table:line_symbol_black"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_symbol_black"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_symbol_black"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_symbol_black"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_symbol_black"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_symbol_black"` + +} + +func (m *LineSymbolBlackGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineSymbolBlackInsertReq struct { + Id int `json:"-" comment:"id"` // id + Symbol string `json:"symbol" comment:"交易对"` + Type string `json:"type" comment:"类型:1=现货,2=合约"` + common.ControlBy +} + +func (s *LineSymbolBlackInsertReq) Generate(model *models.LineSymbolBlack) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.Symbol = s.Symbol + model.Type = s.Type + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *LineSymbolBlackInsertReq) GetId() interface{} { + return s.Id +} + +type LineSymbolBlackUpdateReq struct { + Id int `uri:"id" comment:"id"` // id + Symbol string `json:"symbol" comment:"交易对"` + Type string `json:"type" comment:"类型:1=现货,2=合约"` + common.ControlBy +} + +func (s *LineSymbolBlackUpdateReq) Generate(model *models.LineSymbolBlack) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.Symbol = s.Symbol + model.Type = s.Type + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *LineSymbolBlackUpdateReq) GetId() interface{} { + return s.Id +} + +// LineSymbolBlackGetReq 功能获取请求参数 +type LineSymbolBlackGetReq struct { + Id int `uri:"id"` +} +func (s *LineSymbolBlackGetReq) GetId() interface{} { + return s.Id +} + +// LineSymbolBlackDeleteReq 功能删除请求参数 +type LineSymbolBlackDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineSymbolBlackDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/line_symbol_group.go b/app/admin/service/dto/line_symbol_group.go new file mode 100644 index 0000000..f707915 --- /dev/null +++ b/app/admin/service/dto/line_symbol_group.go @@ -0,0 +1,97 @@ +package dto + +import ( + + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type LineSymbolGroupGetPageReq struct { + dto.Pagination `search:"-"` + Type string `form:"type" search:"type:exact;column:type;table:line_symbol_group" comment:"类型:1=现货,2=合约"` + LineSymbolGroupOrder +} + +type LineSymbolGroupOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_symbol_group"` + GroupName string `form:"groupNameOrder" search:"type:order;column:group_name;table:line_symbol_group"` + Symbol string `form:"symbolOrder" search:"type:order;column:symbol;table:line_symbol_group"` + GroupType string `form:"groupTypeOrder" search:"type:order;column:groupType;table:line_symbol_group"` + Type string `form:"typeOrder" search:"type:order;column:type;table:line_symbol_group"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_symbol_group"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_symbol_group"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_symbol_group"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_symbol_group"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_symbol_group"` + +} + +func (m *LineSymbolGroupGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineSymbolGroupInsertReq struct { + Id int `json:"-" comment:""` // + GroupName string `json:"groupName" comment:"交易对组名称"` + Symbol string `json:"symbol" comment:"交易对"` + GroupType string `json:"groupType" comment:"分组类型:1=普通类型"` + Type string `json:"type" comment:"类型:1=现货,2=合约"` + common.ControlBy +} + +func (s *LineSymbolGroupInsertReq) Generate(model *models.LineSymbolGroup) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.GroupName = s.GroupName + model.Symbol = s.Symbol + model.GroupType = s.GroupType + model.Type = s.Type + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *LineSymbolGroupInsertReq) GetId() interface{} { + return s.Id +} + +type LineSymbolGroupUpdateReq struct { + Id int `uri:"id" comment:""` // + GroupName string `json:"groupName" comment:"交易对组名称"` + Symbol string `json:"symbol" comment:"交易对"` + GroupType string `json:"groupType" comment:"分组类型:1=普通类型"` + Type string `json:"type" comment:"类型:1=现货,2=合约"` + common.ControlBy +} + +func (s *LineSymbolGroupUpdateReq) Generate(model *models.LineSymbolGroup) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.GroupName = s.GroupName + model.Symbol = s.Symbol + model.GroupType = s.GroupType + model.Type = s.Type + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *LineSymbolGroupUpdateReq) GetId() interface{} { + return s.Id +} + +// LineSymbolGroupGetReq 功能获取请求参数 +type LineSymbolGroupGetReq struct { + Id int `uri:"id"` +} +func (s *LineSymbolGroupGetReq) GetId() interface{} { + return s.Id +} + +// LineSymbolGroupDeleteReq 功能删除请求参数 +type LineSymbolGroupDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineSymbolGroupDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/line_system_setting.go b/app/admin/service/dto/line_system_setting.go new file mode 100644 index 0000000..1f2811e --- /dev/null +++ b/app/admin/service/dto/line_system_setting.go @@ -0,0 +1,134 @@ +package dto + +import ( + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" + + "github.com/shopspring/decimal" +) + +type LineSystemSettingGetPageReq struct { + dto.Pagination `search:"-"` + LineSystemSettingOrder +} + +type LineSystemSettingOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_system_setting"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_system_setting"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_system_setting"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_system_setting"` + Time string `form:"timeOrder" search:"type:order;column:time;table:line_system_setting"` + BatchTime string `form:"batchTimeOrder" search:"type:order;column:batch_time;table:line_system_setting"` + ProfitRate string `form:"profitRateOrder" search:"type:order;column:profit_rate;table:line_system_setting"` + CoverOrderTypeBRate string `form:"coverOrderTypeBRateOrder" search:"type:order;column:cover_order_type_b_rate;table:line_system_setting"` + ScaleOrderTypeARate string `form:"scaleOrderTypeARateOrder" search:"type:order;column:scale_order_type_a_rate;table:line_system_setting"` + ScaleOrderTypeBRate string `form:"scaleOrderTypeBRateOrder" search:"type:order;column:scale_order_type_b_rate;table:line_system_setting"` + ScaleUnRealizedProfitRate string `form:"scaleUnRealizedProfitRateOrder" search:"type:order;column:scale_unRealizedProfitRate;table:line_system_setting"` + ScaleNum string `form:"scaleNumOrder" search:"type:order;column:scale_num;table:line_system_setting"` + ScaleSubordinate string `form:"scaleSubordinateOrder" search:"type:order;column:scale_subordinate;table:line_system_setting"` + AutoScaleTimes string `form:"autoScaleTimesOrder" search:"type:order;column:auto_scale_times;table:line_system_setting"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_system_setting"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_system_setting"` +} + +func (m *LineSystemSettingGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineSystemSettingInsertReq struct { + Id int `json:"-" comment:"id"` // id + Time int64 `json:"time" comment:"导入:挂单时长达到时间后失效"` + BatchTime int64 `json:"batchTime" comment:"批量:挂单时长达到时间后失效"` + ProfitRate string `json:"profitRate" comment:"平仓盈利比例"` + CoverOrderTypeBRate string `json:"coverOrderTypeBRate" comment:"b账户限价补单的买入百分比"` + ScaleOrderTypeARate string `json:"scaleOrderTypeARate" comment:"a账户限价加仓买入百分比"` + ScaleOrderTypeBRate string `json:"scaleOrderTypeBRate" comment:"b账户限价加仓买入百分比"` + ScaleUnRealizedProfitRate string `json:"scaleUnRealizedProfitRate" comment:"亏损百分比加仓"` + ScaleNum string `json:"scaleNum" comment:"加仓数值"` + ScaleSubordinate int64 `json:"scaleSubordinate" comment:"加仓账户:1=A账户;2=副账户;3=都加"` + AutoScaleTimes int64 `json:"autoScaleTimes" comment:"自动加仓次数"` + ProtectHedgeEnable int `json:"protectHedgeEnable" comment:"是否只开启保险对冲 0-否 1-是"` + common.ControlBy +} + +func (s *LineSystemSettingInsertReq) Generate(model *models.LineSystemSetting) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.Time = s.Time + model.BatchTime = s.BatchTime + model.ProfitRate = s.ProfitRate + model.CoverOrderTypeBRate = s.CoverOrderTypeBRate + model.ScaleOrderTypeARate = s.ScaleOrderTypeARate + model.ScaleOrderTypeBRate = s.ScaleOrderTypeBRate + model.ScaleUnrealizedProfitRate = s.ScaleUnRealizedProfitRate + model.ScaleNum = s.ScaleNum + model.ScaleSubordinate = s.ScaleSubordinate + model.AutoScaleTimes = s.AutoScaleTimes + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *LineSystemSettingInsertReq) GetId() interface{} { + return s.Id +} + +type LineSystemSettingUpdateReq struct { + Id int `uri:"id" comment:"id"` // id + Time int64 `json:"time" comment:"导入:挂单时长达到时间后失效"` + BatchTime int64 `json:"batchTime" comment:"批量:挂单时长达到时间后失效"` + ProfitRate string `json:"profitRate" comment:"平仓盈利比例"` + CoverOrderTypeBRate string `json:"coverOrderTypeBRate" comment:"b账户限价补单的买入百分比"` + ScaleOrderTypeARate string `json:"scaleOrderTypeARate" comment:"a账户限价加仓买入百分比"` + ScaleOrderTypeBRate string `json:"scaleOrderTypeBRate" comment:"b账户限价加仓买入百分比"` + ScaleUnrealizedProfitRate string `json:"scaleUnRealizedProfitRate" comment:"亏损百分比加仓"` + ScaleNum string `json:"scaleNum" comment:"加仓数值"` + ScaleSubordinate int64 `json:"scaleSubordinate" comment:"加仓账户:1=A账户;2=副账户;3=都加"` + AutoScaleTimes int64 `json:"autoScaleTimes" comment:"自动加仓次数"` + HedgePerformance decimal.Decimal `json:"hedgePerformance" comment:"对冲平仓涨跌幅"` + ProtectHedgeRate decimal.Decimal `json:"protectHedgeRate" comment:"对冲市价亏损百分比"` + ProtectHedgeEnable int `json:"protectHedgeEnable" comment:"是否只开启保险对冲 0-否 1-是"` + common.ControlBy +} + +func (s *LineSystemSettingUpdateReq) Generate(model *models.LineSystemSetting) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.Time = s.Time + model.BatchTime = s.BatchTime + model.ProfitRate = s.ProfitRate + model.CoverOrderTypeBRate = s.CoverOrderTypeBRate + model.ScaleOrderTypeARate = s.ScaleOrderTypeARate + model.ScaleOrderTypeBRate = s.ScaleOrderTypeBRate + model.ScaleUnrealizedProfitRate = s.ScaleUnrealizedProfitRate + model.ScaleNum = s.ScaleNum + model.ScaleSubordinate = s.ScaleSubordinate + model.AutoScaleTimes = s.AutoScaleTimes + model.HedgePerformance = s.HedgePerformance + model.ProtectHedgeRate = s.ProtectHedgeRate + model.ProtectHedgeEnable = s.ProtectHedgeEnable + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *LineSystemSettingUpdateReq) GetId() interface{} { + return s.Id +} + +// LineSystemSettingGetReq 功能获取请求参数 +type LineSystemSettingGetReq struct { + Id int `uri:"id"` +} + +func (s *LineSystemSettingGetReq) GetId() interface{} { + return s.Id +} + +// LineSystemSettingDeleteReq 功能删除请求参数 +type LineSystemSettingDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineSystemSettingDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/line_uduncoin.go b/app/admin/service/dto/line_uduncoin.go new file mode 100644 index 0000000..69bf100 --- /dev/null +++ b/app/admin/service/dto/line_uduncoin.go @@ -0,0 +1,120 @@ +package dto + +import ( + + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type LineUduncoinGetPageReq struct { + dto.Pagination `search:"-"` + LineUduncoinOrder +} + +type LineUduncoinOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_uduncoin"` + TokenStatus string `form:"tokenStatusOrder" search:"type:order;column:token_status;table:line_uduncoin"` + Decimals string `form:"decimalsOrder" search:"type:order;column:decimals;table:line_uduncoin"` + MainCoinType string `form:"mainCoinTypeOrder" search:"type:order;column:main_coin_type;table:line_uduncoin"` + CoinType string `form:"coinTypeOrder" search:"type:order;column:coin_type;table:line_uduncoin"` + Symbol string `form:"symbolOrder" search:"type:order;column:symbol;table:line_uduncoin"` + Name string `form:"nameOrder" search:"type:order;column:name;table:line_uduncoin"` + Logo string `form:"logoOrder" search:"type:order;column:logo;table:line_uduncoin"` + CoinName string `form:"coinNameOrder" search:"type:order;column:coin_name;table:line_uduncoin"` + MainSymbol string `form:"mainSymbolOrder" search:"type:order;column:main_symbol;table:line_uduncoin"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_uduncoin"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_uduncoin"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_uduncoin"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_uduncoin"` + +} + +func (m *LineUduncoinGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineUduncoinInsertReq struct { + Id int `json:"-" comment:"主键ID"` // 主键ID + TokenStatus int64 `json:"tokenStatus" comment:"0: 主幣 1:代幣"` + Decimals int64 `json:"decimals" comment:"幣種精度,8"` + MainCoinType string `json:"mainCoinType" comment:"主幣種類型"` + CoinType string `json:"coinType" comment:"幣種類型"` + Symbol string `json:"symbol" comment:"幣種symbol"` + Name string `json:"name" comment:"幣種別名,BTC"` + Logo string `json:"logo" comment:"幣種logo地址"` + CoinName string `json:"coinName" comment:"幣種全稱,Bitcoin"` + MainSymbol string `json:"mainSymbol" comment:"主幣種symbol"` + common.ControlBy +} + +func (s *LineUduncoinInsertReq) Generate(model *models.LineUduncoin) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.TokenStatus = s.TokenStatus + model.Decimals = s.Decimals + model.MainCoinType = s.MainCoinType + model.CoinType = s.CoinType + model.Symbol = s.Symbol + model.Name = s.Name + model.Logo = s.Logo + model.CoinName = s.CoinName + model.MainSymbol = s.MainSymbol + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *LineUduncoinInsertReq) GetId() interface{} { + return s.Id +} + +type LineUduncoinUpdateReq struct { + Id int `uri:"id" comment:"主键ID"` // 主键ID + TokenStatus int64 `json:"tokenStatus" comment:"0: 主幣 1:代幣"` + Decimals int64 `json:"decimals" comment:"幣種精度,8"` + MainCoinType string `json:"mainCoinType" comment:"主幣種類型"` + CoinType string `json:"coinType" comment:"幣種類型"` + Symbol string `json:"symbol" comment:"幣種symbol"` + Name string `json:"name" comment:"幣種別名,BTC"` + Logo string `json:"logo" comment:"幣種logo地址"` + CoinName string `json:"coinName" comment:"幣種全稱,Bitcoin"` + MainSymbol string `json:"mainSymbol" comment:"主幣種symbol"` + common.ControlBy +} + +func (s *LineUduncoinUpdateReq) Generate(model *models.LineUduncoin) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.TokenStatus = s.TokenStatus + model.Decimals = s.Decimals + model.MainCoinType = s.MainCoinType + model.CoinType = s.CoinType + model.Symbol = s.Symbol + model.Name = s.Name + model.Logo = s.Logo + model.CoinName = s.CoinName + model.MainSymbol = s.MainSymbol + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *LineUduncoinUpdateReq) GetId() interface{} { + return s.Id +} + +// LineUduncoinGetReq 功能获取请求参数 +type LineUduncoinGetReq struct { + Id int `uri:"id"` +} +func (s *LineUduncoinGetReq) GetId() interface{} { + return s.Id +} + +// LineUduncoinDeleteReq 功能删除请求参数 +type LineUduncoinDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineUduncoinDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/line_user.go b/app/admin/service/dto/line_user.go new file mode 100644 index 0000000..24f0c9b --- /dev/null +++ b/app/admin/service/dto/line_user.go @@ -0,0 +1,332 @@ +package dto + +import ( + "github.com/shopspring/decimal" + statuscode "go-admin/common/status_code" + "go-admin/pkg/emailhelper" + "go-admin/pkg/utility" + "time" + + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type LineUserGetPageReq struct { + dto.Pagination `search:"-"` + LineUserOrder +} + +type LineUserOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_user"` + GroupId int `form:"groupIdOrder" search:"type:order;column:group_id;table:line_user"` + Username string `form:"usernameOrder" search:"type:order;column:username;table:line_user"` + Nickname string `form:"nicknameOrder" search:"type:order;column:nickname;table:line_user"` + Password string `form:"passwordOrder" search:"type:order;column:password;table:line_user"` + Salt string `form:"saltOrder" search:"type:order;column:salt;table:line_user"` + Email string `form:"emailOrder" search:"type:order;column:email;table:line_user"` + Mobile string `form:"mobileOrder" search:"type:order;column:mobile;table:line_user"` + Avatar string `form:"avatarOrder" search:"type:order;column:avatar;table:line_user"` + Level int `form:"levelOrder" search:"type:order;column:level;table:line_user"` + Gender int `form:"genderOrder" search:"type:order;column:gender;table:line_user"` + Bio string `form:"bioOrder" search:"type:order;column:bio;table:line_user"` + Money string `form:"moneyOrder" search:"type:order;column:money;table:line_user"` + Score int `form:"scoreOrder" search:"type:order;column:score;table:line_user"` + Successions int `form:"successionsOrder" search:"type:order;column:successions;table:line_user"` + MaxSuccessions int `form:"maxSuccessionsOrder" search:"type:order;column:max_successions;table:line_user"` + Loginip string `form:"loginipOrder" search:"type:order;column:loginip;table:line_user"` + Loginfailure int `form:"loginfailureOrder" search:"type:order;column:loginfailure;table:line_user"` + Joinip string `form:"joinipOrder" search:"type:order;column:joinip;table:line_user"` + Jointime int `form:"jointimeOrder" search:"type:order;column:jointime;table:line_user"` + Token string `form:"tokenOrder" search:"type:order;column:token;table:line_user"` + Status string `form:"statusOrder" search:"type:order;column:status;table:line_user"` + Verification string `form:"verificationOrder" search:"type:order;column:verification;table:line_user"` + LoginTime string `form:"loginTimeOrder" search:"type:order;column:login_time;table:line_user"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_user"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_user"` +} + +func (m *LineUserGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineUserInsertReq struct { + Id int `json:"-" comment:"ID"` // ID + GroupId int `json:"groupId" comment:"组别ID"` + Username string `json:"username" comment:"用户名"` + Nickname string `json:"nickname" comment:"昵称"` + Password string `json:"password" comment:"密码"` + Salt string `json:"salt" comment:"密码盐"` + Email string `json:"email" comment:"电子邮箱"` + Mobile string `json:"mobile" comment:"手机号"` + Area string `json:"area" comment:"手机号归属地"` + Avatar string `json:"avatar" comment:"头像"` + Level int `json:"level" comment:"等级"` + Gender int `json:"gender" comment:"性别"` + Bio string `json:"bio" comment:"格言"` + Money decimal.Decimal `json:"money" comment:"保证金"` + Score int `json:"score" comment:"积分"` + Successions int `json:"successions" comment:"连续登录天数"` + MaxSuccessions int `json:"maxSuccessions" comment:"最大连续登录天数"` + Loginip string `json:"loginip" comment:"登录IP"` + Loginfailure int `json:"loginfailure" comment:"失败次数"` + Joinip string `json:"joinip" comment:"加入IP"` + Jointime int `json:"jointime" comment:"加入时间"` + RecommendNum int `json:"recommend_num" comment:"推荐人数"` + Token string `json:"token" comment:"Token"` + Status string `json:"status" comment:"状态"` + Verification string `json:"verification" comment:"验证"` + LoginTime time.Time `json:"loginTime" comment:"登录时间"` + common.ControlBy +} + +func (s *LineUserInsertReq) Generate(model *models.LineUser) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.GroupId = s.GroupId + model.Username = s.Username + model.Nickname = s.Nickname + model.Password = s.Password + model.Salt = s.Salt + model.Email = s.Email + model.Mobile = s.Mobile + model.Area = s.Area + model.Avatar = s.Avatar + model.Level = s.Level + model.Gender = s.Gender + model.Bio = s.Bio + model.Money = s.Money + model.Score = s.Score + model.Successions = s.Successions + model.MaxSuccessions = s.MaxSuccessions + model.Loginip = s.Loginip + model.Loginfailure = s.Loginfailure + model.Joinip = s.Joinip + model.Jointime = s.Jointime + model.RecommendNum = s.RecommendNum + model.Token = s.Token + model.Status = s.Status + model.Verification = s.Verification + model.LoginTime = s.LoginTime +} + +func (s *LineUserInsertReq) GetId() interface{} { + return s.Id +} + +type LineUserUpdateReq struct { + Id int `uri:"id" comment:"ID"` // ID + GroupId int `json:"groupId" comment:"组别ID"` + Pid int `json:"pid" comment:"推荐人ID"` + Username string `json:"username" comment:"用户名"` + Nickname string `json:"nickname" comment:"昵称"` + Password string `json:"password" comment:"密码"` + Salt string `json:"salt" comment:"密码盐"` + Email string `json:"email" comment:"电子邮箱"` + Mobile string `json:"mobile" comment:"手机号"` + Area string `json:"area" comment:"手机号归属地"` + Avatar string `json:"avatar" comment:"头像"` + Level int `json:"level" comment:"等级"` + Gender int `json:"gender" comment:"性别"` + Bio string `json:"bio" comment:"格言"` + Money decimal.Decimal `json:"money" comment:"余额"` + Score int `json:"score" comment:"积分"` + InviteCode string `json:"invite_code" comment:"邀请码"` + Successions int `json:"successions" comment:"连续登录天数"` + MaxSuccessions int `json:"maxSuccessions" comment:"最大连续登录天数"` + Loginip string `json:"loginip" comment:"登录IP"` + Loginfailure int `json:"loginfailure" comment:"失败次数"` + Joinip string `json:"joinip" comment:"加入IP"` + Jointime int `json:"jointime" comment:"加入时间"` + RecommendNum int `json:"recommend_num" comment:"推荐人数"` + Token string `json:"token" comment:"Token"` + Status string `json:"status" comment:"状态"` + Verification string `json:"verification" comment:"验证"` + LoginTime time.Time `json:"loginTime" comment:"登录时间"` + OpenStatus int `json:"open_status"` + common.ControlBy +} + +func (s *LineUserUpdateReq) Generate(model *models.LineUser) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.GroupId = s.GroupId + model.Pid = s.Pid + model.Username = s.Username + model.Nickname = s.Nickname + model.Password = s.Password + model.Salt = s.Salt + model.Email = s.Email + model.Mobile = s.Mobile + model.Area = s.Area + model.Avatar = s.Avatar + model.Level = s.Level + model.Gender = s.Gender + model.Bio = s.Bio + model.Money = s.Money + model.Score = s.Score + model.InviteCode = s.InviteCode + model.Successions = s.Successions + model.MaxSuccessions = s.MaxSuccessions + model.Loginip = s.Loginip + model.Loginfailure = s.Loginfailure + model.Joinip = s.Joinip + model.Jointime = s.Jointime + model.Token = s.Token + model.Status = s.Status + model.Verification = s.Verification + model.LoginTime = s.LoginTime +} + +func (s *LineUserUpdateReq) GetId() interface{} { + return s.Id +} + +// LineUserGetReq 功能获取请求参数 +type LineUserGetReq struct { + Id int `uri:"id"` +} + +func (s *LineUserGetReq) GetId() interface{} { + return s.Id +} + +// LineUserDeleteReq 功能删除请求参数 +type LineUserDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineUserDeleteReq) GetId() interface{} { + return s.Ids +} + +// FrontedUserVerifyEmailReq 验证邮箱验证码参数 +type FrontedUserVerifyEmailReq struct { + Email string `json:"email"` + VerifyCode string `json:"verify_code"` +} + +type FrontedSendVerifyEmailReq struct { + Email string `json:"email"` +} + +type FrontedSendVerifySmsReq struct { + PhoneAreaCode string `json:"phone_area_code"` // 区域电话代码 + Phone string `json:"phone"` // 手机号码 +} + +func (receiver *FrontedSendVerifyEmailReq) CheckParams() int { + // 邮箱校验 + if receiver.Email == "" { + return statuscode.EmailRequired + } + if !emailhelper.CheckIsEmail(receiver.Email) { + return statuscode.EmailFormatInvalid + } + return statuscode.OK +} + +func (receiver FrontedSendVerifySmsReq) CheckParams() int { + // 短信校验 + if receiver.PhoneAreaCode == "" || receiver.Phone == "" { + return statuscode.ParameterInvalid + } + if receiver.PhoneAreaCode == "+86" { + if !utility.IsMobileChina(receiver.Phone) { + return statuscode.PhoneFormatInvalid + } + } + return statuscode.OK +} + +// FrontedLoginReq 登录请求 +type FrontedLoginReq struct { + Email string `json:"email"` + Phone string `json:"phone"` + PhoneAreaCode string `json:"phone_area_code"` + Password string `json:"password"` // 密码 + LoginType int `json:"login_type"` // 1 手机,2 邮箱 +} + +func (receiver FrontedLoginReq) CheckParams() int { + if receiver.Password == "" { + return statuscode.PasswordRequired + } + if receiver.LoginType == 2 { + + if receiver.Email == "" { + return statuscode.EmailRequired + } + if !emailhelper.CheckIsEmail(receiver.Email) { + return statuscode.EmailFormatInvalid + } + return statuscode.OK + } + + if receiver.LoginType == 1 { //手机号登录 + if receiver.Phone == "" { + return statuscode.PhoneRequired + } + return statuscode.OK + } + return statuscode.ParamErr +} + +type AddApiKeyReq struct { + ApiName string `json:"api_name"` + ApiKey string `json:"api_key"` + ApiSecret string `json:"api_secret"` + ApiIp string `json:"api_ip"` +} + +func (a AddApiKeyReq) CheckParams() int { + if a.ApiKey == "" || a.ApiSecret == "" { + return statuscode.ParamErr + } + return statuscode.OK +} + +// OpenStatusReq 开启或者闭关状态 +type OpenStatusReq struct { + Status int `json:"status"` +} + +type RechargeListReq struct { + Coin string `json:"coin"` +} + +// ApiRestrictions 检查api 是否可用 +type ApiRestrictions struct { + IpRestrict bool `json:"ipRestrict"` + CreateTime int64 `json:"createTime"` + EnableReading bool `json:"enableReading"` + EnableWithdrawals bool `json:"enableWithdrawals"` + EnableInternalTransfer bool `json:"enableInternalTransfer"` + EnableMargin bool `json:"enableMargin"` + EnableFutures bool `json:"enableFutures"` + PermitsUniversalTransfer bool `json:"permitsUniversalTransfer"` + EnableVanillaOptions bool `json:"enableVanillaOptions"` + EnableFixApiTrade bool `json:"enableFixApiTrade"` + EnableFixReadOnly bool `json:"enableFixReadOnly"` + EnableSpotAndMarginTrading bool `json:"enableSpotAndMarginTrading"` + EnablePortfolioMarginTrading bool `json:"enablePortfolioMarginTrading"` +} + +type RechargeAddressListReq struct { + UserId int `json:"user_id"` + NetWorkId int `json:"network_id"` + CoinCode string `json:"coin"` +} + +type VtsRechargePreOrderReq struct { + CoinCode string `json:"coin_code" form:"coin_code"` + Amount decimal.Decimal `json:"amount" form:"amount"` +} + +type VtsRechargePreOrderResp struct { + PaymentUrl string `json:"paymentUrl"` + PriceAmount string `json:"priceAmount"` + PriceCurrency string `json:"priceCurrency"` +} diff --git a/app/admin/service/dto/line_user_funding_trend.go b/app/admin/service/dto/line_user_funding_trend.go new file mode 100644 index 0000000..ed3b5da --- /dev/null +++ b/app/admin/service/dto/line_user_funding_trend.go @@ -0,0 +1,82 @@ +package dto + +import ( + + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type LineUserFundingTrendGetPageReq struct { + dto.Pagination `search:"-"` + LineUserFundingTrendOrder +} + +type LineUserFundingTrendOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_user_funding_trend"` + UserId string `form:"userIdOrder" search:"type:order;column:user_id;table:line_user_funding_trend"` + Funding string `form:"fundingOrder" search:"type:order;column:funding;table:line_user_funding_trend"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_user_funding_trend"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_user_funding_trend"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_user_funding_trend"` + +} + +func (m *LineUserFundingTrendGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineUserFundingTrendInsertReq struct { + Id int `json:"-" comment:""` // + UserId int64 `json:"userId" comment:"用户id"` + Funding string `json:"funding" comment:"资金账户总额 换算U"` + common.ControlBy +} + +func (s *LineUserFundingTrendInsertReq) Generate(model *models.LineUserFundingTrend) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.UserId = s.UserId + model.Funding = s.Funding +} + +func (s *LineUserFundingTrendInsertReq) GetId() interface{} { + return s.Id +} + +type LineUserFundingTrendUpdateReq struct { + Id int `uri:"id" comment:""` // + UserId int64 `json:"userId" comment:"用户id"` + Funding string `json:"funding" comment:"资金账户总额 换算U"` + common.ControlBy +} + +func (s *LineUserFundingTrendUpdateReq) Generate(model *models.LineUserFundingTrend) { + if s.Id == 0 { + model.Model = common.Model{ Id: s.Id } + } + model.UserId = s.UserId + model.Funding = s.Funding +} + +func (s *LineUserFundingTrendUpdateReq) GetId() interface{} { + return s.Id +} + +// LineUserFundingTrendGetReq 功能获取请求参数 +type LineUserFundingTrendGetReq struct { + Id int `uri:"id"` +} +func (s *LineUserFundingTrendGetReq) GetId() interface{} { + return s.Id +} + +// LineUserFundingTrendDeleteReq 功能删除请求参数 +type LineUserFundingTrendDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineUserFundingTrendDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/line_user_profit_logs.go b/app/admin/service/dto/line_user_profit_logs.go new file mode 100644 index 0000000..e662138 --- /dev/null +++ b/app/admin/service/dto/line_user_profit_logs.go @@ -0,0 +1,113 @@ +package dto + +import ( + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type LineUserProfitLogsGetPageReq struct { + dto.Pagination `search:"-"` + UserId int64 `form:"userId" search:"type:exact;column:user_id;table:line_user_profit_logs" comment:"line_user 表的id"` + ApiId int64 `form:"apiId" search:"type:exact;column:api_id;table:line_user_profit_logs" comment:"line_apiuser 表的id"` + PreOrderId int64 `form:"preOrderId" search:"type:exact;column:pre_order_id;table:line_user_profit_logs" comment:"line_pre_order 主订单id"` + Num string `form:"num" search:"type:exact;column:num;table:line_user_profit_logs" comment:"成交数量"` + Symbol string `form:"symbol" search:"type:exact;column:symbol;table:line_user_profit_logs" comment:"交易对"` + LineUserProfitLogsOrder +} + +type LineUserProfitLogsOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_user_profit_logs"` + UserId string `form:"userIdOrder" search:"type:order;column:user_id;table:line_user_profit_logs"` + ApiId string `form:"apiIdOrder" search:"type:order;column:api_id;table:line_user_profit_logs"` + PreOrderId string `form:"preOrderIdOrder" search:"type:order;column:pre_order_id;table:line_user_profit_logs"` + Num string `form:"numOrder" search:"type:order;column:num;table:line_user_profit_logs"` + Symbol string `form:"symbolOrder" search:"type:order;column:symbol;table:line_user_profit_logs"` + Rate string `form:"rateOrder" search:"type:order;column:rate;table:line_user_profit_logs"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_user_profit_logs"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_user_profit_logs"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:line_user_profit_logs"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_user_profit_logs"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_user_profit_logs"` +} + +func (m *LineUserProfitLogsGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineUserProfitLogsInsertReq struct { + Id int `json:"-" comment:""` // + UserId int64 `json:"userId" comment:"line_user 表的id"` + ApiId int64 `json:"apiId" comment:"line_apiuser 表的id"` + PreOrderId int64 `json:"preOrderId" comment:"line_pre_order 主订单id"` + Num string `json:"num" comment:"成交数量"` + Symbol string `json:"symbol" comment:"交易对"` + Rate string `json:"rate" comment:"盈利比例"` + Amount string `json:"amount" comment:"盈利金额(换算成U的价值)"` + common.ControlBy +} + +func (s *LineUserProfitLogsInsertReq) Generate(model *models.LineUserProfitLogs) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.UserId = s.UserId + model.ApiId = s.ApiId + model.PreOrderId = s.PreOrderId + model.Num = s.Num + model.Symbol = s.Symbol + model.Rate = s.Rate + model.Amount = s.Amount + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *LineUserProfitLogsInsertReq) GetId() interface{} { + return s.Id +} + +type LineUserProfitLogsUpdateReq struct { + Id int `uri:"id" comment:""` // + UserId int64 `json:"userId" comment:"line_user 表的id"` + ApiId int64 `json:"apiId" comment:"line_apiuser 表的id"` + PreOrderId int64 `json:"preOrderId" comment:"line_pre_order 主订单id"` + Num string `json:"num" comment:"成交数量"` + Symbol string `json:"symbol" comment:"交易对"` + Rate string `json:"rate" comment:"盈利比例"` + Amoubt string `json:"" comment:"盈利比例"` + common.ControlBy +} + +func (s *LineUserProfitLogsUpdateReq) Generate(model *models.LineUserProfitLogs) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.UserId = s.UserId + model.ApiId = s.ApiId + model.PreOrderId = s.PreOrderId + model.Num = s.Num + model.Symbol = s.Symbol + model.Rate = s.Rate + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *LineUserProfitLogsUpdateReq) GetId() interface{} { + return s.Id +} + +// LineUserProfitLogsGetReq 功能获取请求参数 +type LineUserProfitLogsGetReq struct { + Id int `uri:"id"` +} + +func (s *LineUserProfitLogsGetReq) GetId() interface{} { + return s.Id +} + +// LineUserProfitLogsDeleteReq 功能删除请求参数 +type LineUserProfitLogsDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineUserProfitLogsDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/line_wallet.go b/app/admin/service/dto/line_wallet.go new file mode 100644 index 0000000..6db95b6 --- /dev/null +++ b/app/admin/service/dto/line_wallet.go @@ -0,0 +1,104 @@ +package dto + +import ( + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type LineWalletGetPageReq struct { + dto.Pagination `search:"-"` + LineWalletOrder +} + +type LineWalletOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:line_wallet"` + UserId string `form:"userIdOrder" search:"type:order;column:user_id;table:line_wallet"` + CoinId string `form:"coinIdOrder" search:"type:order;column:coin_id;table:line_wallet"` + CoinCode string `form:"coinCode" search:"type:order;column:coin_code;table:line_wallet"` + Tag string `form:"tagOrder" search:"type:order;column:tag;table:line_wallet"` + Address string `form:"addressOrder" search:"type:order;column:address;table:line_wallet"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:line_wallet"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:line_wallet"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:line_wallet"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:line_wallet"` + CoinNetworkId int `form:"coinNetworkIdOrder" search:"type:order;column:coin_network_id;table:line_wallet"` +} + +func (m *LineWalletGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type LineWalletInsertReq struct { + Id int `json:"-" comment:"主键ID"` // 主键ID + UserId int64 `json:"userId" comment:"用户Id"` + CoinId int64 `json:"coinId" comment:"币种id"` + CoinCode string `json:"coinCode" comment:"币种名称"` + Tag string `json:"tag" comment:"标签"` + Address string `json:"address" comment:"地址"` + CoinNetworkId int `json:"coinNetworkId" comment:"币种主网id,useri+主网id做唯一"` + common.ControlBy +} + +func (s *LineWalletInsertReq) Generate(model *models.LineWallet) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.UserId = s.UserId + model.CoinId = s.CoinId + model.Tag = s.Tag + model.CoinCode = s.CoinCode + model.Address = s.Address + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 + model.CoinNetworkId = s.CoinNetworkId +} + +func (s *LineWalletInsertReq) GetId() interface{} { + return s.Id +} + +type LineWalletUpdateReq struct { + Id int `uri:"id" comment:"主键ID"` // 主键ID + UserId int64 `json:"userId" comment:"用户Id"` + CoinId int64 `json:"coinId" comment:"币种id"` + CoinCode string `json:"coinCode" comment:"币种名称"` + Tag string `json:"tag" comment:"标签"` + Address string `json:"address" comment:"地址"` + CoinNetworkId int `json:"coinNetworkId" comment:"币种主网id,useri+主网id做唯一"` + common.ControlBy +} + +func (s *LineWalletUpdateReq) Generate(model *models.LineWallet) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.UserId = s.UserId + model.CoinId = s.CoinId + model.Tag = s.Tag + model.CoinCode = s.CoinCode + model.Address = s.Address + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 + model.CoinNetworkId = s.CoinNetworkId +} + +func (s *LineWalletUpdateReq) GetId() interface{} { + return s.Id +} + +// LineWalletGetReq 功能获取请求参数 +type LineWalletGetReq struct { + Id int `uri:"id"` +} + +func (s *LineWalletGetReq) GetId() interface{} { + return s.Id +} + +// LineWalletDeleteReq 功能删除请求参数 +type LineWalletDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *LineWalletDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/sys_api.go b/app/admin/service/dto/sys_api.go new file mode 100644 index 0000000..d26034e --- /dev/null +++ b/app/admin/service/dto/sys_api.go @@ -0,0 +1,95 @@ +package dto + +import ( + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +// SysApiGetPageReq 功能列表请求参数 +type SysApiGetPageReq struct { + dto.Pagination `search:"-"` + Title string `form:"title" search:"type:contains;column:title;table:sys_api" comment:"标题"` + Path string `form:"path" search:"type:contains;column:path;table:sys_api" comment:"地址"` + Action string `form:"action" search:"type:exact;column:action;table:sys_api" comment:"请求方式"` + ParentId string `form:"parentId" search:"type:exact;column:parent_id;table:sys_api" comment:"按钮id"` + Type string `form:"type" search:"-" comment:"类型"` + SysApiOrder +} + +type SysApiOrder struct { + TitleOrder string `search:"type:order;column:title;table:sys_api" form:"titleOrder"` + PathOrder string `search:"type:order;column:path;table:sys_api" form:"pathOrder"` + CreatedAtOrder string `search:"type:order;column:created_at;table:sys_api" form:"createdAtOrder"` +} + +func (m *SysApiGetPageReq) GetNeedSearch() interface{} { + return *m +} + +// SysApiInsertReq 功能创建请求参数 +type SysApiInsertReq struct { + Id int `json:"-" comment:"编码"` // 编码 + Handle string `json:"handle" comment:"handle"` + Title string `json:"title" comment:"标题"` + Path string `json:"path" comment:"地址"` + Type string `json:"type" comment:""` + Action string `json:"action" comment:"类型"` + common.ControlBy +} + +func (s *SysApiInsertReq) Generate(model *models.SysApi) { + model.Handle = s.Handle + model.Title = s.Title + model.Path = s.Path + model.Type = s.Type + model.Action = s.Action +} + +func (s *SysApiInsertReq) GetId() interface{} { + return s.Id +} + +// SysApiUpdateReq 功能更新请求参数 +type SysApiUpdateReq struct { + Id int `uri:"id" comment:"编码"` // 编码 + Handle string `json:"handle" comment:"handle"` + Title string `json:"title" comment:"标题"` + Path string `json:"path" comment:"地址"` + Type string `json:"type" comment:""` + Action string `json:"action" comment:"类型"` + common.ControlBy +} + +func (s *SysApiUpdateReq) Generate(model *models.SysApi) { + if s.Id != 0 { + model.Id = s.Id + } + model.Handle = s.Handle + model.Title = s.Title + model.Path = s.Path + model.Type = s.Type + model.Action = s.Action +} + +func (s *SysApiUpdateReq) GetId() interface{} { + return s.Id +} + +// SysApiGetReq 功能获取请求参数 +type SysApiGetReq struct { + Id int `uri:"id"` +} + +func (s *SysApiGetReq) GetId() interface{} { + return s.Id +} + +// SysApiDeleteReq 功能删除请求参数 +type SysApiDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *SysApiDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/sys_config.go b/app/admin/service/dto/sys_config.go new file mode 100644 index 0000000..0e97bc1 --- /dev/null +++ b/app/admin/service/dto/sys_config.go @@ -0,0 +1,117 @@ +package dto + +import ( + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +// SysConfigGetPageReq 列表或者搜索使用结构体 +type SysConfigGetPageReq struct { + dto.Pagination `search:"-"` + ConfigName string `form:"configName" search:"type:contains;column:config_name;table:sys_config"` + ConfigKey string `form:"configKey" search:"type:contains;column:config_key;table:sys_config"` + ConfigType string `form:"configType" search:"type:exact;column:config_type;table:sys_config"` + IsFrontend string `form:"isFrontend" search:"type:exact;column:is_frontend;table:sys_config"` + SysConfigOrder +} + +type SysConfigOrder struct { + IdOrder string `search:"type:order;column:id;table:sys_config" form:"idOrder"` + ConfigNameOrder string `search:"type:order;column:config_name;table:sys_config" form:"configNameOrder"` + ConfigKeyOrder string `search:"type:order;column:config_key;table:sys_config" form:"configKeyOrder"` + ConfigTypeOrder string `search:"type:order;column:config_type;table:sys_config" form:"configTypeOrder"` + CreatedAtOrder string `search:"type:order;column:created_at;table:sys_config" form:"createdAtOrder"` +} + +func (m *SysConfigGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type SysConfigGetToSysAppReq struct { + IsFrontend string `form:"isFrontend" search:"type:exact;column:is_frontend;table:sys_config"` +} + +func (m *SysConfigGetToSysAppReq) GetNeedSearch() interface{} { + return *m +} + +// SysConfigControl 增、改使用的结构体 +type SysConfigControl struct { + Id int `uri:"Id" comment:"编码"` // 编码 + ConfigName string `json:"configName" comment:""` + ConfigKey string `uri:"configKey" json:"configKey" comment:""` + ConfigValue string `json:"configValue" comment:""` + ConfigType string `json:"configType" comment:""` + IsFrontend string `json:"isFrontend"` + Remark string `json:"remark" comment:""` + common.ControlBy +} + +// Generate 结构体数据转化 从 SysConfigControl 至 system.SysConfig 对应的模型 +func (s *SysConfigControl) Generate(model *models.SysConfig) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.ConfigName = s.ConfigName + model.ConfigKey = s.ConfigKey + model.ConfigValue = s.ConfigValue + model.ConfigType = s.ConfigType + model.IsFrontend = s.IsFrontend + model.Remark = s.Remark + +} + +// GetId 获取数据对应的ID +func (s *SysConfigControl) GetId() interface{} { + return s.Id +} + +// GetSetSysConfigReq 增、改使用的结构体 +type GetSetSysConfigReq struct { + ConfigKey string `json:"configKey" comment:""` + ConfigValue string `json:"configValue" comment:""` +} + +// Generate 结构体数据转化 从 SysConfigControl 至 system.SysConfig 对应的模型 +func (s *GetSetSysConfigReq) Generate(model *models.SysConfig) { + model.ConfigValue = s.ConfigValue +} + +type UpdateSetSysConfigReq map[string]string + +// SysConfigByKeyReq 根据Key获取配置 +type SysConfigByKeyReq struct { + ConfigKey string `uri:"configKey" search:"type:contains;column:config_key;table:sys_config"` +} + +func (m *SysConfigByKeyReq) GetNeedSearch() interface{} { + return *m +} + +type GetSysConfigByKEYForServiceResp struct { + ConfigKey string `json:"configKey" comment:""` + ConfigValue string `json:"configValue" comment:""` +} + +type SysConfigGetReq struct { + Id int `uri:"id"` +} + +func (s *SysConfigGetReq) GetId() interface{} { + return s.Id +} + +type SysConfigDeleteReq struct { + Ids []int `json:"ids"` + common.ControlBy +} + +func (s *SysConfigDeleteReq) GetId() interface{} { + return s.Ids +} + +type SameSymbol struct { + Symbol string `json:"symbol"` + Number int `json:"number"` +} diff --git a/app/admin/service/dto/sys_dept.go b/app/admin/service/dto/sys_dept.go new file mode 100644 index 0000000..345a550 --- /dev/null +++ b/app/admin/service/dto/sys_dept.go @@ -0,0 +1,110 @@ +package dto + +import ( + "go-admin/app/admin/models" + common "go-admin/common/models" +) + +// SysDeptGetPageReq 列表或者搜索使用结构体 +type SysDeptGetPageReq struct { + DeptId int `form:"deptId" search:"type:exact;column:dept_id;table:sys_dept" comment:"id"` //id + ParentId int `form:"parentId" search:"type:exact;column:parent_id;table:sys_dept" comment:"上级部门"` //上级部门 + DeptPath string `form:"deptPath" search:"type:exact;column:dept_path;table:sys_dept" comment:""` //路径 + DeptName string `form:"deptName" search:"type:exact;column:dept_name;table:sys_dept" comment:"部门名称"` //部门名称 + Sort int `form:"sort" search:"type:exact;column:sort;table:sys_dept" comment:"排序"` //排序 + Leader string `form:"leader" search:"type:exact;column:leader;table:sys_dept" comment:"负责人"` //负责人 + Phone string `form:"phone" search:"type:exact;column:phone;table:sys_dept" comment:"手机"` //手机 + Email string `form:"email" search:"type:exact;column:email;table:sys_dept" comment:"邮箱"` //邮箱 + Status string `form:"status" search:"type:exact;column:status;table:sys_dept" comment:"状态"` //状态 +} + +func (m *SysDeptGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type SysDeptInsertReq struct { + DeptId int `uri:"id" comment:"编码"` // 编码 + ParentId int `json:"parentId" comment:"上级部门" vd:"?"` //上级部门 + DeptPath string `json:"deptPath" comment:""` //路径 + DeptName string `json:"deptName" comment:"部门名称" vd:"len($)>0"` //部门名称 + Sort int `json:"sort" comment:"排序" vd:"?"` //排序 + Leader string `json:"leader" comment:"负责人" vd:"@:len($)>0; msg:'leader不能为空'"` //负责人 + Phone string `json:"phone" comment:"手机" vd:"?"` //手机 + Email string `json:"email" comment:"邮箱" vd:"?"` //邮箱 + Status int `json:"status" comment:"状态" vd:"$>0"` //状态 + common.ControlBy +} + +func (s *SysDeptInsertReq) Generate(model *models.SysDept) { + if s.DeptId != 0 { + model.DeptId = s.DeptId + } + model.DeptName = s.DeptName + model.ParentId = s.ParentId + model.DeptPath = s.DeptPath + model.Sort = s.Sort + model.Leader = s.Leader + model.Phone = s.Phone + model.Email = s.Email + model.Status = s.Status +} + +// GetId 获取数据对应的ID +func (s *SysDeptInsertReq) GetId() interface{} { + return s.DeptId +} + +type SysDeptUpdateReq struct { + DeptId int `uri:"id" comment:"编码"` // 编码 + ParentId int `json:"parentId" comment:"上级部门" vd:"?"` //上级部门 + DeptPath string `json:"deptPath" comment:""` //路径 + DeptName string `json:"deptName" comment:"部门名称" vd:"len($)>0"` //部门名称 + Sort int `json:"sort" comment:"排序" vd:"?"` //排序 + Leader string `json:"leader" comment:"负责人" vd:"@:len($)>0; msg:'leader不能为空'"` //负责人 + Phone string `json:"phone" comment:"手机" vd:"?"` //手机 + Email string `json:"email" comment:"邮箱" vd:"?"` //邮箱 + Status int `json:"status" comment:"状态" vd:"$>0"` //状态 + common.ControlBy +} + +// Generate 结构体数据转化 从 SysDeptControl 至 SysDept 对应的模型 +func (s *SysDeptUpdateReq) Generate(model *models.SysDept) { + if s.DeptId != 0 { + model.DeptId = s.DeptId + } + model.DeptName = s.DeptName + model.ParentId = s.ParentId + model.DeptPath = s.DeptPath + model.Sort = s.Sort + model.Leader = s.Leader + model.Phone = s.Phone + model.Email = s.Email + model.Status = s.Status +} + +// GetId 获取数据对应的ID +func (s *SysDeptUpdateReq) GetId() interface{} { + return s.DeptId +} + +type SysDeptGetReq struct { + Id int `uri:"id"` +} + +func (s *SysDeptGetReq) GetId() interface{} { + return s.Id +} + +type SysDeptDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *SysDeptDeleteReq) GetId() interface{} { + return s.Ids +} + +type DeptLabel struct { + Id int `gorm:"-" json:"id"` + Label string `gorm:"-" json:"label"` + Children []DeptLabel `gorm:"-" json:"children"` +} diff --git a/app/admin/service/dto/sys_dict_data.go b/app/admin/service/dto/sys_dict_data.go new file mode 100644 index 0000000..842f418 --- /dev/null +++ b/app/admin/service/dto/sys_dict_data.go @@ -0,0 +1,108 @@ +package dto + +import ( + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type SysDictDataGetPageReq struct { + dto.Pagination `search:"-"` + Id int `form:"id" search:"type:exact;column:dict_code;table:sys_dict_data" comment:""` + DictLabel string `form:"dictLabel" search:"type:contains;column:dict_label;table:sys_dict_data" comment:""` + DictValue string `form:"dictValue" search:"type:contains;column:dict_value;table:sys_dict_data" comment:""` + DictType string `form:"dictType" search:"type:contains;column:dict_type;table:sys_dict_data" comment:""` + Status string `form:"status" search:"type:exact;column:status;table:sys_dict_data" comment:""` +} + +func (m *SysDictDataGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type SysDictDataGetAllResp struct { + DictLabel string `json:"label"` + DictValue string `json:"value"` +} + +type SysDictDataInsertReq struct { + Id int `json:"-" comment:""` + DictSort int `json:"dictSort" comment:""` + DictLabel string `json:"dictLabel" comment:""` + DictValue string `json:"dictValue" comment:""` + DictType string `json:"dictType" comment:""` + CssClass string `json:"cssClass" comment:""` + ListClass string `json:"listClass" comment:""` + IsDefault string `json:"isDefault" comment:""` + Status int `json:"status" comment:""` + Default string `json:"default" comment:""` + Remark string `json:"remark" comment:""` + common.ControlBy +} + +func (s *SysDictDataInsertReq) Generate(model *models.SysDictData) { + model.DictCode = s.Id + model.DictSort = s.DictSort + model.DictLabel = s.DictLabel + model.DictValue = s.DictValue + model.DictType = s.DictType + model.CssClass = s.CssClass + model.ListClass = s.ListClass + model.IsDefault = s.IsDefault + model.Status = s.Status + model.Default = s.Default + model.Remark = s.Remark +} + +func (s *SysDictDataInsertReq) GetId() interface{} { + return s.Id +} + +type SysDictDataUpdateReq struct { + Id int `uri:"dictCode" comment:""` + DictSort int `json:"dictSort" comment:""` + DictLabel string `json:"dictLabel" comment:""` + DictValue string `json:"dictValue" comment:""` + DictType string `json:"dictType" comment:""` + CssClass string `json:"cssClass" comment:""` + ListClass string `json:"listClass" comment:""` + IsDefault string `json:"isDefault" comment:""` + Status int `json:"status" comment:""` + Default string `json:"default" comment:""` + Remark string `json:"remark" comment:""` + common.ControlBy +} + +func (s *SysDictDataUpdateReq) Generate(model *models.SysDictData) { + model.DictCode = s.Id + model.DictSort = s.DictSort + model.DictLabel = s.DictLabel + model.DictValue = s.DictValue + model.DictType = s.DictType + model.CssClass = s.CssClass + model.ListClass = s.ListClass + model.IsDefault = s.IsDefault + model.Status = s.Status + model.Default = s.Default + model.Remark = s.Remark +} + +func (s *SysDictDataUpdateReq) GetId() interface{} { + return s.Id +} + +type SysDictDataGetReq struct { + Id int `uri:"dictCode"` +} + +func (s *SysDictDataGetReq) GetId() interface{} { + return s.Id +} + +type SysDictDataDeleteReq struct { + Ids []int `json:"ids"` + common.ControlBy `json:"-"` +} + +func (s *SysDictDataDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/sys_dict_type.go b/app/admin/service/dto/sys_dict_type.go new file mode 100644 index 0000000..db66432 --- /dev/null +++ b/app/admin/service/dto/sys_dict_type.go @@ -0,0 +1,89 @@ +package dto + +import ( + "go-admin/app/admin/models" + + "go-admin/common/dto" + common "go-admin/common/models" +) + +type SysDictTypeGetPageReq struct { + dto.Pagination `search:"-"` + DictId []int `form:"dictId" search:"type:in;column:dict_id;table:sys_dict_type"` + DictName string `form:"dictName" search:"type:icontains;column:dict_name;table:sys_dict_type"` + DictType string `form:"dictType" search:"type:icontains;column:dict_type;table:sys_dict_type"` + Status int `form:"status" search:"type:exact;column:status;table:sys_dict_type"` +} + +type SysDictTypeOrder struct { + DictIdOrder string `search:"type:order;column:dict_id;table:sys_dict_type" form:"dictIdOrder"` +} + +func (m *SysDictTypeGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type SysDictTypeInsertReq struct { + Id int `uri:"id"` + DictName string `json:"dictName"` + DictType string `json:"dictType"` + Status int `json:"status"` + Remark string `json:"remark"` + common.ControlBy +} + +func (s *SysDictTypeInsertReq) Generate(model *models.SysDictType) { + if s.Id != 0 { + model.ID = s.Id + } + model.DictName = s.DictName + model.DictType = s.DictType + model.Status = s.Status + model.Remark = s.Remark + +} + +func (s *SysDictTypeInsertReq) GetId() interface{} { + return s.Id +} + +type SysDictTypeUpdateReq struct { + Id int `uri:"id"` + DictName string `json:"dictName"` + DictType string `json:"dictType"` + Status int `json:"status"` + Remark string `json:"remark"` + common.ControlBy +} + +func (s *SysDictTypeUpdateReq) Generate(model *models.SysDictType) { + if s.Id != 0 { + model.ID = s.Id + } + model.DictName = s.DictName + model.DictType = s.DictType + model.Status = s.Status + model.Remark = s.Remark + +} + +func (s *SysDictTypeUpdateReq) GetId() interface{} { + return s.Id +} + +type SysDictTypeGetReq struct { + Id int `uri:"id"` +} + +func (s *SysDictTypeGetReq) GetId() interface{} { + return s.Id +} + +type SysDictTypeDeleteReq struct { + Ids []int `json:"ids"` + common.ControlBy +} + +func (s *SysDictTypeDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/sys_login_log.go b/app/admin/service/dto/sys_login_log.go new file mode 100644 index 0000000..42a6e45 --- /dev/null +++ b/app/admin/service/dto/sys_login_log.go @@ -0,0 +1,57 @@ +package dto + +import ( + "time" + + "go-admin/common/dto" +) + +type SysLoginLogGetPageReq struct { + dto.Pagination `search:"-"` + Username string `form:"username" search:"type:exact;column:username;table:sys_login_log" comment:"用户名"` + Status string `form:"status" search:"type:exact;column:status;table:sys_login_log" comment:"状态"` + Ipaddr string `form:"ipaddr" search:"type:exact;column:ipaddr;table:sys_login_log" comment:"ip地址"` + LoginLocation string `form:"loginLocation" search:"type:exact;column:login_location;table:sys_login_log" comment:"归属地"` + BeginTime string `form:"beginTime" search:"type:gte;column:ctime;table:sys_login_log" comment:"创建时间"` + EndTime string `form:"endTime" search:"type:lte;column:ctime;table:sys_login_log" comment:"创建时间"` + SysLoginLogOrder +} + +type SysLoginLogOrder struct { + CreatedAtOrder string `search:"type:order;column:created_at;table:sys_login_log" form:"createdAtOrder"` +} + +func (m *SysLoginLogGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type SysLoginLogControl struct { + ID int `uri:"Id" comment:"主键"` // 主键 + Username string `json:"username" comment:"用户名"` + Status string `json:"status" comment:"状态"` + Ipaddr string `json:"ipaddr" comment:"ip地址"` + LoginLocation string `json:"loginLocation" comment:"归属地"` + Browser string `json:"browser" comment:"浏览器"` + Os string `json:"os" comment:"系统"` + Platform string `json:"platform" comment:"固件"` + LoginTime time.Time `json:"loginTime" comment:"登录时间"` + Remark string `json:"remark" comment:"备注"` + Msg string `json:"msg" comment:"信息"` +} + +type SysLoginLogGetReq struct { + Id int `uri:"id"` +} + +func (s *SysLoginLogGetReq) GetId() interface{} { + return s.Id +} + +// SysLoginLogDeleteReq 功能删除请求参数 +type SysLoginLogDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *SysLoginLogDeleteReq) GetId() interface{} { + return s.Ids +} \ No newline at end of file diff --git a/app/admin/service/dto/sys_menu.go b/app/admin/service/dto/sys_menu.go new file mode 100644 index 0000000..67f4674 --- /dev/null +++ b/app/admin/service/dto/sys_menu.go @@ -0,0 +1,159 @@ +package dto + +import ( + "go-admin/app/admin/models" + common "go-admin/common/models" + + "go-admin/common/dto" +) + +// SysMenuGetPageReq 列表或者搜索使用结构体 +type SysMenuGetPageReq struct { + dto.Pagination `search:"-"` + Title string `form:"title" search:"type:contains;column:title;table:sys_menu" comment:"菜单名称"` // 菜单名称 + Visible int `form:"visible" search:"type:exact;column:visible;table:sys_menu" comment:"显示状态"` // 显示状态 +} + +func (m *SysMenuGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type SysMenuInsertReq struct { + MenuId int `uri:"id" comment:"编码"` // 编码 + MenuName string `form:"menuName" comment:"菜单name"` //菜单name + Title string `form:"title" comment:"显示名称"` //显示名称 + Icon string `form:"icon" comment:"图标"` //图标 + Path string `form:"path" comment:"路径"` //路径 + Paths string `form:"paths" comment:"id路径"` //id路径 + MenuType string `form:"menuType" comment:"菜单类型"` //菜单类型 + SysApi []models.SysApi `form:"sysApi"` + Apis []int `form:"apis"` + Action string `form:"action" comment:"请求方式"` //请求方式 + Permission string `form:"permission" comment:"权限编码"` //权限编码 + ParentId int `form:"parentId" comment:"上级菜单"` //上级菜单 + NoCache bool `form:"noCache" comment:"是否缓存"` //是否缓存 + Breadcrumb string `form:"breadcrumb" comment:"是否面包屑"` //是否面包屑 + Component string `form:"component" comment:"组件"` //组件 + Sort int `form:"sort" comment:"排序"` //排序 + Visible string `form:"visible" comment:"是否显示"` //是否显示 + IsFrame string `form:"isFrame" comment:"是否frame"` //是否frame + common.ControlBy +} + +func (s *SysMenuInsertReq) Generate(model *models.SysMenu) { + if s.MenuId != 0 { + model.MenuId = s.MenuId + } + model.MenuName = s.MenuName + model.Title = s.Title + model.Icon = s.Icon + model.Path = s.Path + model.Paths = s.Paths + model.MenuType = s.MenuType + model.Action = s.Action + model.SysApi = s.SysApi + model.Permission = s.Permission + model.ParentId = s.ParentId + model.NoCache = s.NoCache + model.Breadcrumb = s.Breadcrumb + model.Component = s.Component + model.Sort = s.Sort + model.Visible = s.Visible + model.IsFrame = s.IsFrame + if s.CreateBy != 0 { + model.CreateBy = s.CreateBy + } + if s.UpdateBy != 0 { + model.UpdateBy = s.UpdateBy + } +} + +func (s *SysMenuInsertReq) GetId() interface{} { + return s.MenuId +} + +type SysMenuUpdateReq struct { + MenuId int `uri:"id" comment:"编码"` // 编码 + MenuName string `form:"menuName" comment:"菜单name"` //菜单name + Title string `form:"title" comment:"显示名称"` //显示名称 + Icon string `form:"icon" comment:"图标"` //图标 + Path string `form:"path" comment:"路径"` //路径 + Paths string `form:"paths" comment:"id路径"` //id路径 + MenuType string `form:"menuType" comment:"菜单类型"` //菜单类型 + SysApi []models.SysApi `form:"sysApi"` + Apis []int `form:"apis"` + Action string `form:"action" comment:"请求方式"` //请求方式 + Permission string `form:"permission" comment:"权限编码"` //权限编码 + ParentId int `form:"parentId" comment:"上级菜单"` //上级菜单 + NoCache bool `form:"noCache" comment:"是否缓存"` //是否缓存 + Breadcrumb string `form:"breadcrumb" comment:"是否面包屑"` //是否面包屑 + Component string `form:"component" comment:"组件"` //组件 + Sort int `form:"sort" comment:"排序"` //排序 + Visible string `form:"visible" comment:"是否显示"` //是否显示 + IsFrame string `form:"isFrame" comment:"是否frame"` //是否frame + common.ControlBy +} + +func (s *SysMenuUpdateReq) Generate(model *models.SysMenu) { + if s.MenuId != 0 { + model.MenuId = s.MenuId + } + model.MenuName = s.MenuName + model.Title = s.Title + model.Icon = s.Icon + model.Path = s.Path + model.Paths = s.Paths + model.MenuType = s.MenuType + model.Action = s.Action + model.SysApi = s.SysApi + model.Permission = s.Permission + model.ParentId = s.ParentId + model.NoCache = s.NoCache + model.Breadcrumb = s.Breadcrumb + model.Component = s.Component + model.Sort = s.Sort + model.Visible = s.Visible + model.IsFrame = s.IsFrame + if s.CreateBy != 0 { + model.CreateBy = s.CreateBy + } + if s.UpdateBy != 0 { + model.UpdateBy = s.UpdateBy + } +} + +func (s *SysMenuUpdateReq) GetId() interface{} { + return s.MenuId +} + +type SysMenuGetReq struct { + Id int `uri:"id"` +} + +func (s *SysMenuGetReq) GetId() interface{} { + return s.Id +} + +type SysMenuDeleteReq struct { + Ids []int `json:"ids"` + common.ControlBy +} + +func (s *SysMenuDeleteReq) GetId() interface{} { + return s.Ids +} + +type MenuLabel struct { + Id int `json:"id,omitempty" gorm:"-"` + Label string `json:"label,omitempty" gorm:"-"` + Children []MenuLabel `json:"children,omitempty" gorm:"-"` +} + +type MenuRole struct { + models.SysMenu + IsSelect bool `json:"is_select" gorm:"-"` +} + +type SelectRole struct { + RoleId int `uri:"roleId"` +} diff --git a/app/admin/service/dto/sys_opera_log.go b/app/admin/service/dto/sys_opera_log.go new file mode 100644 index 0000000..5df36eb --- /dev/null +++ b/app/admin/service/dto/sys_opera_log.go @@ -0,0 +1,102 @@ +package dto + +import ( + "time" + + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +const ( + OperaStatusEnabel = "1" // 状态-正常 + OperaStatusDisable = "2" // 状态-关闭 +) + +type SysOperaLogGetPageReq struct { + dto.Pagination `search:"-"` + Title string `form:"title" search:"type:contains;column:title;table:sys_opera_log" comment:"操作模块"` + Method string `form:"method" search:"type:contains;column:method;table:sys_opera_log" comment:"函数"` + RequestMethod string `form:"requestMethod" search:"type:contains;column:request_method;table:sys_opera_log" comment:"请求方式: GET POST PUT DELETE"` + OperUrl string `form:"operUrl" search:"type:contains;column:oper_url;table:sys_opera_log" comment:"访问地址"` + OperIp string `form:"operIp" search:"type:exact;column:oper_ip;table:sys_opera_log" comment:"客户端ip"` + Status int `form:"status" search:"type:exact;column:status;table:sys_opera_log" comment:"状态 1:正常 2:关闭"` + BeginTime string `form:"beginTime" search:"type:gte;column:created_at;table:sys_opera_log" comment:"创建时间"` + EndTime string `form:"endTime" search:"type:lte;column:created_at;table:sys_opera_log" comment:"更新时间"` + SysOperaLogOrder +} + +type SysOperaLogOrder struct { + CreatedAtOrder string `search:"type:order;column:created_at;table:sys_opera_log" form:"createdAtOrder"` +} + +func (m *SysOperaLogGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type SysOperaLogControl struct { + ID int `uri:"Id" comment:"编码"` // 编码 + Title string `json:"title" comment:"操作模块"` + BusinessType string `json:"businessType" comment:"操作类型"` + BusinessTypes string `json:"businessTypes" comment:""` + Method string `json:"method" comment:"函数"` + RequestMethod string `json:"requestMethod" comment:"请求方式"` + OperatorType string `json:"operatorType" comment:"操作类型"` + OperName string `json:"operName" comment:"操作者"` + DeptName string `json:"deptName" comment:"部门名称"` + OperUrl string `json:"operUrl" comment:"访问地址"` + OperIp string `json:"operIp" comment:"客户端ip"` + OperLocation string `json:"operLocation" comment:"访问位置"` + OperParam string `json:"operParam" comment:"请求参数"` + Status string `json:"status" comment:"操作状态"` + OperTime time.Time `json:"operTime" comment:"操作时间"` + JsonResult string `json:"jsonResult" comment:"返回数据"` + Remark string `json:"remark" comment:"备注"` + LatencyTime string `json:"latencyTime" comment:"耗时"` + UserAgent string `json:"userAgent" comment:"ua"` +} + +func (s *SysOperaLogControl) Generate() (*models.SysOperaLog, error) { + return &models.SysOperaLog{ + Model: common.Model{Id: s.ID}, + Title: s.Title, + BusinessType: s.BusinessType, + BusinessTypes: s.BusinessTypes, + Method: s.Method, + RequestMethod: s.RequestMethod, + OperatorType: s.OperatorType, + OperName: s.OperName, + DeptName: s.DeptName, + OperUrl: s.OperUrl, + OperIp: s.OperIp, + OperLocation: s.OperLocation, + OperParam: s.OperParam, + Status: s.Status, + OperTime: s.OperTime, + JsonResult: s.JsonResult, + Remark: s.Remark, + LatencyTime: s.LatencyTime, + UserAgent: s.UserAgent, + }, nil +} + +func (s *SysOperaLogControl) GetId() interface{} { + return s.ID +} + +type SysOperaLogGetReq struct { + Id int `uri:"id"` +} + +func (s *SysOperaLogGetReq) GetId() interface{} { + return s.Id +} + +// SysOperaLogDeleteReq 功能删除请求参数 +type SysOperaLogDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *SysOperaLogDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/sys_post.go b/app/admin/service/dto/sys_post.go new file mode 100644 index 0000000..2b432a7 --- /dev/null +++ b/app/admin/service/dto/sys_post.go @@ -0,0 +1,111 @@ +package dto + +import ( + "go-admin/app/admin/models" + common "go-admin/common/models" + + "go-admin/common/dto" +) + +// SysPostPageReq 列表或者搜索使用结构体 +type SysPostPageReq struct { + dto.Pagination `search:"-"` + PostId int `form:"postId" search:"type:exact;column:post_id;table:sys_post" comment:"id"` // id + PostName string `form:"postName" search:"type:contains;column:post_name;table:sys_post" comment:"名称"` // 名称 + PostCode string `form:"postCode" search:"type:contains;column:post_code;table:sys_post" comment:"编码"` // 编码 + Sort int `form:"sort" search:"type:exact;column:sort;table:sys_post" comment:"排序"` // 排序 + Status int `form:"status" search:"type:exact;column:status;table:sys_post" comment:"状态"` // 状态 + Remark string `form:"remark" search:"type:exact;column:remark;table:sys_post" comment:"备注"` // 备注 +} + +func (m *SysPostPageReq) GetNeedSearch() interface{} { + return *m +} + +// SysPostInsertReq 增使用的结构体 +type SysPostInsertReq struct { + PostId int `uri:"id" comment:"id"` + PostName string `form:"postName" comment:"名称"` + PostCode string `form:"postCode" comment:"编码"` + Sort int `form:"sort" comment:"排序"` + Status int `form:"status" comment:"状态"` + Remark string `form:"remark" comment:"备注"` + common.ControlBy +} + +func (s *SysPostInsertReq) Generate(model *models.SysPost) { + model.PostName = s.PostName + model.PostCode = s.PostCode + model.Sort = s.Sort + model.Status = s.Status + model.Remark = s.Remark + if s.ControlBy.UpdateBy != 0 { + model.UpdateBy = s.UpdateBy + } + if s.ControlBy.CreateBy != 0 { + model.CreateBy = s.CreateBy + } +} + +// GetId 获取数据对应的ID +func (s *SysPostInsertReq) GetId() interface{} { + return s.PostId +} + +// SysPostUpdateReq 改使用的结构体 +type SysPostUpdateReq struct { + PostId int `uri:"id" comment:"id"` + PostName string `form:"postName" comment:"名称"` + PostCode string `form:"postCode" comment:"编码"` + Sort int `form:"sort" comment:"排序"` + Status int `form:"status" comment:"状态"` + Remark string `form:"remark" comment:"备注"` + common.ControlBy +} + +func (s *SysPostUpdateReq) Generate(model *models.SysPost) { + model.PostId = s.PostId + model.PostName = s.PostName + model.PostCode = s.PostCode + model.Sort = s.Sort + model.Status = s.Status + model.Remark = s.Remark + if s.ControlBy.UpdateBy != 0 { + model.UpdateBy = s.UpdateBy + } + if s.ControlBy.CreateBy != 0 { + model.CreateBy = s.CreateBy + } +} + +func (s *SysPostUpdateReq) GetId() interface{} { + return s.PostId +} + +// SysPostGetReq 获取单个的结构体 +type SysPostGetReq struct { + Id int `uri:"id"` +} + +func (s *SysPostGetReq) GetId() interface{} { + return s.Id +} + +// SysPostDeleteReq 删除的结构体 +type SysPostDeleteReq struct { + Ids []int `json:"ids"` + common.ControlBy +} + +func (s *SysPostDeleteReq) Generate(model *models.SysPost) { + if s.ControlBy.UpdateBy != 0 { + model.UpdateBy = s.UpdateBy + } + if s.ControlBy.CreateBy != 0 { + model.CreateBy = s.CreateBy + } +} + +func (s *SysPostDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/sys_role.go b/app/admin/service/dto/sys_role.go new file mode 100644 index 0000000..789b85e --- /dev/null +++ b/app/admin/service/dto/sys_role.go @@ -0,0 +1,164 @@ +package dto + +import ( + "go-admin/app/admin/models" + common "go-admin/common/models" + + "go-admin/common/dto" +) + +type SysRoleGetPageReq struct { + dto.Pagination `search:"-"` + + RoleId int `form:"roleId" search:"type:exact;column:role_id;table:sys_role" comment:"角色编码"` // 角色编码 + RoleName string `form:"roleName" search:"type:exact;column:role_name;table:sys_role" comment:"角色名称"` // 角色名称 + Status string `form:"status" search:"type:exact;column:status;table:sys_role" comment:"状态"` // 状态 + RoleKey string `form:"roleKey" search:"type:exact;column:role_key;table:sys_role" comment:"角色代码"` // 角色代码 + RoleSort int `form:"roleSort" search:"type:exact;column:role_sort;table:sys_role" comment:"角色排序"` // 角色排序 + Flag string `form:"flag" search:"type:exact;column:flag;table:sys_role" comment:"标记"` // 标记 + Remark string `form:"remark" search:"type:exact;column:remark;table:sys_role" comment:"备注"` // 备注 + Admin bool `form:"admin" search:"type:exact;column:admin;table:sys_role" comment:"是否管理员"` + DataScope string `form:"dataScope" search:"type:exact;column:data_scope;table:sys_role" comment:"是否管理员"` +} + +type SysRoleOrder struct { + RoleIdOrder string `search:"type:order;column:role_id;table:sys_role" form:"roleIdOrder"` + RoleNameOrder string `search:"type:order;column:role_name;table:sys_role" form:"roleNameOrder"` + RoleSortOrder string `search:"type:order;column:role_sort;table:sys_role" form:"usernameOrder"` + StatusOrder string `search:"type:order;column:status;table:sys_role" form:"statusOrder"` + CreatedAtOrder string `search:"type:order;column:created_at;table:sys_role" form:"createdAtOrder"` +} + +func (m *SysRoleGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type SysRoleInsertReq struct { + RoleId int `uri:"id" comment:"角色编码"` // 角色编码 + RoleName string `form:"roleName" comment:"角色名称"` // 角色名称 + Status string `form:"status" comment:"状态"` // 状态 1禁用 2正常 + RoleKey string `form:"roleKey" comment:"角色代码"` // 角色代码 + RoleSort int `form:"roleSort" comment:"角色排序"` // 角色排序 + Flag string `form:"flag" comment:"标记"` // 标记 + Remark string `form:"remark" comment:"备注"` // 备注 + Admin bool `form:"admin" comment:"是否管理员"` + DataScope string `form:"dataScope"` + SysMenu []models.SysMenu `form:"sysMenu"` + MenuIds []int `form:"menuIds"` + SysDept []models.SysDept `form:"sysDept"` + DeptIds []int `form:"deptIds"` + common.ControlBy +} + +func (s *SysRoleInsertReq) Generate(model *models.SysRole) { + if s.RoleId != 0 { + model.RoleId = s.RoleId + } + model.RoleName = s.RoleName + model.Status = s.Status + model.RoleKey = s.RoleKey + model.RoleSort = s.RoleSort + model.Flag = s.Flag + model.Remark = s.Remark + model.Admin = s.Admin + model.DataScope = s.DataScope + model.SysMenu = &s.SysMenu + model.SysDept = s.SysDept +} + +func (s *SysRoleInsertReq) GetId() interface{} { + return s.RoleId +} + +type SysRoleUpdateReq struct { + RoleId int `uri:"id" comment:"角色编码"` // 角色编码 + RoleName string `form:"roleName" comment:"角色名称"` // 角色名称 + Status string `form:"status" comment:"状态"` // 状态 + RoleKey string `form:"roleKey" comment:"角色代码"` // 角色代码 + RoleSort int `form:"roleSort" comment:"角色排序"` // 角色排序 + Flag string `form:"flag" comment:"标记"` // 标记 + Remark string `form:"remark" comment:"备注"` // 备注 + Admin bool `form:"admin" comment:"是否管理员"` + DataScope string `form:"dataScope"` + SysMenu []models.SysMenu `form:"sysMenu"` + MenuIds []int `form:"menuIds"` + SysDept []models.SysDept `form:"sysDept"` + DeptIds []int `form:"deptIds"` + common.ControlBy +} + +func (s *SysRoleUpdateReq) Generate(model *models.SysRole) { + if s.RoleId != 0 { + model.RoleId = s.RoleId + } + model.RoleName = s.RoleName + model.Status = s.Status + model.RoleKey = s.RoleKey + model.RoleSort = s.RoleSort + model.Flag = s.Flag + model.Remark = s.Remark + model.Admin = s.Admin + model.DataScope = s.DataScope + model.SysMenu = &s.SysMenu + model.SysDept = s.SysDept +} + +func (s *SysRoleUpdateReq) GetId() interface{} { + return s.RoleId +} + +type UpdateStatusReq struct { + RoleId int `form:"roleId" comment:"角色编码"` // 角色编码 + Status string `form:"status" comment:"状态"` // 状态 + common.ControlBy +} + +func (s *UpdateStatusReq) Generate(model *models.SysRole) { + if s.RoleId != 0 { + model.RoleId = s.RoleId + } + model.Status = s.Status +} + +func (s *UpdateStatusReq) GetId() interface{} { + return s.RoleId +} + +type SysRoleByName struct { + RoleName string `form:"role"` // 角色编码 +} + +type SysRoleGetReq struct { + Id int `uri:"id"` +} + +func (s *SysRoleGetReq) GetId() interface{} { + return s.Id +} + +type SysRoleDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *SysRoleDeleteReq) GetId() interface{} { + return s.Ids +} + +// RoleDataScopeReq 角色数据权限修改 +type RoleDataScopeReq struct { + RoleId int `json:"roleId" binding:"required"` + DataScope string `json:"dataScope" binding:"required"` + DeptIds []int `json:"deptIds"` +} + +func (s *RoleDataScopeReq) Generate(model *models.SysRole) { + if s.RoleId != 0 { + model.RoleId = s.RoleId + } + model.DataScope = s.DataScope + model.DeptIds = s.DeptIds +} + +type DeptIdList struct { + DeptId int `json:"DeptId"` +} diff --git a/app/admin/service/dto/sys_user.go b/app/admin/service/dto/sys_user.go new file mode 100644 index 0000000..b1de094 --- /dev/null +++ b/app/admin/service/dto/sys_user.go @@ -0,0 +1,189 @@ +package dto + +import ( + "go-admin/app/admin/models" + + "go-admin/common/dto" + common "go-admin/common/models" +) + +type SysUserGetPageReq struct { + dto.Pagination `search:"-"` + UserId int `form:"userId" search:"type:exact;column:user_id;table:sys_user" comment:"用户ID"` + Username string `form:"username" search:"type:contains;column:username;table:sys_user" comment:"用户名"` + NickName string `form:"nickName" search:"type:contains;column:nick_name;table:sys_user" comment:"昵称"` + Phone string `form:"phone" search:"type:contains;column:phone;table:sys_user" comment:"手机号"` + RoleId string `form:"roleId" search:"type:exact;column:role_id;table:sys_user" comment:"角色ID"` + Sex string `form:"sex" search:"type:exact;column:sex;table:sys_user" comment:"性别"` + Email string `form:"email" search:"type:contains;column:email;table:sys_user" comment:"邮箱"` + PostId string `form:"postId" search:"type:exact;column:post_id;table:sys_user" comment:"岗位"` + Status string `form:"status" search:"type:exact;column:status;table:sys_user" comment:"状态"` + DeptJoin `search:"type:left;on:dept_id:dept_id;table:sys_user;join:sys_dept"` + SysUserOrder +} + +type SysUserOrder struct { + UserIdOrder string `search:"type:order;column:user_id;table:sys_user" form:"userIdOrder"` + UsernameOrder string `search:"type:order;column:username;table:sys_user" form:"usernameOrder"` + StatusOrder string `search:"type:order;column:status;table:sys_user" form:"statusOrder"` + CreatedAtOrder string `search:"type:order;column:created_at;table:sys_user" form:"createdAtOrder"` +} + +type DeptJoin struct { + DeptId string `search:"type:contains;column:dept_path;table:sys_dept" form:"deptId"` +} + +func (m *SysUserGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type ResetSysUserPwdReq struct { + UserId int `json:"userId" comment:"用户ID" vd:"$>0"` // 用户ID + Password string `json:"password" comment:"密码" vd:"len($)>0"` + common.ControlBy +} + +func (s *ResetSysUserPwdReq) GetId() interface{} { + return s.UserId +} + +func (s *ResetSysUserPwdReq) Generate(model *models.SysUser) { + if s.UserId != 0 { + model.UserId = s.UserId + } + model.Password = s.Password +} + +type UpdateSysUserAvatarReq struct { + UserId int `json:"userId" comment:"用户ID" vd:"len($)>0"` // 用户ID + Avatar string `json:"avatar" comment:"头像" vd:"len($)>0"` + common.ControlBy +} + +func (s *UpdateSysUserAvatarReq) GetId() interface{} { + return s.UserId +} + +func (s *UpdateSysUserAvatarReq) Generate(model *models.SysUser) { + if s.UserId != 0 { + model.UserId = s.UserId + } + model.Avatar = s.Avatar +} + +type UpdateSysUserStatusReq struct { + UserId int `json:"userId" comment:"用户ID" vd:"$>0"` // 用户ID + Status string `json:"status" comment:"状态" vd:"len($)>0"` + common.ControlBy +} + +func (s *UpdateSysUserStatusReq) GetId() interface{} { + return s.UserId +} + +func (s *UpdateSysUserStatusReq) Generate(model *models.SysUser) { + if s.UserId != 0 { + model.UserId = s.UserId + } + model.Status = s.Status +} + +type SysUserInsertReq struct { + UserId int `json:"userId" comment:"用户ID"` // 用户ID + Username string `json:"username" comment:"用户名" vd:"len($)>0"` + Password string `json:"password" comment:"密码"` + NickName string `json:"nickName" comment:"昵称" vd:"len($)>0"` + Phone string `json:"phone" comment:"手机号" vd:"len($)>0"` + RoleId int `json:"roleId" comment:"角色ID"` + Avatar string `json:"avatar" comment:"头像"` + Sex string `json:"sex" comment:"性别"` + Email string `json:"email" comment:"邮箱" vd:"len($)>0,email"` + DeptId int `json:"deptId" comment:"部门" vd:"$>0"` + PostId int `json:"postId" comment:"岗位"` + Remark string `json:"remark" comment:"备注"` + Status string `json:"status" comment:"状态" vd:"len($)>0" default:"1"` + common.ControlBy +} + +func (s *SysUserInsertReq) Generate(model *models.SysUser) { + if s.UserId != 0 { + model.UserId = s.UserId + } + model.Username = s.Username + model.Password = s.Password + model.NickName = s.NickName + model.Phone = s.Phone + model.RoleId = s.RoleId + model.Avatar = s.Avatar + model.Sex = s.Sex + model.Email = s.Email + model.DeptId = s.DeptId + model.PostId = s.PostId + model.Remark = s.Remark + model.Status = s.Status + model.CreateBy = s.CreateBy +} + +func (s *SysUserInsertReq) GetId() interface{} { + return s.UserId +} + +type SysUserUpdateReq struct { + UserId int `json:"userId" comment:"用户ID"` // 用户ID + Username string `json:"username" comment:"用户名" vd:"len($)>0"` + NickName string `json:"nickName" comment:"昵称" vd:"len($)>0"` + Phone string `json:"phone" comment:"手机号" vd:"len($)>0"` + RoleId int `json:"roleId" comment:"角色ID"` + Avatar string `json:"avatar" comment:"头像"` + Sex string `json:"sex" comment:"性别"` + Email string `json:"email" comment:"邮箱" vd:"len($)>0,email"` + DeptId int `json:"deptId" comment:"部门" vd:"$>0"` + PostId int `json:"postId" comment:"岗位"` + Remark string `json:"remark" comment:"备注"` + Status string `json:"status" comment:"状态" default:"1"` + common.ControlBy +} + +func (s *SysUserUpdateReq) Generate(model *models.SysUser) { + if s.UserId != 0 { + model.UserId = s.UserId + } + model.Username = s.Username + model.NickName = s.NickName + model.Phone = s.Phone + model.RoleId = s.RoleId + model.Avatar = s.Avatar + model.Sex = s.Sex + model.Email = s.Email + model.DeptId = s.DeptId + model.PostId = s.PostId + model.Remark = s.Remark + model.Status = s.Status +} + +func (s *SysUserUpdateReq) GetId() interface{} { + return s.UserId +} + +type SysUserById struct { + dto.ObjectById + common.ControlBy +} + +func (s *SysUserById) GetId() interface{} { + if len(s.Ids) > 0 { + s.Ids = append(s.Ids, s.Id) + return s.Ids + } + return s.Id +} + +func (s *SysUserById) GenerateM() (common.ActiveRecord, error) { + return &models.SysUser{}, nil +} + +// PassWord 密码 +type PassWord struct { + NewPassword string `json:"newPassword" vd:"len($)>0"` + OldPassword string `json:"oldPassword" vd:"len($)>0"` +} diff --git a/app/admin/service/dto/vts_recharge.go b/app/admin/service/dto/vts_recharge.go new file mode 100644 index 0000000..55eaa07 --- /dev/null +++ b/app/admin/service/dto/vts_recharge.go @@ -0,0 +1,176 @@ +package dto + +import ( + "github.com/shopspring/decimal" + "time" + + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type VtsRechargeGetPageReq struct { + dto.Pagination `search:"-"` + CoinCode string `form:"coinCode" search:"type:exact;column:coin_code;table:vts_recharge" comment:"币种"` + OrderNo string `form:"orderNo" search:"type:exact;column:order_no;table:vts_recharge" comment:"订单号"` + AdUserId int64 `form:"adUserId" search:"type:exact;column:ad_user_id;table:vts_recharge" comment:"用户id"` + VtsRechargeOrder +} + +type VtsRechargeOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:vts_recharge"` + CoinCode string `form:"coinCodeOrder" search:"type:order;column:coin_code;table:vts_recharge"` + TranType string `form:"tranTypeOrder" search:"type:order;column:tran_type;table:vts_recharge"` + OrderNo string `form:"orderNoOrder" search:"type:order;column:order_no;table:vts_recharge"` + AdUserId string `form:"adUserIdOrder" search:"type:order;column:ad_user_id;table:vts_recharge"` + Amount string `form:"amountOrder" search:"type:order;column:amount;table:vts_recharge"` + PriceCurrency string `form:"priceCurrencyOrder" search:"type:order;column:price_currency;table:vts_recharge"` + ReceiveAmount string `form:"receiveAmountOrder" search:"type:order;column:receive_amount;table:vts_recharge"` + PayAmount string `form:"payAmountOrder" search:"type:order;column:pay_amount;table:vts_recharge"` + PayCurrency string `form:"payCurrencyOrder" search:"type:order;column:pay_currency;table:vts_recharge"` + UnderpaidAmount string `form:"underpaidAmountOrder" search:"type:order;column:underpaid_amount;table:vts_recharge"` + OverpaidAmount string `form:"overpaidAmountOrder" search:"type:order;column:overpaid_amount;table:vts_recharge"` + IsRefundable string `form:"isRefundableOrder" search:"type:order;column:is_refundable;table:vts_recharge"` + WalletCreateAt string `form:"walletCreateAtOrder" search:"type:order;column:wallet_create_at;table:vts_recharge"` + WalletId string `form:"walletIdOrder" search:"type:order;column:wallet_id;table:vts_recharge"` + Fee string `form:"feeOrder" search:"type:order;column:fee;table:vts_recharge"` + CallbackAt string `form:"callbackAtOrder" search:"type:order;column:callback_at;table:vts_recharge"` + Status string `form:"statusOrder" search:"type:order;column:status;table:vts_recharge"` + ConfirmStatus string `form:"confirmStatusOrder" search:"type:order;column:confirm_status;table:vts_recharge"` + Remark string `form:"remarkOrder" search:"type:order;column:remark;table:vts_recharge"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:vts_recharge"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:vts_recharge"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:vts_recharge"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:vts_recharge"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:vts_recharge"` +} + +func (m *VtsRechargeGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type VtsRechargeInsertReq struct { + Id int `json:"-" comment:""` // + CoinCode string `json:"coinCode" comment:"币种"` + TranType int `json:"tranType" comment:"类型:1-线上 2-内部"` + OrderNo string `json:"orderNo" comment:"订单号"` + AdUserId int `json:"adUserId" comment:"用户id"` + Amount decimal.Decimal `json:"amount" comment:"订单价格"` + PriceCurrency string `json:"priceCurrency" comment:"CoinGate结算货币代码"` + ReceiveAmount decimal.Decimal `json:"receiveAmount" comment:"实收数量"` + PayAmount decimal.Decimal `json:"payAmount" comment:"实际支付数量"` + PayCurrency string `json:"payCurrency" comment:"实际支付的加密货币"` + UnderpaidAmount decimal.Decimal `json:"underpaidAmount" comment:"买家少付数量"` + OverpaidAmount decimal.Decimal `json:"overpaidAmount" comment:"买家多付数量"` + IsRefundable int `json:"isRefundable" comment:"指示购物者是否可以请求发票退款"` + WalletCreateAt time.Time `json:"walletCreateAt" comment:"第三方创建时间"` + WalletId int `json:"walletId" comment:"第三方钱包订单"` + Fee decimal.Decimal `json:"fee" comment:"手续费"` + CallbackAt time.Time `json:"callbackAt" comment:"回调时间"` + Status string `json:"status" comment:"状态"` + ConfirmStatus string `json:"confirmStatus" comment:"确认状态"` + Remark string `json:"remark" comment:"备注"` + common.ControlBy +} + +func (s *VtsRechargeInsertReq) Generate(model *models.VtsRecharge) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.CoinCode = s.CoinCode + model.TranType = s.TranType + model.OrderNo = s.OrderNo + model.AdUserId = s.AdUserId + model.Amount = s.Amount + model.PriceCurrency = s.PriceCurrency + model.ReceiveAmount = s.ReceiveAmount + model.PayAmount = s.PayAmount + model.PayCurrency = s.PayCurrency + model.UnderpaidAmount = s.UnderpaidAmount + model.OverpaidAmount = s.OverpaidAmount + model.IsRefundable = s.IsRefundable + model.WalletCreateAt = s.WalletCreateAt + model.WalletId = s.WalletId + model.Fee = s.Fee + model.CallbackAt = s.CallbackAt + model.Status = s.Status + model.ConfirmStatus = s.ConfirmStatus + model.Remark = s.Remark + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *VtsRechargeInsertReq) GetId() interface{} { + return s.Id +} + +type VtsRechargeUpdateReq struct { + Id int `uri:"id" comment:""` // + CoinCode string `json:"coinCode" comment:"币种"` + TranType int `json:"tranType" comment:"类型:1-线上 2-内部"` + OrderNo string `json:"orderNo" comment:"订单号"` + AdUserId int `json:"adUserId" comment:"用户id"` + Amount decimal.Decimal `json:"amount" comment:"订单价格"` + PriceCurrency string `json:"priceCurrency" comment:"CoinGate结算货币代码"` + ReceiveAmount decimal.Decimal `json:"receiveAmount" comment:"实收数量"` + PayAmount decimal.Decimal `json:"payAmount" comment:"实际支付数量"` + PayCurrency string `json:"payCurrency" comment:"实际支付的加密货币"` + UnderpaidAmount decimal.Decimal `json:"underpaidAmount" comment:"买家少付数量"` + OverpaidAmount decimal.Decimal `json:"overpaidAmount" comment:"买家多付数量"` + IsRefundable int `json:"isRefundable" comment:"指示购物者是否可以请求发票退款"` + WalletCreateAt time.Time `json:"walletCreateAt" comment:"第三方创建时间"` + WalletId int `json:"walletId" comment:"第三方钱包订单"` + Fee decimal.Decimal `json:"fee" comment:"手续费"` + CallbackAt time.Time `json:"callbackAt" comment:"回调时间"` + Status string `json:"status" comment:"状态"` + ConfirmStatus string `json:"confirmStatus" comment:"确认状态"` + Remark string `json:"remark" comment:"备注"` + common.ControlBy +} + +func (s *VtsRechargeUpdateReq) Generate(model *models.VtsRecharge) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.CoinCode = s.CoinCode + model.TranType = s.TranType + model.OrderNo = s.OrderNo + model.AdUserId = s.AdUserId + model.Amount = s.Amount + model.PriceCurrency = s.PriceCurrency + model.ReceiveAmount = s.ReceiveAmount + model.PayAmount = s.PayAmount + model.PayCurrency = s.PayCurrency + model.UnderpaidAmount = s.UnderpaidAmount + model.OverpaidAmount = s.OverpaidAmount + model.IsRefundable = s.IsRefundable + model.WalletCreateAt = s.WalletCreateAt + model.WalletId = s.WalletId + model.Fee = s.Fee + model.CallbackAt = s.CallbackAt + model.Status = s.Status + model.ConfirmStatus = s.ConfirmStatus + model.Remark = s.Remark + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *VtsRechargeUpdateReq) GetId() interface{} { + return s.Id +} + +// VtsRechargeGetReq 功能获取请求参数 +type VtsRechargeGetReq struct { + Id int `uri:"id"` +} + +func (s *VtsRechargeGetReq) GetId() interface{} { + return s.Id +} + +// VtsRechargeDeleteReq 功能删除请求参数 +type VtsRechargeDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *VtsRechargeDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/line_account_setting.go b/app/admin/service/line_account_setting.go new file mode 100644 index 0000000..3bd1ba6 --- /dev/null +++ b/app/admin/service/line_account_setting.go @@ -0,0 +1,109 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type LineAccountSetting struct { + service.Service +} + +// GetPage 获取LineAccountSetting列表 +func (e *LineAccountSetting) GetPage(c *dto.LineAccountSettingGetPageReq, p *actions.DataPermission, list *[]models.LineAccountSetting, count *int64) error { + var err error + var data models.LineAccountSetting + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineAccountSettingService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LineAccountSetting对象 +func (e *LineAccountSetting) Get(d *dto.LineAccountSettingGetReq, p *actions.DataPermission, model *models.LineAccountSetting) error { + var data models.LineAccountSetting + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineAccountSetting error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineAccountSetting对象 +func (e *LineAccountSetting) Insert(c *dto.LineAccountSettingInsertReq) error { + var err error + var data models.LineAccountSetting + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineAccountSettingService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改LineAccountSetting对象 +func (e *LineAccountSetting) Update(c *dto.LineAccountSettingUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineAccountSetting{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineAccountSettingService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除LineAccountSetting +func (e *LineAccountSetting) Remove(d *dto.LineAccountSettingDeleteReq, p *actions.DataPermission) error { + var data models.LineAccountSetting + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineAccountSetting error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} diff --git a/app/admin/service/line_api_group.go b/app/admin/service/line_api_group.go new file mode 100644 index 0000000..f79a60e --- /dev/null +++ b/app/admin/service/line_api_group.go @@ -0,0 +1,217 @@ +package service + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/bytedance/sonic" + "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + "go-admin/common/const/rediskey" + cDto "go-admin/common/dto" + commondto "go-admin/common/dto" + "go-admin/common/helper" +) + +type LineApiGroup struct { + service.Service +} + +// GetPage 获取LineApiGroup列表 +func (e *LineApiGroup) GetPage(c *dto.LineApiGroupGetPageReq, p *actions.DataPermission, list *[]models.LineApiGroup, count *int64) error { + var err error + var data models.LineApiGroup + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineApiGroupService GetPage error:%s \r\n", err) + return err + } + + for j, group := range *list { + split := strings.Split(group.ApiUserId, ",") + var ( + aApiUserName string + bApiUserName string + ) + for i, s := range split { + if i == 0 { + e.Orm.Model(&models.LineApiUser{}).Where("id = ?", s).Select("api_name").Scan(&aApiUserName) + (*list)[j].AApiName = aApiUserName + } else { + e.Orm.Model(&models.LineApiUser{}).Where("id = ?", s).Select("api_name").Scan(&bApiUserName) + (*list)[j].BApiName = bApiUserName + } + } + + } + + return nil +} + +// Get 获取LineApiGroup对象 +func (e *LineApiGroup) Get(d *dto.LineApiGroupGetReq, p *actions.DataPermission, model *models.LineApiGroup) error { + var data models.LineApiGroup + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineApiGroup error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineApiGroup对象 +func (e *LineApiGroup) Insert(c *dto.LineApiGroupInsertReq) error { + var err error + var data models.LineApiGroup + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineApiGroupService Insert error:%s \r\n", err) + return err + } + + SetApiGroupCache(data) + + return nil +} + +func SetApiGroupCache(data models.LineApiGroup) { + apiUserId := 0 + childApiUserId := 0 + ids := strings.Split(data.ApiUserId, ",") + + if len(ids) == 2 { + apiUserId, _ = strconv.Atoi(ids[0]) + childApiUserId, _ = strconv.Atoi(ids[1]) + + groupCache := commondto.ApiGroupDto{ + Id: data.Id, + Name: data.GroupName, + ApiUserId: apiUserId, + ChildApiUserId: childApiUserId, + } + + groupVal, _ := sonic.MarshalString(&groupCache) + + if groupVal != "" { + if err := helper.DefaultRedis.SetString(fmt.Sprintf(rediskey.ApiGroup, data.Id), groupVal); err != nil { + logger.Errorf("api group redis set error:%s", err) + } + } + + SaveApiInfo(apiUserId, data.Id) + SaveApiInfo(childApiUserId, data.Id) + } +} + +func SaveApiInfo(apiUserId int, groupId int) { + apiInfoVal, _ := helper.DefaultRedis.GetString(fmt.Sprintf(rediskey.API_USER, apiUserId)) + var data models.LineApiUser + + if apiInfoVal != "" { + _ = sonic.UnmarshalString(apiInfoVal, &data) + + if data.Id > 0 { + data.GroupId = int64(groupId) + } + + apiInfoVal, _ := sonic.MarshalString(&data) + + if apiInfoVal != "" { + if err := helper.DefaultRedis.SetString(fmt.Sprintf(rediskey.API_USER, apiUserId), apiInfoVal); err != nil { + logger.Errorf("api user redis set error:%s", err) + } + } + } +} + +// Update 修改LineApiGroup对象 +func (e *LineApiGroup) Update(c *dto.LineApiGroupUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineApiGroup{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineApiGroupService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + + SetApiGroupCache(data) + + return nil +} + +// Remove 删除LineApiGroup +func (e *LineApiGroup) Remove(d *dto.LineApiGroupDeleteReq, p *actions.DataPermission) error { + var data models.LineApiGroup + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineApiGroup error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + userIds := make([]models.LineApiUser, 0) + e.Orm.Model(&models.LineApiUser{}).Where("group_id in ? ", d.GetId()).Select("id").Find(&userIds) + + e.Orm.Model(&models.LineApiUser{}).Where("group_id in ? ", d.GetId()).Updates(map[string]interface{}{ + "subordinate": "0", + "group_id": 0, + }) + + keys := make([]string, 0) + for _, id := range d.Ids { + key := fmt.Sprintf(rediskey.ApiGroup, id) + keys = append(keys, key) + } + if _, err := helper.DefaultRedis.BatchDeleteKeys(keys); err != nil { + e.Log.Error("删除用户组失败,err:", err) + } + + for _, item := range userIds { + if item.Id == 0 { + continue + } + + SaveApiInfo(item.Id, 0) + } + + return nil +} diff --git a/app/admin/service/line_api_user.go b/app/admin/service/line_api_user.go new file mode 100644 index 0000000..cf14bee --- /dev/null +++ b/app/admin/service/line_api_user.go @@ -0,0 +1,335 @@ +package service + +import ( + "errors" + "fmt" + "strings" + + "github.com/bytedance/sonic" + "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + "go-admin/common/const/rediskey" + cDto "go-admin/common/dto" + "go-admin/common/global" + "go-admin/common/helper" + "go-admin/models/binancedto" + "go-admin/services/excservice" +) + +type LineApiUser struct { + service.Service +} + +// GetPage 获取LineApiUser列表 +func (e *LineApiUser) GetPage(c *dto.LineApiUserGetPageReq, p *actions.DataPermission, list *[]models.LineApiUser, count *int64) error { + var err error + var data models.LineApiUser + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineApiUserService GetPage error:%s \r\n", err) + return err + } + + var userSub binancedto.UserSubscribeState + for index := range *list { + val, _ := helper.DefaultRedis.GetString(fmt.Sprintf(global.USER_SUBSCRIBE, (*list)[index].ApiKey)) + + if val != "" { + sonic.Unmarshal([]byte(val), &userSub) + + if userSub.FuturesLastTime != nil { + (*list)[index].FuturesLastTime = userSub.FuturesLastTime.Format("2006-01-02 15:04:05") + } + + if userSub.SpotLastTime != nil { + (*list)[index].SpotLastTime = userSub.SpotLastTime.Format("2006-01-02 15:04:05") + } + } + } + return nil +} + +// Get 获取LineApiUser对象 +func (e *LineApiUser) Get(d *dto.LineApiUserGetReq, p *actions.DataPermission, model *models.LineApiUser) error { + var data models.LineApiUser + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineApiUser error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineApiUser对象 +func (e *LineApiUser) Insert(c *dto.LineApiUserInsertReq) error { + var err error + var data models.LineApiUser + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineApiUserService Insert error:%s \r\n", err) + return err + } + + e.saveCache(data) + val, _ := sonic.MarshalString(&data) + + if val != "" { + if err := helper.DefaultRedis.RPushList(rediskey.ApiUserActiveList, val); err != nil { + logger.Error("添加待连接队列失败") + } + } + + return nil +} + +/* +重启ws +*/ +func (e *LineApiUser) restartWebsocket(data models.LineApiUser) { + socket, ok := excservice.SpotSockets[data.ApiKey] + + if ok && socket != nil { + socket.Stop() + } + + fuSocket, ok := excservice.FutureSockets[data.ApiKey] + + if ok && fuSocket != nil { + fuSocket.Stop() + } + + e.saveCache(data) + + OpenUserBinanceWebsocket(data) +} + +// 保存缓存 +func (e *LineApiUser) saveCache(data models.LineApiUser) { + val, _ := sonic.MarshalString(&data) + + if val != "" { + key := fmt.Sprintf(rediskey.API_USER, data.Id) + + if err := helper.DefaultRedis.SetString(key, val); err != nil { + e.Log.Error("缓存api user失败:", err) + } + } +} + +/* +打开用户websocket订阅 +*/ +func OpenUserBinanceWebsocket(item models.LineApiUser) { + proxyType := "" + ipAddress := "" + ips := strings.Split(strings.ReplaceAll(item.IpAddress, ":", ":"), "://") + + if len(ips) == 2 { + proxyType = ips[0] + + if item.UserPass != "" { + ipAddress = fmt.Sprintf("%s@%s", item.UserPass, ips[1]) + } else { + ipAddress = ips[1] + } + } + + //现货 + if wm, ok := excservice.SpotSockets[item.ApiKey]; ok { + wm.Restart(item.ApiKey, item.ApiSecret, proxyType, ipAddress) + } else { + spotSocket := excservice.NewBinanceWebSocketManager(0, item.ApiKey, item.ApiSecret, proxyType, ipAddress) + + spotSocket.Start() + excservice.SpotSockets[item.ApiKey] = spotSocket + } + + if wm, ok := excservice.FutureSockets[item.ApiKey]; ok { + wm.Restart(item.ApiKey, item.ApiSecret, proxyType, ipAddress) + } else { + //合约 + futureSocket := excservice.NewBinanceWebSocketManager(1, item.ApiKey, item.ApiSecret, proxyType, ipAddress) + + futureSocket.Start() + excservice.FutureSockets[item.ApiKey] = futureSocket + } +} + +// Update 修改LineApiUser对象 +func (e *LineApiUser) Update(c *dto.LineApiUserUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineApiUser{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + oldApiKey := data.ApiKey + + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineApiUserService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + + e.saveCache(data) + + //旧key和新的key不一样,则关闭旧的websocket + if oldApiKey != data.ApiKey { + if err := helper.DefaultRedis.RPushList(rediskey.ApiUserDeleteList, oldApiKey); err != nil { + logger.Error("写入待关闭websocket api key 失败:", err) + } + } + + val, _ := sonic.MarshalString(&data) + + if val != "" { + if err := helper.DefaultRedis.RPushList(rediskey.ApiUserActiveList, val); err != nil { + logger.Error("写入待激活websocket api key 失败:", err) + } + } + + return nil +} + +// Remove 删除LineApiUser +func (e *LineApiUser) Remove(d *dto.LineApiUserDeleteReq, p *actions.DataPermission) error { + var data models.LineApiUser + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineApiUser error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + + //移除缓存 + delKeys := make([]string, 0) + for _, id := range d.Ids { + key := fmt.Sprintf(rediskey.API_USER, id) + delKeys = append(delKeys, key) + } + + _, err := helper.DefaultRedis.BatchDeleteKeys(delKeys) + + if err != nil { + e.Log.Error("批量删除api_user key失败", err) + } + + return nil +} + +// Bind 绑定从属关系 +func (e *LineApiUser) Bind(req *dto.LineApiUserBindSubordinateReq, p *actions.DataPermission) error { + split := strings.Split(req.UserIdStr, ",") + if len(split) != 2 { + return errors.New("绑定关系必须是两个账号") + } + + for _, s := range split { + var apiUserinfo models.LineApiUser + e.Orm.Model(&models.LineApiUser{}).Where("id = ? AND subordinate <> '0'", s).Find(&apiUserinfo) + if apiUserinfo.Id > 0 { + return errors.New("该用户已经绑定关系") + } + } + + group := models.LineApiGroup{ + GroupName: req.GroupName, + ApiUserId: req.UserIdStr, + } + + err := e.Orm.Transaction(func(tx *gorm.DB) error { + + err := tx.Model(&models.LineApiGroup{}).Create(&group).Error + if err != nil { + return err + } + + for i, s := range split { + if i == 0 { + err = e.Orm.Model(&models.LineApiUser{}).Where("id = ?", s).Updates(map[string]interface{}{ + "subordinate": "1", + "group_id": group.Id, + }).Error + if err != nil { + return err + } + } else { + err = e.Orm.Model(&models.LineApiUser{}).Where("id = ?", s).Updates(map[string]interface{}{ + "subordinate": "2", + "group_id": group.Id, + }).Error + if err != nil { + return err + } + } + } + return nil + + }) + if err != nil { + return errors.New("操作失败") + } + + SetApiGroupCache(group) + return nil +} + +// GetUser 获取未绑定的用户列表 +func (e *LineApiUser) GetUser(list *[]models.LineApiUser) error { + var err error + var data models.LineApiUser + + err = e.Orm.Model(&data).Where("subordinate = '0'").Find(list).Error + if err != nil { + e.Log.Errorf("LineApiUserService error:%s \r\n", err) + return err + } + return nil +} + +// GetMainUser 获取主账号 +func (e *LineApiUser) GetMainUser(list *[]models.LineApiUser) error { + var err error + var data models.LineApiUser + + err = e.Orm.Model(&data).Where("subordinate = '1' AND group_id > 0").Find(list).Error + if err != nil { + e.Log.Errorf("LineApiUserService error:%s \r\n", err) + return err + } + return nil +} diff --git a/app/admin/service/line_coinnetwork.go b/app/admin/service/line_coinnetwork.go new file mode 100644 index 0000000..c10c412 --- /dev/null +++ b/app/admin/service/line_coinnetwork.go @@ -0,0 +1,109 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type LineCoinnetwork struct { + service.Service +} + +// GetPage 获取LineCoinnetwork列表 +func (e *LineCoinnetwork) GetPage(c *dto.LineCoinnetworkGetPageReq, p *actions.DataPermission, list *[]models.LineCoinnetwork, count *int64) error { + var err error + var data models.LineCoinnetwork + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineCoinnetworkService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LineCoinnetwork对象 +func (e *LineCoinnetwork) Get(d *dto.LineCoinnetworkGetReq, p *actions.DataPermission, model *models.LineCoinnetwork) error { + var data models.LineCoinnetwork + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineCoinnetwork error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineCoinnetwork对象 +func (e *LineCoinnetwork) Insert(c *dto.LineCoinnetworkInsertReq) error { + var err error + var data models.LineCoinnetwork + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineCoinnetworkService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改LineCoinnetwork对象 +func (e *LineCoinnetwork) Update(c *dto.LineCoinnetworkUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineCoinnetwork{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineCoinnetworkService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除LineCoinnetwork +func (e *LineCoinnetwork) Remove(d *dto.LineCoinnetworkDeleteReq, p *actions.DataPermission) error { + var data models.LineCoinnetwork + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineCoinnetwork error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} diff --git a/app/admin/service/line_cointonetwork.go b/app/admin/service/line_cointonetwork.go new file mode 100644 index 0000000..ad0b495 --- /dev/null +++ b/app/admin/service/line_cointonetwork.go @@ -0,0 +1,109 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type LineCointonetwork struct { + service.Service +} + +// GetPage 获取LineCointonetwork列表 +func (e *LineCointonetwork) GetPage(c *dto.LineCointonetworkGetPageReq, p *actions.DataPermission, list *[]models.LineCointonetwork, count *int64) error { + var err error + var data models.LineCointonetwork + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineCointonetworkService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LineCointonetwork对象 +func (e *LineCointonetwork) Get(d *dto.LineCointonetworkGetReq, p *actions.DataPermission, model *models.LineCointonetwork) error { + var data models.LineCointonetwork + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineCointonetwork error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineCointonetwork对象 +func (e *LineCointonetwork) Insert(c *dto.LineCointonetworkInsertReq) error { + var err error + var data models.LineCointonetwork + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineCointonetworkService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改LineCointonetwork对象 +func (e *LineCointonetwork) Update(c *dto.LineCointonetworkUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineCointonetwork{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineCointonetworkService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除LineCointonetwork +func (e *LineCointonetwork) Remove(d *dto.LineCointonetworkDeleteReq, p *actions.DataPermission) error { + var data models.LineCointonetwork + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineCointonetwork error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} diff --git a/app/admin/service/line_direction.go b/app/admin/service/line_direction.go new file mode 100644 index 0000000..3a3e482 --- /dev/null +++ b/app/admin/service/line_direction.go @@ -0,0 +1,222 @@ +package service + +import ( + "errors" + "fmt" + "go-admin/pkg/utility" + "strings" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type LineDirection struct { + service.Service +} + +// GetPage 获取LineDirection列表 +func (e *LineDirection) GetPage(c *dto.LineDirectionGetPageReq, p *actions.DataPermission, list *[]models.LineDirection, count *int64) error { + var err error + var data models.LineDirection + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineDirectionService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LineDirection对象 +func (e *LineDirection) Get(d *dto.LineDirectionGetReq, p *actions.DataPermission, model *models.LineDirection) error { + var data models.LineDirection + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineDirection error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineDirection对象 +func (e *LineDirection) Insert(c *dto.LineDirectionInsertReq) error { + var err error + var data models.LineDirection + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineDirectionService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改LineDirection对象 +func (e *LineDirection) Update(c *dto.LineDirectionUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineDirection{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineDirectionService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除LineDirection +func (e *LineDirection) Remove(d *dto.LineDirectionDeleteReq, p *actions.DataPermission) error { + var data models.LineDirection + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineDirection error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} + +// AddDirection 新增预估方向 +func (e *LineDirection) AddDirection(req dto.AddDirectionReq) error { + var accountInfo models.LineAccountSetting + e.Orm.Model(&models.LineAccountSetting{}).Where("user_name = ?", req.AccountName).Find(&accountInfo) + if accountInfo.Id <= 0 || accountInfo.Password != req.Password { + return errors.New("账号密码错误请重新输入") + } + + req.BuyPoint1 = strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(req.BuyPoint1, ",", "."), "。", "."), " ", "") + req.BuyPoint2 = strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(req.BuyPoint2, ",", "."), "。", "."), " ", "") + req.BuyPoint3 = strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(req.BuyPoint3, ",", "."), "。", "."), " ", "") + req.SellPoint1 = strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(req.SellPoint1, ",", "."), "。", "."), " ", "") + req.SellPoint2 = strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(req.SellPoint2, ",", "."), "。", "."), " ", "") + req.SellPoint3 = strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(req.SellPoint3, ",", "."), "。", "."), " ", "") + + req.Symbol = utility.ReplaceSuffix(strings.ReplaceAll(req.Symbol, " ", ""), "4小时", "USDT") + req.Symbol = strings.ReplaceAll(req.Symbol, "1小时", "USDT") + req.Symbol = strings.ReplaceAll(req.Symbol, "1日", "USDT") + req.Symbol = strings.ReplaceAll(req.Symbol, "四小时", "USDT") + req.Symbol = strings.ReplaceAll(req.Symbol, "一小时", "USDT") + req.Symbol = strings.ReplaceAll(req.Symbol, "一日", "USDT") + + symbolType := utility.StringToInt(req.Type) + + direction := models.LineDirection{ + Symbol: req.Symbol, + Type: int64(symbolType), + BuyPoint1: req.BuyPoint1, + BuyPoint2: req.BuyPoint2, + BuyPoint3: req.BuyPoint3, + SellPoint1: req.SellPoint1, + SellPoint2: req.SellPoint2, + SellPoint3: req.SellPoint3, + Direction: req.Direction, + AiAnswer: req.AiAnswer, + } + var directionInfo models.LineDirection + e.Orm.Model(&models.LineDirection{}).Where("symbol = ? AND type = ?", req.Symbol, symbolType).Find(&directionInfo) + if directionInfo.Id > 0 { + e.Orm.Model(&models.LineDirection{}).Where("id = ?", directionInfo.Id).Delete(&models.LineDirection{}) + } + err := e.Orm.Model(&models.LineDirection{}).Create(&direction).Error + if err != nil { + return fmt.Errorf("生成数据失败 err:%s", err) + } + + return nil +} + +// 重新统计aicoin 分组 +func (e *LineDirection) ReloadGroup() error { + directions := make([]models.LineDirection, 0) + groups := make([]models.LineSymbolGroup, 0) + spotSymbols := make(map[string][]string) + futSymbols := make(map[string][]string) + groupNames := make([]string, 0) + + if err := e.Orm.Model(&models.LineDirection{}).Find(&directions).Error; err != nil { + return err + } + for _, v := range directions { + if v.Type == 1 { + spotSymbols[v.Direction] = append(spotSymbols[v.Direction], v.Symbol) + } else { + futSymbols[v.Direction] = append(futSymbols[v.Direction], v.Symbol) + } + } + + for key, item := range spotSymbols { + groupName := fmt.Sprintf("现货-%s", key) + + group := models.LineSymbolGroup{ + GroupName: groupName, + Type: "1", + GroupType: "1", + Symbol: strings.Join(item, ","), + } + + groupNames = append(groupNames, groupName) + groups = append(groups, group) + } + + for key, item := range futSymbols { + groupName := fmt.Sprintf("合约-%s", key) + + group := models.LineSymbolGroup{ + GroupName: groupName, + Type: "2", + GroupType: "1", + Symbol: strings.Join(item, ","), + } + + groupNames = append(groupNames, groupName) + groups = append(groups, group) + } + + err := e.Orm.Transaction(func(tx *gorm.DB) error { + if err2 := tx.Delete(&models.LineSymbolGroup{}, "group_name in ?", groupNames).Error; err2 != nil { + return err2 + } + if err2 := tx.Create(&groups).Error; err2 != nil { + return err2 + } + + return nil + }) + + return err +} diff --git a/app/admin/service/line_order_template_logs.go b/app/admin/service/line_order_template_logs.go new file mode 100644 index 0000000..f0083aa --- /dev/null +++ b/app/admin/service/line_order_template_logs.go @@ -0,0 +1,109 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type LineOrderTemplateLogs struct { + service.Service +} + +// GetPage 获取LineOrderTemplateLogs列表 +func (e *LineOrderTemplateLogs) GetPage(c *dto.LineOrderTemplateLogsGetPageReq, p *actions.DataPermission, list *[]models.LineOrderTemplateLogs, count *int64) error { + var err error + var data models.LineOrderTemplateLogs + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineOrderTemplateLogsService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LineOrderTemplateLogs对象 +func (e *LineOrderTemplateLogs) Get(d *dto.LineOrderTemplateLogsGetReq, p *actions.DataPermission, model *models.LineOrderTemplateLogs) error { + var data models.LineOrderTemplateLogs + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineOrderTemplateLogs error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineOrderTemplateLogs对象 +func (e *LineOrderTemplateLogs) Insert(c *dto.LineOrderTemplateLogsInsertReq) error { + var err error + var data models.LineOrderTemplateLogs + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineOrderTemplateLogsService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改LineOrderTemplateLogs对象 +func (e *LineOrderTemplateLogs) Update(c *dto.LineOrderTemplateLogsUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineOrderTemplateLogs{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineOrderTemplateLogsService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除LineOrderTemplateLogs +func (e *LineOrderTemplateLogs) Remove(d *dto.LineOrderTemplateLogsDeleteReq, p *actions.DataPermission) error { + var data models.LineOrderTemplateLogs + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineOrderTemplateLogs error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} diff --git a/app/admin/service/line_pre_order.go b/app/admin/service/line_pre_order.go new file mode 100644 index 0000000..f9b88b8 --- /dev/null +++ b/app/admin/service/line_pre_order.go @@ -0,0 +1,1308 @@ +package service + +import ( + "errors" + "fmt" + "go-admin/common/const/rediskey" + "go-admin/common/global" + "go-admin/common/helper" + models2 "go-admin/models" + "go-admin/models/binancedto" + "go-admin/pkg/utility" + "go-admin/pkg/utility/snowflakehelper" + "go-admin/services/binanceservice" + "strconv" + "strings" + "sync" + "time" + + "github.com/bytedance/sonic" + "github.com/jinzhu/copier" + "github.com/shopspring/decimal" + + "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type LinePreOrder struct { + service.Service +} + +// GetPage 获取LinePreOrder列表 +func (e *LinePreOrder) GetPage(c *dto.LinePreOrderGetPageReq, p *actions.DataPermission, list *[]models.LinePreOrder, count *int64) error { + var err error + var data models.LinePreOrder + tx := e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ).Where("pid = 0").Joins("JOIN line_api_user on line_api_user.id = line_pre_order.api_id"). + Joins("JOIN line_pre_order_status on line_pre_order_status.order_sn = line_pre_order.order_sn"). + Select("line_pre_order.*,line_api_user.api_name,line_pre_order_status.add_position_status,line_pre_order_status.hedge_status") + if c.AddPositionStatus >= 0 { + tx.Where("line_pre_order_status.add_position_status = ?", c.AddPositionStatus) + } + + if c.HedgeStatus >= 0 { + tx.Where("line_pre_order_status.hedge_status = ?", c.HedgeStatus) + } + + err = tx. + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LinePreOrderService GetPage error:%s \r\n", err) + return err + } + for i, order := range *list { + var childNum int64 + e.Orm.Model(&models.LinePreOrder{}).Where("pid = ?", order.Id).Count(&childNum) + (*list)[i].ChildNum = childNum + } + return nil +} + +// GetOrderPage 获取LinePreOrder列表 +func (e *LinePreOrder) GetOrderPage(c *dto.LinePreOrderGetPageReq, p *actions.DataPermission, list *[]models.LinePreOrder, count *int64) error { + var err error + var data models.LinePreOrder + tx := e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ).Where("pid = 0 AND status != '0'").Joins("JOIN line_api_user on line_api_user.id = line_pre_order.api_id"). + Joins("JOIN line_pre_order_status on line_pre_order_status.order_id = line_pre_order.id"). + Select("line_pre_order.*,line_api_user.api_name,line_pre_order_status.add_position_status,line_pre_order_status.hedge_status") + if c.AddPositionStatus >= 0 { + tx.Where("line_pre_order_status.add_position_status = ?", c.AddPositionStatus) + } + + if c.HedgeStatus >= 0 { + tx.Where("line_pre_order_status.hedge_status = ?", c.HedgeStatus) + } + err = tx. + Find(list).Limit(-1).Offset(-1). + Count(count).Error + //err = e.Orm.Model(&data). + // Scopes( + // cDto.MakeCondition(c.GetNeedSearch()), + // cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + // actions.Permission(data.TableName(), p), + // ).Where("pid = 0 AND status != '0'").Joins("JOIN line_api_user on line_api_user.id = line_pre_order.api_id").Select("line_pre_order.*,line_api_user.api_name"). + // Find(list).Limit(-1).Offset(-1). + // Count(count).Error + if err != nil { + e.Log.Errorf("LinePreOrderService GetChildPage error:%s \r\n", err) + return err + } + for i, order := range *list { + var childNum int64 + e.Orm.Model(&models.LinePreOrder{}).Where("pid = ?", order.Id).Count(&childNum) + (*list)[i].ChildNum = childNum + } + return nil +} + +// GetAllPage 获取LinePreOrder列表 +func (e *LinePreOrder) GetAllPage(c *dto.LinePreOrderGetPageReq, p *actions.DataPermission, list *[]models.LinePreOrder, count *int64) error { + var err error + var data models.LinePreOrder + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LinePreOrderService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// GetChildList 获取子订单列表 +func (e *LinePreOrder) GetChildList(req *dto.GetChildOrderReq, p *actions.DataPermission, order *[]models.LinePreOrder) error { + err := e.Orm.Model(&models.LinePreOrder{}).Where("pid = ?", req.Id). + Order("id asc"). + Joins("JOIN line_api_user on line_api_user.id = line_pre_order.api_id").Select("line_pre_order.*,line_api_user.api_name"). + Find(order).Error + if err != nil { + e.Log.Errorf("LinePreOrderService GetPage error:%s \r\n", err) + return err + } + return nil + +} + +// Get 获取LinePreOrder对象 +func (e *LinePreOrder) Get(d *dto.LinePreOrderGetReq, p *actions.DataPermission, model *models.LinePreOrder) error { + var data models.LinePreOrder + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLinePreOrder error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LinePreOrder对象 +func (e *LinePreOrder) Insert(c *dto.LinePreOrderInsertReq) error { + var err error + var data models.LinePreOrder + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LinePreOrderService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改LinePreOrder对象 +func (e *LinePreOrder) Update(c *dto.LinePreOrderUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LinePreOrder{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LinePreOrderService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除LinePreOrder +func (e *LinePreOrder) Remove(d *dto.LinePreOrderDeleteReq, p *actions.DataPermission) error { + var data models.LinePreOrder + var list []models.LinePreOrder + e.Orm.Model(&models.LinePreOrder{}).Where("id in ?", d.GetId()).Find(&list) + //for _, order := range list { + //if order.Status != "0" { + // return errors.New(fmt.Sprintf("订单id %d 已被触发 无法被删除", order.Id)) + //} + //} + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Unscoped().Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLinePreOrder error:%s \r\n", err) + return err + } + ints := make([]int, 0) + stoplossMarket, _ := helper.DefaultRedis.GetAllList(rediskey.StoplossMarkt) + stoploss := binancedto.StoplossMarket{} + apiIds := make([]int, 0) + + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + + //删除的缓存 + for _, order := range list { + redisList := dto.PreOrderRedisList{ + Id: order.Id, + Symbol: order.Symbol, + Price: order.Price, + Site: order.Site, + ApiId: order.ApiId, + OrderSn: order.OrderSn, + QuoteSymbol: order.QuoteSymbol, + } + + if !utility.ContainsInt(apiIds, order.ApiId) { + apiIds = append(apiIds, order.ApiId) + } + + tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, fmt.Sprintf("%s:%s", global.TICKER_SPOT, order.Symbol)) + redisList.Price = utility.StringToDecimal(redisList.Price).Truncate(int32(tradeSet.PriceDigit)).String() + marshal, _ := sonic.Marshal(redisList) + if order.SymbolType == 1 { + helper.DefaultRedis.LRem(rediskey.PreFutOrderList, string(marshal)) + } else { + helper.DefaultRedis.LRem(rediskey.PreSpotOrderList, string(marshal)) + } + + binanceservice.MainClosePositionClearCache(order.Id, order.CoverType) + + helper.DefaultRedis.DeleteString(fmt.Sprintf(rediskey.SpotHedgeClosePosition, order.Id, order.Symbol)) + helper.DefaultRedis.DeleteString(fmt.Sprintf(rediskey.FuturesHedgeClosePosition, order.Id, order.Symbol)) + + helper.DefaultRedis.DeleteString(fmt.Sprintf(rediskey.HoldeA, order.Id)) + helper.DefaultRedis.DeleteString(fmt.Sprintf(rediskey.HoldeB, order.Id)) + helper.DefaultRedis.DeleteString(fmt.Sprintf(rediskey.HedgeClosePosition, order.Id)) + + for _, st := range stoplossMarket { + sonic.Unmarshal([]byte(st), &stoploss) + + if stoploss.Pid == order.Id { + helper.DefaultRedis.LRem(rediskey.StoplossMarkt, st) + } + } + + ints = append(ints, order.Id) + } + + if len(apiIds) > 0 { + apiSymbols := make([]models.LinePreOrder, 0) + userHold := make(map[int][]string) + + e.Orm.Model(&models.LinePreOrder{}).Where("pid = 0 AND status IN ('1','5','9','13') AND api_id IN ?", apiIds). + Group("api_id,symbol,quote_symbol"). + Select("api_id,symbol,quote_symbol"). + Find(&apiSymbols) + + for _, apiId := range apiIds { + bre := false + + for _, api := range apiSymbols { + if apiId == api.ApiId { + coin := utility.ReplaceSuffix(api.Symbol, api.QuoteSymbol, "") + userHold[api.ApiId] = append(userHold[api.ApiId], coin) + bre = true + } + } + + if !bre { + userHold[apiId] = make([]string, 0) + } + } + + for key, val := range userHold { + key := fmt.Sprintf(rediskey.UserHolding, key) + + if len(val) > 0 { + if err := helper.DefaultRedis.SetListCache(key, 0, val...); err != nil { + logger.Errorf("重新设置用户持仓失败 apiid:%v,err:%v", key, err) + } + } else { + if err := helper.DefaultRedis.SetEmptyListCache(key, 0); err != nil { + logger.Errorf("重新设置用户持仓为空 失败 apiid:%v,err:%v", key, err) + } + } + } + } + + if len(ints) > 0 { + e.Orm.Model(&models.LinePreOrder{}).Where("pid >0 AND pid in ?", ints).Unscoped().Delete(&models.LinePreOrder{}) + } + return nil +} + +// AddPreOrder 单个添加 +func (e *LinePreOrder) AddPreOrder(req *dto.LineAddPreOrderReq, p *actions.DataPermission, errs *[]error, tickerSymbol string) error { + apiUserIds := strings.Split(req.ApiUserId, ",") + if req.SaveTemplate == "2" || req.SaveTemplate == "1" { //2 = 只保存模板 1= 保存模板并下单 + var templateLog dto.LineAddPreOrderReq + copier.Copy(&templateLog, req) + //templateLog := *req + templateLog.SaveTemplate = "0" + templateLog.TemplateName = "" + marshal, _ := sonic.Marshal(templateLog) + saveTemplateParams := models.LineOrderTemplateLogs{ + Name: req.TemplateName, + UserId: 0, + Params: string(marshal), + Type: 1, + Switch: "0", + } + e.Orm.Model(&models.LineOrderTemplateLogs{}).Create(&saveTemplateParams) + } + if req.SaveTemplate == "2" { + return nil + } + var key string + //var tickerSymbol string + if req.OrderType == global.SYMBOL_SPOT { + key = fmt.Sprintf("%s:%s", global.TICKER_SPOT, req.Symbol) + //tickerSymbol = helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val() + } else { + key = fmt.Sprintf("%s:%s", global.TICKER_FUTURES, req.Symbol) + //tickerSymbol = helper.DefaultRedis.Get(rediskey.FutSymbolTicker).Val() + } + + for _, id := range apiUserIds { + if req.Site == "SELL" && req.OrderType == global.SYMBOL_SPOT { + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%s 获取交易对:%s 现货不支持卖出操作", id, req.Symbol))) + continue + } + var AddOrder models.LinePreOrder + var profitOrder models.LinePreOrder + var stopOrder models.LinePreOrder + + //获取交易对 + tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, key) + orderCount := e.CheckRepeatOrder(req.OrderType, id, req.Site, tradeSet.Coin) + if orderCount > 0 { + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%s 获取交易对:%s 该交易对已存在,请勿重复下单", id, req.Symbol))) + continue + } + var tickerPrice decimal.Decimal + ticker := models2.TradeSet{} + tickerVal := "" + + if req.SymbolType == 1 { + tickerVal, _ = helper.DefaultRedis.GetString(fmt.Sprintf(global.TICKER_SPOT, req.ExchangeType)) + } else { + tickerVal, _ = helper.DefaultRedis.GetString(fmt.Sprintf(global.TICKER_FUTURES, req.ExchangeType)) + } + + if tickerVal == "" { + logger.Error("获取交易对:%s 现货ticker失败", req.Symbol) + continue + } + err := sonic.Unmarshal([]byte(tickerVal), &ticker) + + if ticker.LastPrice == "" { + logger.Error("获取交易对:%s 现货ticker失败,err:%v", req.Symbol, err) + continue + } + + if tickerPrice.Equal(decimal.Zero) { //redis 没有这个值 + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%s 获取交易对:%s 交易行情出错", id, req.Symbol))) + continue + } + + AddOrder.SignPriceType = "new" + AddOrder.BuyPrice = req.BuyPrice //购买多少U + AddOrder.SymbolType = req.SymbolType //交易对类型1= 现货 2 = 合约 + AddOrder.OrderType = req.OrderType //订单类型 + AddOrder.CoverType = req.CoverType //对冲类型 0=无对冲 1= 现货对合约 2=合约对合约 3 合约对现货 + if req.ExpireHour == 0 { //过期时间默认为4个小时 + req.ExpireHour = 4 + } + AddOrder.ExpireTime = time.Now().Add(time.Duration(req.ExpireHour) * time.Hour) //过期时间 + AddOrder.MainOrderType = req.MainOrderType + AddOrder.HedgeOrderType = req.HedgeOrderType + AddOrder.Site = req.Site + if req.PricePattern == "percentage" { + AddOrder.Rate = req.Price + orderPrice, _ := decimal.NewFromString(req.Price) //下单价百分比 10% + priceRate := orderPrice.Div(decimal.NewFromInt(100)) //下单价除100 =0.1 + AddOrder.SignPrice = tickerPrice.String() + if strings.ToUpper(req.Site) == "BUY" { //购买方向 + //实际下单价格 + truncate := tickerPrice.Mul(decimal.NewFromInt(1).Sub(priceRate)).Truncate(int32(tradeSet.PriceDigit)) + AddOrder.Price = truncate.String() + } else { + truncate := tickerPrice.Mul(decimal.NewFromInt(1).Add(priceRate)).Truncate(int32(tradeSet.PriceDigit)) + AddOrder.Price = truncate.String() + } + + } else { //实际价格下单 + AddOrder.Price = utility.StringToDecimal(req.Price).Truncate(int32(tradeSet.PriceDigit)).String() + AddOrder.SignPrice = req.Price + AddOrder.SignPriceType = "mixture" + AddOrder.Rate = "0" + } + buyPrice, _ := decimal.NewFromString(req.BuyPrice) //购买多少U + var symbolInfo models.LineSymbol + e.Orm.Model(&models.LineSymbol{}).Where("type = ? AND symbol = ?", req.SymbolType, req.Symbol).Find(&symbolInfo) + //计算购买数量 判断是否是否是U本位 + if symbolInfo.QuoteAsset != "USDT" { //不是U本位 + //获取币本位兑换u的价格 + ticker2 := models2.TradeSet{} + tickerVal, _ := helper.DefaultRedis.GetString(fmt.Sprintf(global.TICKER_SPOT, req.ExchangeType, strings.ToUpper(symbolInfo.QuoteAsset+"USDT"))) + + if tickerVal == "" { + logger.Error("查询行情失败") + continue + } + + err = sonic.Unmarshal([]byte(tickerVal), &ticker2) + + if ticker2.LastPrice == "" { + logger.Errorf("查询行情失败 %s err:%v", strings.ToUpper(symbolInfo.QuoteAsset+"USDT"), err) + continue + } + //LTCBTC --> LTCUSDT + uTickerPrice, _ := decimal.NewFromString(ticker2.LastPrice) //94069 + //换算成U + //div := decimal.NewFromInt(1).Div(uTickerPrice) //0.0000106365 + //在换算成对应交易对对应的价值 + //LTCBTC --> LTCUSDT == LTCUSDT -- 100.502 + div := tickerPrice.Div(decimal.NewFromInt(1).Div(uTickerPrice)) + //计算下单数量 + AddOrder.Num = buyPrice.Div(div).Truncate(int32(tradeSet.AmountDigit)).String() + } else { + fromString, _ := decimal.NewFromString(AddOrder.Price) + AddOrder.Num = buyPrice.Div(fromString).Truncate(int32(tradeSet.AmountDigit)).String() + } + if utility.StringToFloat64(AddOrder.Num) < tradeSet.MinQty { + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%s 获取交易对:%s 小于最小下单数量", id, req.Symbol))) + continue + } + AddOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10) + AddOrder.ApiId, _ = strconv.Atoi(id) + AddOrder.Symbol = req.Symbol + AddOrder.QuoteSymbol = symbolInfo.QuoteAsset + AddOrder.Pid = 0 + AddOrder.GroupId = "0" + AddOrder.AdminId = "0" + AddOrder.Status = 0 + copier.Copy(&profitOrder, &AddOrder) + copier.Copy(&stopOrder, &AddOrder) + + err = e.Orm.Model(&models.LinePreOrder{}).Omit("id", "save_template", "template_name").Create(&AddOrder).Error + if err != nil { + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%s 获取交易对:%s 生成订单失败", id, req.Symbol))) + continue + } + + preOrderStatus := models.LinePreOrderStatus{} + preOrderStatus.OrderId = AddOrder.Id + preOrderStatus.OrderSn = AddOrder.OrderSn + + e.Orm.Model(&models.LinePreOrderStatus{}).Create(&preOrderStatus) + + list := dto.PreOrderRedisList{ + Id: AddOrder.Id, + Symbol: AddOrder.Symbol, + Price: AddOrder.Price, + Site: AddOrder.Site, + ApiId: AddOrder.ApiId, + OrderSn: AddOrder.OrderSn, + QuoteSymbol: AddOrder.QuoteSymbol, + } + marshal, _ := sonic.Marshal(&list) + var preKey string + if AddOrder.OrderType == global.SYMBOL_SPOT { + preKey = rediskey.PreSpotOrderList + } else { + preKey = rediskey.PreFutOrderList + } + helper.DefaultRedis.LPushList(preKey, string(marshal)) + + //是否有止盈止损订单 + if req.Profit != "" { + if strings.ToUpper(req.Site) == "BUY" { + profitOrder.Site = "SELL" + profitOrder.Price = decimal.NewFromFloat(utility.StringToFloat64(AddOrder.Price) * (1 + utility.StringToFloat64(req.Profit)/100)).Truncate(int32(tradeSet.PriceDigit)).String() + } else { + profitOrder.Site = "BUY" + profitOrder.Price = decimal.NewFromFloat(utility.StringToFloat64(AddOrder.Price) * (1 - utility.StringToFloat64(req.Profit)/100)).Truncate(int32(tradeSet.PriceDigit)).String() + } + profitOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10) + profitOrder.Pid = AddOrder.Id + profitOrder.OrderType = 2 + profitOrder.Status = 0 + profitOrder.Rate = req.Profit + + e.Orm.Model(&models.LinePreOrder{}).Omit("id", "save_template", "template_name").Create(&profitOrder) + } + + if req.StopPrice != "" { + if strings.ToUpper(req.Site) == "BUY" { + stopOrder.Site = "SELL" + stopOrder.Price = decimal.NewFromFloat(utility.StringToFloat64(AddOrder.Price) * (1 - utility.StringToFloat64(req.StopPrice)/100)).Truncate(int32(tradeSet.PriceDigit)).String() + } else { + stopOrder.Site = "BUY" + stopOrder.Price = decimal.NewFromFloat(utility.StringToFloat64(AddOrder.Price) * (1 + utility.StringToFloat64(req.StopPrice)/100)).Truncate(int32(tradeSet.PriceDigit)).String() + } + stopOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10) + stopOrder.Pid = AddOrder.Id + stopOrder.OrderType = 1 + stopOrder.Status = 0 + stopOrder.Rate = req.StopPrice + + e.Orm.Model(&models.LinePreOrder{}).Omit("id", "save_template", "template_name").Create(&stopOrder) + } + } + + return nil +} + +// CheckRepeatOrder 检查重复下单 检查基础货币 +func (e *LinePreOrder) CheckRepeatOrder(orderType int, apiUserId, site, baseCoin string) int64 { + var count int64 + e.Orm.Model(&models.LinePreOrder{}).Where("api_id = ? AND symbol like ? AND order_type = ? AND site = ? AND `status` != '2' AND `status`!='4' AND `status` != '0' AND `status` != '6'", apiUserId, baseCoin+"%", orderType, site).Count(&count) + return count +} + +// AddBatchPreOrder 批量添加 +func (e *LinePreOrder) AddBatchPreOrder(batchReq *dto.LineBatchAddPreOrderReq, p *actions.DataPermission, errs *[]error) error { + if batchReq.SaveTemplate == "2" || batchReq.SaveTemplate == "1" { //2 = 只保存模板 1= 保存模板并下单 + var templateLog dto.LineBatchAddPreOrderReq + copier.Copy(&templateLog, batchReq) + //templateLog = *batchReq + templateLog.SaveTemplate = "0" + templateLog.TemplateName = "" + marshal, _ := sonic.Marshal(templateLog) + saveTemplateParams := models.LineOrderTemplateLogs{ + Name: batchReq.TemplateName, + UserId: 0, + Params: string(marshal), + Type: 2, + Switch: "0", + } + e.Orm.Model(&models.LineOrderTemplateLogs{}).Create(&saveTemplateParams) + } + + if batchReq.SaveTemplate == "2" { + return nil + } + + if batchReq.SymbolGroupId != "" { + var symbolGroupInfo models.LineSymbolGroup + e.Orm.Model(&models.LineSymbolGroup{}).Where("id = ?", utility.StringToInt(batchReq.SymbolGroupId)).Find(&symbolGroupInfo) + if symbolGroupInfo.Id <= 0 || symbolGroupInfo.Symbol == "" { + *errs = append(*errs, errors.New(fmt.Sprintf("选择的交易对组:%s不存在或交易对组的交易对为空", batchReq.SymbolGroupId))) + return nil + } + batchReq.Symbol = symbolGroupInfo.Symbol + } + + //脚本次数 + if batchReq.OrderNum > 0 { + var tickerSymbol string + if batchReq.SymbolType == global.SYMBOL_SPOT { + tickerSymbol = helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val() + } else { + tickerSymbol = helper.DefaultRedis.Get(rediskey.FutSymbolTicker).Val() + } + apiUserIds := strings.Split(batchReq.ApiUserId, ",") + if batchReq.Script == "1" { + //scriptLogs := make([]models.LinePreScript, 0) + logParams := *batchReq + for _, id := range apiUserIds { + for j := 1; j <= batchReq.OrderNum; j++ { + var log models.LinePreScript + logParams.SaveTemplate = "0" + logParams.TemplateName = "" + logParams.Script = "" + marshal, _ := sonic.Marshal(logParams) + log.ApiId = int64(utility.StringToInt(id)) + log.ScriptNum = int64(j) + log.ScriptParams = string(marshal) + log.AdminId = 0 + log.Status = "0" + //scriptLogs = append(scriptLogs, log) + err := e.Orm.Model(&models.LinePreScript{}).Create(&log).Error + if err != nil { + *errs = append(*errs, errors.New(fmt.Sprintf("记录脚本失败:%+v", err.Error()))) + return nil + } + helper.DefaultRedis.RPushList(rediskey.PreOrderScriptList, utility.IntToString(log.Id)) + } + } + + return nil + } + for _, id := range apiUserIds { + for j := 0; j < batchReq.OrderNum; j++ { + symbols := strings.Split(batchReq.Symbol, ",") + for _, symbol := range symbols { + var req dto.LineAddPreOrderReq + req.ExchangeType = batchReq.ExchangeType + req.OrderType = batchReq.OrderType + req.Symbol = symbol + req.ApiUserId = id + req.Site = batchReq.Site + req.BuyPrice = batchReq.BuyPrice + req.PricePattern = batchReq.PricePattern + req.Price = batchReq.Price + req.Profit = batchReq.Profit + req.StopPrice = batchReq.StopPrice + req.PriceType = batchReq.PriceType + req.CoverType = batchReq.CoverType + req.ExpireHour = batchReq.ExpireHour + req.MainOrderType = batchReq.MainOrderType + req.HedgeOrderType = batchReq.HedgeOrderType + + e.AddPreOrder(&req, p, errs, tickerSymbol) + } + } + } + return nil + } else { + *errs = append(*errs, errors.New("请选择运行次数")) + return nil + } +} + +// QuickAddPreOrder 模板快速下单 +func (e *LinePreOrder) QuickAddPreOrder(quickReq *dto.QuickAddPreOrderReq, p *actions.DataPermission, errs *[]error) error { + templateLogs := make([]models.LineOrderTemplateLogs, 0) + e.Orm.Model(&models.LineOrderTemplateLogs{}).Where("id in ?", strings.Split(quickReq.Ids, ",")).Find(&templateLogs) + for _, log := range templateLogs { + //单独添加 + if log.Type == 1 { + var addPreOrderParams dto.LineAddPreOrderReq + sonic.Unmarshal([]byte(log.Params), &addPreOrderParams) + + var tickerSymbol string + if addPreOrderParams.OrderType == global.SYMBOL_SPOT { + tickerSymbol = helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val() + } else { + tickerSymbol = helper.DefaultRedis.Get(rediskey.FutSymbolTicker).Val() + } + err := e.AddPreOrder(&addPreOrderParams, p, errs, tickerSymbol) + if err != nil { + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%s 获取交易对:%s 生成订单失败", addPreOrderParams.ApiUserId, addPreOrderParams.Symbol))) + continue + } + } + + //批量添加 + if log.Type == 2 { + var batchAddPreOrder dto.LineBatchAddPreOrderReq + sonic.Unmarshal([]byte(log.Params), &batchAddPreOrder) + e.AddBatchPreOrder(&batchAddPreOrder, p, errs) + } + } + return nil +} + +// Lever 设置杠杆 +func (e *LinePreOrder) Lever(req *dto.LeverReq, p *actions.DataPermission, errs *[]error) { + users := make([]models.LineApiUser, 0) + err := e.Orm.Model(&models.LineApiUser{}).Where("id in ? ", strings.Split(req.ApiUserIds, ",")).Find(&users).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + *errs = append(*errs, errors.New(fmt.Sprintf("设置杠杆失败:%+v", err.Error()))) + return + } + var symbols string + if req.Symbol != "" { + symbols = req.Symbol + } else { + var symbolGroupInfo models.LineSymbolGroup + e.Orm.Model(&models.LineSymbolGroup{}).Where("id = ?", req.GroupId).Find(&symbolGroupInfo) + symbols = symbolGroupInfo.Symbol + } + + if req.IsAll == 1 { + lineSymbols := make([]models.LineSymbol, 0) + futSymbols := make([]string, 0) + e.Orm.Model(&models.LineSymbol{}).Where("type = '2' AND switch = '1'").Find(&lineSymbols) + for _, symbol := range lineSymbols { + futSymbols = append(futSymbols, symbol.Symbol) + } + if len(futSymbols) != 0 { + symbols = strings.Join(futSymbols, ",") + } + } + + for _, user := range users { + var client *helper.BinanceClient + if user.UserPass == "" { + client, _ = helper.NewBinanceClient(user.ApiKey, user.ApiSecret, "", user.IpAddress) + } else { + client, _ = helper.NewBinanceClient(user.ApiKey, user.ApiSecret, "socks5", user.UserPass+"@"+user.IpAddress) + } + //client.SendFuturesRequestAuth("/") + symbolsSlice := strings.Split(symbols, ",") + for _, s := range symbolsSlice { + params := map[string]string{ + "leverage": utility.IntToString(req.Leverage), + "symbol": s, + } + resp, _, err := client.SendFuturesRequestAuth("/fapi/v1/leverage", "POST", params) + if err != nil { + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 交易对:%s 设置杠杆失败:%+v", user.Id, s, err.Error()))) + continue + } + var dataMap map[string]interface{} + if err := sonic.Unmarshal(resp, &dataMap); err != nil { + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 交易对:%s 设置杠杆失败:%+v", user.Id, s, err.Error()))) + continue + } + + if _, ok := dataMap["leverage"]; !ok { + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 交易对:%s 设置杠杆失败:%+v", user.Id, s, dataMap["message"]))) + continue + } + } + } + return +} + +// MarginType 设置仓位模式 +func (e *LinePreOrder) MarginType(req *dto.MarginTypeReq, p *actions.DataPermission, errs *[]error) { + users := make([]models.LineApiUser, 0) + err := e.Orm.Model(&models.LineApiUser{}).Where("id in ? ", strings.Split(req.ApiUserIds, ",")).Find(&users).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + *errs = append(*errs, errors.New(fmt.Sprintf("设置杠杆失败:%+v", err.Error()))) + return + } + var symbols string + if req.Symbol != "" { + symbols = req.Symbol + } else { + var symbolGroupInfo models.LineSymbolGroup + e.Orm.Model(&models.LineSymbolGroup{}).Where("id = ?", req.GroupId).Find(&symbolGroupInfo) + symbols = symbolGroupInfo.Symbol + } + + if req.IsAll == 1 { + lineSymbols := make([]models.LineSymbol, 0) + futSymbols := make([]string, 0) + e.Orm.Model(&models.LineSymbol{}).Where("type = '2' AND switch = '1'").Find(&lineSymbols) + for _, symbol := range lineSymbols { + futSymbols = append(futSymbols, symbol.Symbol) + } + if len(futSymbols) != 0 { + symbols = strings.Join(futSymbols, ",") + } + } + + for _, user := range users { + var client *helper.BinanceClient + if user.UserPass == "" { + client, _ = helper.NewBinanceClient(user.ApiKey, user.ApiSecret, "", user.IpAddress) + } else { + client, _ = helper.NewBinanceClient(user.ApiKey, user.ApiSecret, "socks5", user.UserPass+"@"+user.IpAddress) + } + //client.SendFuturesRequestAuth("/") + symbolsSlice := strings.Split(symbols, ",") + for _, s := range symbolsSlice { + params := map[string]string{ + "marginType": req.MarginType, + "symbol": s, + } + resp, _, err := client.SendFuturesRequestAuth("/fapi/v1/marginType", "POST", params) + if err != nil { + *errs = append(*errs, fmt.Errorf("api_id:%d 交易对:%s 设置仓位失败:%+v", user.Id, s, err.Error())) + continue + } + var dataMap map[string]interface{} + if err := sonic.Unmarshal(resp, &dataMap); err != nil { + *errs = append(*errs, fmt.Errorf("api_id:%d 交易对:%s 设置仓位失败:%+v", user.Id, s, err.Error())) + continue + } + code, ok := dataMap["code"] + if !ok { + *errs = append(*errs, fmt.Errorf("api_id:%d 交易对:%s 设置仓位失败:%+v", user.Id, s, dataMap["message"])) + continue + } + if code.(float64) != 200 { + *errs = append(*errs, fmt.Errorf("api_id:%d 交易对:%s 设置仓位失败:%+v", user.Id, s, dataMap["message"])) + continue + } + } + } +} + +// CancelOpenOrder 取消委托 +func (e *LinePreOrder) CancelOpenOrder(req *dto.CancelOpenOrderReq, errs *[]error) { + newClientOrderIdList := make([]string, 0) + var apiUserInfo models.LineApiUser + e.Orm.Model(&models.LineApiUser{}).Where("id = ?", req.ApiId).Find(&apiUserInfo) + futApi := binanceservice.FutRestApi{} + spotApi := binanceservice.SpotRestApi{} + + //取消指定订单号的委托 + if req.OrderSn != "" && req.Symbol != "" { + var err error + var orderInfo models.LinePreOrder + e.Orm.Model(&models.LinePreOrder{}).Where("symbol = ? AND order_sn = ? AND status = '5'", req.Symbol, req.OrderSn).Find(&orderInfo) + if orderInfo.Id <= 0 { + *errs = append(*errs, errors.New(fmt.Sprintf("取消委托失败:%+v", "未找到可以取消的委托"))) + return + } + if req.OrderType == 1 { //现货 + err = spotApi.CancelOpenOrderByOrderSn(apiUserInfo, req.Symbol, req.OrderSn) + } else { + newClientOrderIdList = append(newClientOrderIdList, req.OrderSn) + err = futApi.CancelBatchFutOrder(apiUserInfo, req.Symbol, newClientOrderIdList) + } + if err != nil { + *errs = append(*errs, errors.New(fmt.Sprintf("取消委托失败:%+v", err.Error()))) + return + } + } else { + var orderList []models.LinePreOrder + e.Orm.Model(&models.LinePreOrder{}).Where("api_id = ? AND pid = 0 status = '5' AND order_type = ?", req.ApiId, strconv.Itoa(req.OrderType)).Find(&orderList) + if len(orderList) <= 0 { + *errs = append(*errs, errors.New(fmt.Sprintf("没有可撤销的委托"))) + return + } + for _, order := range orderList { + + if order.OrderType == global.SYMBOL_SPOT { + err := spotApi.CancelOpenOrderByOrderSn(apiUserInfo, req.Symbol, req.OrderSn) + if err != nil { + *errs = append(*errs, errors.New(fmt.Sprintf("取消委托失败:%+v", err.Error()))) + continue + } + } else { + err := futApi.CancelBatchFutOrder(apiUserInfo, order.Symbol, []string{order.OrderSn}) + if err != nil { + *errs = append(*errs, errors.New(fmt.Sprintf("取消委托失败:%+v", err.Error()))) + continue + } + } + } + } +} + +// ClearAll 一键清除数据 +func (e *LinePreOrder) ClearAll() error { + _, err := helper.DefaultRedis.BatchDeleteKeys([]string{rediskey.PreSpotOrderList, rediskey.PreFutOrderList, + rediskey.SpotStopLossList, rediskey.FuturesStopLossList, rediskey.SpotAddPositionList, rediskey.FuturesAddPositionList, + }) + if err != nil { + e.Log.Errorf("Service RemoveLinePreOrder error:%s \r\n", err) + return err + } + prefixs := []string{ + "api_user_hold", + "spot_trigger_lock", + "fut_trigger_lock", + "fut_trigger_stop_lock", + "spot_trigger_stop_lock", + "spot_addposition_trigger", + "fut_addposition_trigger", + "spot_hedge_close_position", + "futures_hedge_close_position", + "spot_callback", + "fut_callback", + "holde_a", + "holde_b", + "stop_loss_markt", + } + err = helper.DefaultRedis.DeleteKeysByPrefix(prefixs...) + if err != nil { + e.Log.Errorf("Service RemoveLinePreOrder error:%s \r\n", err) + return err + } + e.Orm.Model(&models.LinePreOrder{}).Exec("TRUNCATE TABLE line_pre_order") + e.Orm.Model(&models.LinePreOrder{}).Exec("TRUNCATE TABLE line_pre_order_status") + return err +} + +func (e *LinePreOrder) ManuallyCover(req dto.ManuallyCover, p *actions.DataPermission, errs *[]error) { + symbols := strings.Split(req.Symbols, ",") + futApi := binanceservice.FutRestApi{} + spotApi := binanceservice.SpotRestApi{} + addPositionService := binanceservice.AddPosition{Db: e.Orm} + + var wg sync.WaitGroup // 用于等待所有协程完成 + var mu sync.Mutex // 用于保护错误切片的并发访问 + for _, symbol := range symbols { + wg.Add(1) // 增加协程计数 + go func(symbol string) { + defer wg.Done() // 协程完成后减少计数 + if err := addPositionService.ProcessSymbol(req, symbol, &futApi, &spotApi, errs); err != nil { + mu.Lock() // 加锁保护错误切片 + *errs = append(*errs, err) + mu.Unlock() // 解锁 + } + }(symbol) + } + wg.Wait() // 等待所有协程完成 +} + +// GetTargetSymbol 获取目标交易对信息 +func (e *LinePreOrder) GetTargetSymbol(symbol string, symbolType int) (string, bool, models.LineSymbol, error) { + var targetSymbol string + var notUsdt bool + var symbolInfo models.LineSymbol + + // 处理非 USDT 交易对 + if !strings.HasSuffix(symbol, "USDT") { + notUsdt = true + if err := e.Orm.Model(&models.LineSymbol{}).Where("symbol = ? AND type = ?", symbol, utility.IntToString(symbolType)).Find(&symbolInfo).Error; err != nil { + return "", false, models.LineSymbol{}, err + } + if symbolInfo.Id <= 0 { + return "", false, models.LineSymbol{}, fmt.Errorf("未找到交易对信息") + } + targetSymbol = symbolInfo.BaseAsset + "USDT" + } else { + targetSymbol = symbol + } + + return targetSymbol, notUsdt, symbolInfo, nil +} + +func (e *LinePreOrder) GetOrderInfo(req dto.ManuallyCover, symbol, orderType, site, status string) (models.LinePreOrder, error) { + var orderInfo models.LinePreOrder + if err := e.Orm.Model(models.LinePreOrder{}).Where("api_id = ? AND symbol = ? AND order_type = ? AND site = ? AND status = ?", req.ApiId, symbol, orderType, site, status).Find(&orderInfo).Error; err != nil { + return models.LinePreOrder{}, err + } + if orderInfo.Id <= 0 { + return models.LinePreOrder{}, fmt.Errorf("未找到主仓信息") + } + return orderInfo, nil +} + +func (e *LinePreOrder) GetFutOrderInfo(req dto.ManuallyCover, symbol, orderType, status string) (models.LinePreOrder, error) { + var orderInfo models.LinePreOrder + if err := e.Orm.Model(models.LinePreOrder{}).Where("api_id = ? AND symbol = ? AND order_type = ? AND status = ? AND cover_type = 2", req.ApiId, symbol, orderType, status).Find(&orderInfo).Error; err != nil { + return models.LinePreOrder{}, err + } + if orderInfo.Id <= 0 { + return models.LinePreOrder{}, fmt.Errorf("未找到主仓信息") + } + return orderInfo, nil +} + +// GetFutSpotOrderInfo 获取合约对现货的订单信息 +func (e *LinePreOrder) GetFutSpotOrderInfo(req dto.ManuallyCover, symbol, orderType, status string) (models.LinePreOrder, error) { + var orderInfo models.LinePreOrder + if err := e.Orm.Model(models.LinePreOrder{}).Where("api_id = ? AND symbol = ? AND order_type = ? AND status = ? AND cover_type = 3", req.ApiId, symbol, orderType, status).Find(&orderInfo).Error; err != nil { + return models.LinePreOrder{}, err + } + if orderInfo.Id <= 0 { + return models.LinePreOrder{}, fmt.Errorf("未找到主仓信息") + } + return orderInfo, nil +} + +// CalculateAmount 计算加仓数量 +func (e *LinePreOrder) CalculateAmount(req dto.ManuallyCover, totalNum, lastPrice decimal.Decimal, amountDigit int, notUsdt bool, symbolInfo models.LineSymbol) (decimal.Decimal, error) { + var amt decimal.Decimal + if req.CoverType == 1 { + decimalValue := utility.StringToDecimal(req.Value).Div(decimal.NewFromInt(100)) + amt = totalNum.Mul(decimalValue) + } else { + decimalValue := utility.StringToDecimal(req.Value) + if notUsdt { + tickerSymbolMaps := make([]dto.Ticker, 0) + tickerSymbol := helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val() + if err := sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps); err != nil { + return decimal.Zero, err + } + + var tickerPrice decimal.Decimal + for _, symbolMap := range tickerSymbolMaps { + if symbolMap.Symbol == strings.ToUpper(symbolInfo.BaseAsset+"USDT") { + tickerPrice, _ = decimal.NewFromString(symbolMap.Price) + break + } + } + + for _, symbolMap := range tickerSymbolMaps { + if symbolMap.Symbol == strings.ToUpper(symbolInfo.QuoteAsset+"USDT") { + uTickerPrice, _ := decimal.NewFromString(symbolMap.Price) + div := tickerPrice.Div(decimal.NewFromInt(1).Div(uTickerPrice)) + amt = decimalValue.Div(div) + break + } + } + } else { + amt = decimalValue.Div(lastPrice) + } + } + return amt.Truncate(int32(amountDigit)), nil +} + +// SpotClosePosition 现货单个交易对平仓 +func (e *LinePreOrder) SpotClosePosition(position *dto.ClosePosition, errs *[]error) { + var apiUserInfo models.LineApiUser + e.Orm.Model(&models.LineApiUser{}).Where("id = ?", position.ApiId).Find(&apiUserInfo) + client := binanceservice.GetClient(&apiUserInfo) + + resp, _, err := client.SendSpotAuth("/api/v3/account", "GET", map[string]interface{}{ + "omitZeroBalances": true, + }) + + if err != nil { + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 获取账户信息失败", position.ApiId))) + return + } + var balanceInfo binanceservice.SpotAccountInfo + sonic.Unmarshal(resp, &balanceInfo) + + if len(balanceInfo.Balances) == 0 { + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 没有可平仓的交易对", position.ApiId))) + } + + //查询已经开仓的现货交易对 + var spotList []models.LinePreOrder + if position.Symbol == "" { //全平 + e.Orm.Model(&models.LinePreOrder{}).Where("api_id = ? AND status = '9' AND pid = 0 AND order_type = '1'", position.ApiId).Find(&spotList) + } else { + e.Orm.Model(&models.LinePreOrder{}).Where("api_id = ? AND symbol = ? AND status = '9' AND pid = 0 AND order_type = '1'", position.ApiId, position.Symbol).Find(&spotList) + } + if len(spotList) <= 0 { + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 没有可平仓的交易对", position.ApiId))) + } + api := binanceservice.SpotRestApi{} + + for _, list := range spotList { + for _, balance := range balanceInfo.Balances { + suffix := utility.ReplaceSuffix(list.Symbol, list.QuoteSymbol, "") + if utility.StringToDecimal(balance.Free).GreaterThan(decimal.Zero) && balance.Asset == suffix { + //锁仓数量大于0 + if utility.StringToDecimal(balance.Locked).GreaterThan(decimal.Zero) { + //撤销之前的委托 + client.SendSpotAuth("/api/v3/openOrders", "DELETE", map[string]string{ + "symbol": list.Symbol, + }) + } + key := fmt.Sprintf("%s:%s", global.TICKER_SPOT, list.Symbol) + tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, key) + total := utility.StringToDecimal(balance.Free).Add(utility.StringToDecimal(balance.Locked)).Truncate(int32(tradeSet.AmountDigit)) + lastPrice := api.GetSpotSymbolLastPrice(list.Symbol) + var price decimal.Decimal + paramsMaps := make(map[string]string) + if total.GreaterThan(decimal.NewFromFloat(tradeSet.MinQty)) { + paramsMaps = map[string]string{ + "symbol": list.Symbol, + "side": "SELL", + "quantity": total.String(), + "type": "LIMIT", + "newClientOrderId": utility.Int64ToString(snowflakehelper.GetOrderId()), + "timeInForce": "GTC", + } + price = lastPrice.Mul(decimal.NewFromInt(1).Sub(utility.StringToDecimal(position.Rate).Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)) + paramsMaps["price"] = price.String() + } else { + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 下单数量小于最小下单数量", position.ApiId))) + continue + } + order := models.LinePreOrder{ + ExchangeType: global.EXCHANGE_BINANCE, + Pid: list.Id, + ApiId: list.ApiId, + GroupId: "0", + Symbol: list.Symbol, + QuoteSymbol: list.QuoteSymbol, + SignPrice: lastPrice.String(), + SignPriceType: "new", + Rate: position.Rate, + Price: price.String(), + Num: total.String(), + BuyPrice: "0", + Site: "SELL", + OrderSn: paramsMaps["newClientOrderId"], + OrderType: 3, + Desc: "", + Status: 0, + AdminId: "0", + CloseType: 0, + CoverType: list.CoverType, + ExpireTime: time.Now().Add(time.Hour * 24 * 30), + MainOrderType: list.MainOrderType, + HedgeOrderType: list.HedgeOrderType, + Child: nil, + } + err := e.Orm.Model(&models.LinePreOrder{}).Create(&order).Error + if err != nil { + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 生成订单失败:%s", position.ApiId, err.Error()))) + continue + } + + //下订单 + _, _, err = client.SendSpotAuth("/api/v3/order", "POST", paramsMaps) + if err != nil { + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 币安下订单失败:%s", position.ApiId, err.Error()))) + continue + } + binanceservice.MainClosePositionClearCache(list.Id, list.CoverType) + } + } + } +} + +// FutClosePosition 合约平仓 +func (e *LinePreOrder) FutClosePosition(position *dto.ClosePosition, errs *[]error) { + var apiUserInfo models.LineApiUser + e.Orm.Model(&models.LineApiUser{}).Where("id = ?", position.ApiId).Find(&apiUserInfo) + //client := binanceservice.GetClient(&apiUserInfo) + + //查询已经开仓的合约交易对 + var futList []models.LinePreOrder + if position.Symbol == "" { + e.Orm.Model(&models.LinePreOrder{}).Where("api_id = ? AND status = '13' AND pid = 0 AND order_type = '2'", position.ApiId).Find(&futList) + } else { + e.Orm.Model(&models.LinePreOrder{}).Where("api_id = ? AND symbol = ? AND status = '13' AND pid = 0 AND order_type = '2'", position.ApiId, position.Symbol).Find(&futList) + } + if len(futList) <= 0 { + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 没有可平仓的交易对", position.ApiId))) + return + } + + api := binanceservice.FutRestApi{} + + for _, list := range futList { + risks, err := api.GetPositionV3(&apiUserInfo, list.Symbol) + if err != nil { + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 获取仓位信息时 没有可平仓的交易对 err: %s", position.ApiId, err))) + continue + } + + key := fmt.Sprintf(global.TICKER_FUTURES, list.SymbolType, list.Symbol) + tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, key) + lastPrice := api.GetFutSymbolLastPrice(list.Symbol) + for _, risk := range risks { + positionAmt := utility.StringToDecimal(risk.PositionAmt) + var riskSide string + var orderSide string + if positionAmt.GreaterThan(decimal.Zero) { + riskSide = "BUY" + } else { + riskSide = "SELL" + } + if riskSide == list.Site { + var price decimal.Decimal + if list.Site == "BUY" && risk.PositionSide == "LONG" { //做多 + //根据仓位数量去平多 + orderSide = "SELL" + price = lastPrice.Mul(decimal.NewFromInt(1).Add(utility.StringToDecimal(position.Rate).Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)) + } else if list.Site == "SELL" && risk.PositionSide == "SHORT" { + orderSide = "BUY" + price = lastPrice.Mul(decimal.NewFromInt(1).Sub(utility.StringToDecimal(position.Rate).Div(decimal.NewFromInt(100)))).Truncate(int32(tradeSet.PriceDigit)) + } + + if price.LessThanOrEqual(decimal.Zero) { + continue + } + + if positionAmt.Abs().LessThanOrEqual(decimal.NewFromFloat(tradeSet.MinQty)) { + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 下单数量小于最小下单数量", position.ApiId))) + continue + } + + order := models.LinePreOrder{ + Pid: list.Id, + ApiId: list.ApiId, + GroupId: "0", + Symbol: list.Symbol, + QuoteSymbol: list.QuoteSymbol, + SignPrice: lastPrice.String(), + SignPriceType: "new", + Rate: position.Rate, + Price: price.String(), + Num: positionAmt.Abs().String(), + BuyPrice: "0", + Site: orderSide, + OrderSn: utility.Int64ToString(snowflakehelper.GetOrderId()), + OrderType: 3, + Desc: "", + Status: 0, + AdminId: "0", + CloseType: 0, + CoverType: list.CoverType, + ExpireTime: time.Now().Add(24 * 30 * time.Hour), + MainOrderType: list.MainOrderType, + HedgeOrderType: list.HedgeOrderType, + Child: nil, + } + err = e.Orm.Model(&models.LinePreOrder{}).Create(&order).Error + if err != nil { + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 生成平仓单错误:%s", position.ApiId, err))) + continue + } + + //撤销合约的委托 + api.CancelAllFutOrder(apiUserInfo, list.Symbol) + //side=BUY&positionSide=LONG是开多, + //side=SELL&positionSide=LONG是平多, + //side=SELL&positionSide=SHORT是开空, + //side=BUY&positionSide=SHORT是平空。 + if orderSide != "" { + if orderSide == "BUY" { //平空 + err = api.ClosePosition(list.Symbol, order.OrderSn, utility.StringToDecimal(order.Num), "BUY", "SHORT", apiUserInfo, "LIMIT", "0", price) + } else { // 平多 + err = api.ClosePosition(list.Symbol, order.OrderSn, utility.StringToDecimal(order.Num), "SELL", "LONG", apiUserInfo, "LIMIT", "0", price) + } + if err != nil { + *errs = append(*errs, errors.New(fmt.Sprintf("api_id:%d 币安平仓单错误:%s", position.ApiId, err))) + continue + } + // 主单平仓删除缓存 + binanceservice.MainClosePositionClearCache(list.Id, list.CoverType) + } + } + } + } + +} + +// ClearUnTriggered 清除待触发的交易对 +func (e *LinePreOrder) ClearUnTriggered() error { + var orderLists []models.LinePreOrder + e.Orm.Model(&models.LinePreOrder{}).Where("pid = 0 AND status = '0'").Find(&orderLists).Unscoped().Delete(&models.LinePreOrder{}) + + for _, order := range orderLists { + redisList := dto.PreOrderRedisList{ + Id: order.Id, + Symbol: order.Symbol, + Price: order.Price, + Site: order.Site, + ApiId: order.ApiId, + OrderSn: order.OrderSn, + QuoteSymbol: order.QuoteSymbol, + } + tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, fmt.Sprintf("%s:%s", global.TICKER_SPOT, order.Symbol)) + redisList.Price = utility.StringToDecimal(redisList.Price).Truncate(int32(tradeSet.PriceDigit)).String() + marshal, _ := sonic.Marshal(redisList) + if order.SymbolType == 1 { + helper.DefaultRedis.LRem(rediskey.PreFutOrderList, string(marshal)) + } else { + helper.DefaultRedis.LRem(rediskey.PreSpotOrderList, string(marshal)) + } + e.Orm.Model(&models.LinePreOrder{}).Where("pid = ?", order.Id).Unscoped().Delete(&models.LinePreOrder{}) + } + return nil +} + +func (e *LinePreOrder) QueryOrder(req *dto.QueryOrderReq) (res interface{}, err error) { + var apiUserInfo models.LineApiUser + e.Orm.Model(&models.LineApiUser{}).Where("id = ?", req.ApiId).Find(&apiUserInfo) + var client *helper.BinanceClient + + if apiUserInfo.UserPass == "" { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress) + } else { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress) + } + params := map[string]string{"symbol": req.Symbol, "origClientOrderId": req.OrderSn} + if req.OrderType == 1 { //现货 + var resp dto.QuickAddPreOrderReq + auth, code, err := client.SendSpotAuth("/api/v3/order", "GET", params) + if code != 200 { + return nil, err + } + + if err != nil { + return nil, err + } + sonic.Unmarshal(auth, &resp) + return resp, nil + } + + if req.OrderType == 2 { + var resp dto.FutQueryOrderResp + auth, code, err := client.SendFuturesRequestAuth("/fapi/v1/order", "GET", params) + if code != 200 { + return nil, err + } + if err != nil { + return nil, err + } + sonic.Unmarshal(auth, &resp) + return resp, nil + } + return nil, err +} diff --git a/app/admin/service/line_pre_order_status.go b/app/admin/service/line_pre_order_status.go new file mode 100644 index 0000000..4a36bd8 --- /dev/null +++ b/app/admin/service/line_pre_order_status.go @@ -0,0 +1,109 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type LinePreOrderStatus struct { + service.Service +} + +// GetPage 获取LinePreOrderStatus列表 +func (e *LinePreOrderStatus) GetPage(c *dto.LinePreOrderStatusGetPageReq, p *actions.DataPermission, list *[]models.LinePreOrderStatus, count *int64) error { + var err error + var data models.LinePreOrderStatus + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LinePreOrderStatusService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LinePreOrderStatus对象 +func (e *LinePreOrderStatus) Get(d *dto.LinePreOrderStatusGetReq, p *actions.DataPermission, model *models.LinePreOrderStatus) error { + var data models.LinePreOrderStatus + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLinePreOrderStatus error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LinePreOrderStatus对象 +func (e *LinePreOrderStatus) Insert(c *dto.LinePreOrderStatusInsertReq) error { + var err error + var data models.LinePreOrderStatus + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LinePreOrderStatusService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改LinePreOrderStatus对象 +func (e *LinePreOrderStatus) Update(c *dto.LinePreOrderStatusUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LinePreOrderStatus{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LinePreOrderStatusService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除LinePreOrderStatus +func (e *LinePreOrderStatus) Remove(d *dto.LinePreOrderStatusDeleteReq, p *actions.DataPermission) error { + var data models.LinePreOrderStatus + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLinePreOrderStatus error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} diff --git a/app/admin/service/line_pre_script.go b/app/admin/service/line_pre_script.go new file mode 100644 index 0000000..c4eb0d1 --- /dev/null +++ b/app/admin/service/line_pre_script.go @@ -0,0 +1,109 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type LinePreScript struct { + service.Service +} + +// GetPage 获取LinePreScript列表 +func (e *LinePreScript) GetPage(c *dto.LinePreScriptGetPageReq, p *actions.DataPermission, list *[]models.LinePreScript, count *int64) error { + var err error + var data models.LinePreScript + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LinePreScriptService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LinePreScript对象 +func (e *LinePreScript) Get(d *dto.LinePreScriptGetReq, p *actions.DataPermission, model *models.LinePreScript) error { + var data models.LinePreScript + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLinePreScript error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LinePreScript对象 +func (e *LinePreScript) Insert(c *dto.LinePreScriptInsertReq) error { + var err error + var data models.LinePreScript + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LinePreScriptService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改LinePreScript对象 +func (e *LinePreScript) Update(c *dto.LinePreScriptUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LinePreScript{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LinePreScriptService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除LinePreScript +func (e *LinePreScript) Remove(d *dto.LinePreScriptDeleteReq, p *actions.DataPermission) error { + var data models.LinePreScript + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLinePreScript error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} diff --git a/app/admin/service/line_price_limit.go b/app/admin/service/line_price_limit.go new file mode 100644 index 0000000..30fd41e --- /dev/null +++ b/app/admin/service/line_price_limit.go @@ -0,0 +1,249 @@ +package service + +import ( + "errors" + "fmt" + "go-admin/common/global" + "go-admin/common/helper" + ext "go-admin/config" + models2 "go-admin/models" + "go-admin/pkg/utility" + "sort" + + "github.com/bytedance/sonic" + "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type LinePriceLimit struct { + service.Service +} + +// GetPage 获取LinePriceLimit列表 +func (e *LinePriceLimit) GetPage(c *dto.LinePriceLimitGetPageReq, p *actions.DataPermission, list *[]models.LinePriceLimit, count *int64) error { + var err error + var data models.LinePriceLimit + tx := e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ) + + if c.StartRange != "" { + tx = tx.Where("`range` >= ?", utility.StringToFloat64(c.StartRange)) + } + if c.EndRange != "" { + tx = tx.Where("`range` <= ?", utility.StringToFloat64(c.EndRange)) + } + err = tx.Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LinePriceLimitService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LinePriceLimit对象 +func (e *LinePriceLimit) Get(d *dto.LinePriceLimitGetReq, p *actions.DataPermission, model *models.LinePriceLimit) error { + var data models.LinePriceLimit + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLinePriceLimit error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LinePriceLimit对象 +func (e *LinePriceLimit) Insert(c *dto.LinePriceLimitInsertReq) error { + var err error + var data models.LinePriceLimit + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LinePriceLimitService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改LinePriceLimit对象 +func (e *LinePriceLimit) Update(c *dto.LinePriceLimitUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LinePriceLimit{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LinePriceLimitService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除LinePriceLimit +func (e *LinePriceLimit) Remove(d *dto.LinePriceLimitDeleteReq, p *actions.DataPermission) error { + var data models.LinePriceLimit + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLinePriceLimit error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} + +// UpRange 更新涨跌幅 +func (e *LinePriceLimit) UpRange() error { + var client *helper.BinanceClient + if ext.ExtConfig.ProxyUrl != "" { + client, _ = helper.NewBinanceClient("", "", "", "http://127.0.0.1:7890") + } else { + client, _ = helper.NewBinanceClient("", "", "", "") + } + request, httpCode, err := client.SendSpotRequest("/api/v3/ticker/24hr", "GET", map[string]string{}) + if err != nil { + return errors.New(fmt.Sprintf("获取现货交易对涨跌幅失败 err:%s", err.Error())) + } + + if httpCode != 200 { + return errors.New(fmt.Sprintf("获取现货交易对涨跌幅失败 http code:%d", httpCode)) + } + spotTicker24HrAll := make([]dto.Ticker24Hr, 0) + spotTicker24Hr := make([]dto.Ticker24Hr, 0) + + if err := sonic.Unmarshal(request, &spotTicker24HrAll); err != nil { + logger.Error("反序列化报错", err) + return err + } + + for _, hr := range spotTicker24HrAll { + tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, fmt.Sprintf("%s:%s", global.TICKER_SPOT, hr.Symbol)) + if tradeSet.Currency != "" && tradeSet.Coin != "" { + spotTicker24Hr = append(spotTicker24Hr, hr) + } + } + sort.Slice(spotTicker24Hr, func(i, j int) bool { + return utility.StringToFloat64(spotTicker24Hr[i].PriceChangePercent) > utility.StringToFloat64(spotTicker24Hr[j].PriceChangePercent) + }) + + limits := make([]models.LinePriceLimit, 0) + for _, hr := range spotTicker24Hr { + var status string + if utility.StringToFloat64(hr.PriceChangePercent) > 0 { + status = "1" + } else { + status = "2" + } + limit := models.LinePriceLimit{ + Symbol: hr.Symbol, + Type: "1", + DirectionStatus: status, + Range: utility.StringToDecimal(hr.PriceChangePercent), + } + limits = append(limits, limit) + } + err = e.Orm.Transaction(func(tx *gorm.DB) error { + + err2 := tx.Model(&models.LinePriceLimit{}).Where("type = ?", "1").Unscoped().Delete(&models.LinePriceLimit{}).Error + if err2 != nil { + return errors.New(fmt.Sprintf("删除现货交易对涨跌幅失败 err:%d", err)) + } + + err = tx.Model(&models.LinePriceLimit{}).Create(&limits).Error + if err != nil { + return errors.New(fmt.Sprintf("更新现货交易对涨跌幅失败 err:%d", err)) + } + return nil + }) + if err != nil { + return err + } + + //更新合约涨跌幅 + futuresRequest, code, err := client.SendFuturesRequest("/fapi/v1/ticker/24hr", "GET", map[string]string{}) + if err != nil { + return errors.New(fmt.Sprintf("获取合约交易对涨跌幅失败 err:%s", err.Error())) + } + + if code != 200 { + return errors.New(fmt.Sprintf("获取合约交易对涨跌幅失败 http code:%d", code)) + } + futTicker24HrAll := make([]dto.FutTicker24Hr, 0) + futTicker24Hr := make([]dto.FutTicker24Hr, 0) + sonic.Unmarshal(futuresRequest, &futTicker24HrAll) + for _, hr := range futTicker24HrAll { + tradeSet, _ := helper.GetObjString[models2.TradeSet](helper.DefaultRedis, fmt.Sprintf("%s:%s", global.TICKER_FUTURES, hr.Symbol)) + if tradeSet.Currency != "" && tradeSet.Coin != "" { + futTicker24Hr = append(futTicker24Hr, hr) + } + } + sort.Slice(futTicker24Hr, func(i, j int) bool { + return utility.StringToFloat64(futTicker24Hr[i].PriceChangePercent) > utility.StringToFloat64(futTicker24Hr[j].PriceChangePercent) + }) + + futLimits := make([]models.LinePriceLimit, 0) + for _, hr := range futTicker24Hr { + var status string + if utility.StringToFloat64(hr.PriceChangePercent) > 0 { + status = "1" + } else { + status = "2" + } + limit := models.LinePriceLimit{ + Symbol: hr.Symbol, + Type: "2", + DirectionStatus: status, + Range: utility.StringToDecimal(hr.PriceChangePercent), + } + futLimits = append(futLimits, limit) + } + + err = e.Orm.Transaction(func(tx *gorm.DB) error { + + err2 := tx.Model(&models.LinePriceLimit{}).Where("type = ?", "2").Unscoped().Delete(&models.LinePriceLimit{}).Error + if err2 != nil { + return errors.New(fmt.Sprintf("删除现货交易对涨跌幅失败 err:%d", err)) + } + + err = tx.Model(&models.LinePriceLimit{}).Create(&futLimits).Error + if err != nil { + return errors.New(fmt.Sprintf("更新现货交易对涨跌幅失败 err:%d", err)) + } + return nil + }) + if err != nil { + return err + } + return nil +} diff --git a/app/admin/service/line_recharge.go b/app/admin/service/line_recharge.go new file mode 100644 index 0000000..7fbd301 --- /dev/null +++ b/app/admin/service/line_recharge.go @@ -0,0 +1,109 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type LineRecharge struct { + service.Service +} + +// GetPage 获取LineRecharge列表 +func (e *LineRecharge) GetPage(c *dto.LineRechargeGetPageReq, p *actions.DataPermission, list *[]models.LineRecharge, count *int64) error { + var err error + var data models.LineRecharge + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineRechargeService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LineRecharge对象 +func (e *LineRecharge) Get(d *dto.LineRechargeGetReq, p *actions.DataPermission, model *models.LineRecharge) error { + var data models.LineRecharge + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineRecharge error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineRecharge对象 +func (e *LineRecharge) Insert(c *dto.LineRechargeInsertReq) error { + var err error + var data models.LineRecharge + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineRechargeService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改LineRecharge对象 +func (e *LineRecharge) Update(c *dto.LineRechargeUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineRecharge{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineRechargeService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除LineRecharge +func (e *LineRecharge) Remove(d *dto.LineRechargeDeleteReq, p *actions.DataPermission) error { + var data models.LineRecharge + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineRecharge error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} diff --git a/app/admin/service/line_symbol.go b/app/admin/service/line_symbol.go new file mode 100644 index 0000000..7567182 --- /dev/null +++ b/app/admin/service/line_symbol.go @@ -0,0 +1,463 @@ +package service + +import ( + "errors" + "fmt" + "strings" + + "github.com/bytedance/sonic" + "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/service" + "github.com/shopspring/decimal" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + "go-admin/common/const/rediskey" + cDto "go-admin/common/dto" + "go-admin/common/global" + "go-admin/common/helper" + commonModels "go-admin/models" + "go-admin/pkg/utility" + "go-admin/services/binanceservice" +) + +type LineSymbol struct { + service.Service +} + +// GetPage 获取LineSymbol列表 +func (e *LineSymbol) GetPage(c *dto.LineSymbolGetPageReq, p *actions.DataPermission, list *[]models.LineSymbol, count *int64) error { + var err error + var data models.LineSymbol + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineSymbolService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// GetSamePage 获取LineSymbol列表 +func (e *LineSymbol) GetSamePage(c *dto.LineSymbolGetPageReq, p *actions.DataPermission, list *[]models.LineSymbol, count *int64) error { + var err error + var data models.LineSymbol + tx := e.Orm.Model(&data). + Scopes( + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ) + + scopes := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ) + if c.Symbol != "" { + tx.Where("symbol = ?", c.Symbol) + scopes.Where("symbol = ?", c.Symbol) + } + err = scopes.Select("count(id) as number").Group("symbol").Having("number > ?", 1).Count(count).Error + if err != nil { + e.Log.Errorf("LineSymbolService GetSamePage count:%s \r\n", err) + return err + } + err = tx.Select("count(id) as number,symbol").Group("symbol").Having("number > ?", 1). + Find(list).Limit(-1).Offset(-1). + Error + + if err != nil { + e.Log.Errorf("LineSymbolService GetSamePage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LineSymbol对象 +func (e *LineSymbol) Get(d *dto.LineSymbolGetReq, p *actions.DataPermission, model *models.LineSymbol) error { + var data models.LineSymbol + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineSymbol error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineSymbol对象 +func (e *LineSymbol) Insert(c *dto.LineSymbolInsertReq) error { + var err error + var data models.LineSymbol + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineSymbolService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改LineSymbol对象 +func (e *LineSymbol) Update(c *dto.LineSymbolUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineSymbol{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Omit("api_id").Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineSymbolService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除LineSymbol +func (e *LineSymbol) Remove(d *dto.LineSymbolDeleteReq, p *actions.DataPermission) error { + var data models.LineSymbol + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineSymbol error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} + +func (e *LineSymbol) GetSymbol() (data []models.LineSymbol, err error) { + var ( + spotSymbol, futSymbol []models.LineSymbol + ) + err = e.Orm.Model(&models.LineSymbol{}).Where("type = '1' AND switch = 1").Find(&spotSymbol).Error + if err != nil { + e.Log.Errorf("Service GetSpotSymbol error:%s \r\n", err) + return data, err + } + err = e.Orm.Model(&models.LineSymbol{}).Where("type = '2' AND switch = 1").Find(&futSymbol).Error + if err != nil { + e.Log.Errorf("Service GetFutSymbol error:%s \r\n", err) + return data, err + } + //取两个的交集 + symbols := intersection(spotSymbol, futSymbol) + return symbols, nil +} + +// 泛型交集函数 +func intersection[T comparable](slice1, slice2 []T) []T { + result := []T{} + seen := make(map[T]bool) + + for _, v := range slice1 { + seen[v] = true + } + + for _, v := range slice2 { + if seen[v] { + result = append(result, v) + delete(seen, v) // 防止重复添加 + } + } + + return result +} + +// 重设现货交易对 +func (e *LineSymbol) ResetSpotSymbol() error { + tradeSets, deleteSymbols, err := binanceservice.GetSpotSymbols() + + if err != nil { + logger.Error("获取币安现货交易对失败") + return err + } + symbols := make([]models.LineSymbol, 0) + + sysConfig := SysConfig{Service: service.Service{Orm: e.Orm}} + + var req = new(dto.SysConfigByKeyReq) + var resp = new(dto.GetSysConfigByKEYForServiceResp) + req.ConfigKey = "quote_volume_24hr" + sysConfig.GetWithKey(req, resp) + symbolBlack := make([]models.LineSymbolBlack, 0) + e.Orm.Model(&models.LineSymbolBlack{}).Where("type = '1'").Find(&symbolBlack) + + type Ticker struct { + Symbol string `json:"symbol"` + Price string `json:"price"` + } + tickerSymbol := helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val() + tickerSymbolMaps := make([]Ticker, 0) + sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps) + oldMapSymbols, err := getMapSymbol(e, "1") + if err != nil { + return err + } + + for symbol, tradeSet := range tradeSets { + + key := fmt.Sprintf("%s:%s", global.TICKER_SPOT, symbol) + + //判断是否在黑名单里面 + for _, black := range symbolBlack { + if black.Symbol == symbol { + helper.DefaultRedis.DeleteString(key) + deleteSymbols = append(deleteSymbols, symbol) + continue + } + } + + val := helper.DefaultRedis.Get(key).Val() + var spotTicker24h commonModels.TradeSet + sonic.Unmarshal([]byte(val), &spotTicker24h) + //成交量 + if spotTicker24h.Currency == "USDT" { + if utility.StringToFloat64(spotTicker24h.QuoteVolume) < utility.StringToFloat64(resp.ConfigValue) { + helper.DefaultRedis.DeleteString(key) + deleteSymbols = append(deleteSymbols, symbol) + continue + } + } else { + + var tickerPrice decimal.Decimal + for _, symbolMap := range tickerSymbolMaps { + if symbolMap.Symbol == strings.ToUpper(spotTicker24h.Currency+"USDT") { + tickerPrice, _ = decimal.NewFromString(symbolMap.Price) + } + } + if tickerPrice.GreaterThan(decimal.Zero) { + mul := decimal.NewFromFloat(utility.StringToFloat64(spotTicker24h.QuoteVolume)).Mul(tickerPrice) + if mul.LessThan(decimal.NewFromFloat(utility.StringToFloat64(resp.ConfigValue))) { + helper.DefaultRedis.DeleteString(key) + deleteSymbols = append(deleteSymbols, symbol) + continue + } + } + } + lineSymbol, _ := oldMapSymbols[symbol] + + if lineSymbol.Id <= 0 { + lineSymbol.Symbol = symbol + lineSymbol.BaseAsset = tradeSet.Coin + lineSymbol.QuoteAsset = tradeSet.Currency + lineSymbol.Switch = "1" + lineSymbol.Type = "1" + if lineSymbol.Symbol == "" { + continue + } + symbols = append(symbols, lineSymbol) + } + } + + groups, err := getSymbolGroups(e, "1") + if err != nil { + return err + } + + if len(deleteSymbols) > 0 { + for _, symbol := range deleteSymbols { + for _, group := range groups { + if group.Id > 0 && strings.Contains(group.Symbol, symbol) { + split := strings.Split(group.Symbol, ",") + value := utility.RemoveByValue(split, symbol) + join := strings.Join(value, ",") + e.Orm.Model(&models.LineSymbolGroup{}).Where("id = ?", group.Id).Update("symbol", join) + } + } + } + + batchDeleteBySymbols(deleteSymbols, "1", e) + } + + if len(symbols) > 0 { + err := e.Orm.Model(&models.LineSymbol{}).Omit("api_id").Create(&symbols).Error + if err != nil { + return err + } + } + + return nil +} + +// 批量删除 根据交易对名 +// symbolType 1-现货 2-合约 +func batchDeleteBySymbols(deleteSymbols []string, symbolType string, e *LineSymbol) { + deleteSymbolArray := utility.SplitSlice(deleteSymbols, 100) + + for _, item := range deleteSymbolArray { + e.Orm.Model(&models.LineSymbol{}).Where("type = ? AND symbol IN ? ", symbolType, item).Unscoped().Delete(&models.LineSymbol{}) + } +} + +// 获取交易对分组 +// symbolType 1-现货 2-合约 +func getSymbolGroups(e *LineSymbol, symbolType string) ([]models.LineSymbolGroup, error) { + groups := make([]models.LineSymbolGroup, 0) + + if err := e.Orm.Model(&models.LineSymbolGroup{}).Where("type = ? ", symbolType).Find(&groups).Error; err != nil { + return nil, err + } + return groups, nil +} + +// 获取所有交易对 +// symbolType 1-现货 2-合约 +func getMapSymbol(e *LineSymbol, symbolType string) (map[string]models.LineSymbol, error) { + oldSymbols := make([]models.LineSymbol, 0) + oldMapSymbols := make(map[string]models.LineSymbol) + + if err := e.Orm.Model(&models.LineSymbol{}).Where(" type = ?", symbolType).Find(&oldSymbols).Error; err != nil { + return nil, errors.New("获取现有交易对失败") + } + + for _, item := range oldSymbols { + oldMapSymbols[item.Symbol] = item + } + return oldMapSymbols, nil +} + +// 重设合约交易对 +func (e *LineSymbol) ResetFuturesSymbol() error { + tradeSets := make(map[string]commonModels.TradeSet, 0) + + if err := binanceservice.GetAndReloadSymbols(&tradeSets); err != nil { + logger.Error("定时同步合约交易对失败", err) + } + //获取交易对24小时行情 + deleteSymbols, err := binanceservice.InitSymbolsTicker24h(&tradeSets) + if err != nil { + return err + } + + sysConfig := SysConfig{Service: service.Service{Orm: e.Orm}} + var req = new(dto.SysConfigByKeyReq) + var resp = new(dto.GetSysConfigByKEYForServiceResp) + req.ConfigKey = "quote_volume_24hr" + sysConfig.GetWithKey(req, resp) + symbols := make([]models.LineSymbol, 0) + symbolBlack := make([]models.LineSymbolBlack, 0) + + type Ticker struct { + Symbol string `json:"symbol"` + Price string `json:"price"` + } + tickerSymbol := helper.DefaultRedis.Get(rediskey.FutSymbolTicker).Val() + tickerSymbolMaps := make([]Ticker, 0) + sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps) + oldMapSymbols, err := getMapSymbol(e, "2") + if err != nil { + return err + } + + e.Orm.Model(&models.LineSymbolBlack{}).Where("type = 2").Find(&symbolBlack) + for symbol, tradeSet := range tradeSets { + + key := fmt.Sprintf("%s:%s", global.TICKER_FUTURES, symbol) + + //判断是否在黑名单里面 + for _, black := range symbolBlack { + if black.Symbol == symbol { + helper.DefaultRedis.DeleteString(key) + deleteSymbols = append(deleteSymbols, symbol) + continue + } + } + val := helper.DefaultRedis.Get(key).Val() + var spotTicker24h commonModels.TradeSet + sonic.Unmarshal([]byte(val), &spotTicker24h) + //成交量 + if spotTicker24h.Currency == "USDT" { + if utility.StringToFloat64(spotTicker24h.QuoteVolume) < utility.StringToFloat64(resp.ConfigValue) { + helper.DefaultRedis.DeleteString(key) + deleteSymbols = append(deleteSymbols, symbol) + continue + } + } else { + var tickerPrice decimal.Decimal + for _, symbolMap := range tickerSymbolMaps { + if symbolMap.Symbol == strings.ToUpper(spotTicker24h.Currency+"USDT") { + tickerPrice, _ = decimal.NewFromString(symbolMap.Price) + } + } + if tickerPrice.GreaterThan(decimal.Zero) { + mul := decimal.NewFromFloat(utility.StringToFloat64(spotTicker24h.QuoteVolume)).Mul(tickerPrice) + if mul.LessThan(decimal.NewFromFloat(utility.StringToFloat64(resp.ConfigValue))) { + helper.DefaultRedis.DeleteString(key) + deleteSymbols = append(deleteSymbols, symbol) + continue + } + } + } + lineSymbol, _ := oldMapSymbols[symbol] + + if lineSymbol.Id <= 0 { + lineSymbol.Symbol = symbol + lineSymbol.BaseAsset = tradeSet.Coin + lineSymbol.QuoteAsset = tradeSet.Currency + lineSymbol.Switch = "1" + lineSymbol.Type = "2" + if lineSymbol.Symbol == "" { + continue + } + symbols = append(symbols, lineSymbol) + } + } + + groups, err := getSymbolGroups(e, "2") + if err != nil { + return err + } + + if len(deleteSymbols) > 0 { + for _, symbol := range deleteSymbols { + //如果在交易对组里面 + for _, group := range groups { + if group.Id > 0 { + split := strings.Split(group.Symbol, ",") + value := utility.RemoveByValue(split, symbol) + join := strings.Join(value, ",") + e.Orm.Model(&models.LineSymbolGroup{}).Where("id = ?", group.Id).Update("symbol", join) + } + } + } + + batchDeleteBySymbols(deleteSymbols, "2", e) + } + + if len(symbols) > 0 { + err = e.Orm.Model(&models.LineSymbol{}).Omit("api_id").Create(&symbols).Error + if err != nil { + return err + } + } + + return nil +} diff --git a/app/admin/service/line_symbol_black.go b/app/admin/service/line_symbol_black.go new file mode 100644 index 0000000..4c1e7f9 --- /dev/null +++ b/app/admin/service/line_symbol_black.go @@ -0,0 +1,164 @@ +package service + +import ( + "errors" + "go-admin/pkg/utility" + "strings" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type LineSymbolBlack struct { + service.Service +} + +// GetPage 获取LineSymbolBlack列表 +func (e *LineSymbolBlack) GetPage(c *dto.LineSymbolBlackGetPageReq, p *actions.DataPermission, list *[]models.LineSymbolBlack, count *int64) error { + var err error + var data models.LineSymbolBlack + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineSymbolBlackService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LineSymbolBlack对象 +func (e *LineSymbolBlack) Get(d *dto.LineSymbolBlackGetReq, p *actions.DataPermission, model *models.LineSymbolBlack) error { + var data models.LineSymbolBlack + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineSymbolBlack error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineSymbolBlack对象 +func (e *LineSymbolBlack) Insert(c *dto.LineSymbolBlackInsertReq) error { + var err error + var data models.LineSymbolBlack + c.Generate(&data) + //判断是否已经在用这个交易对 + groups := make([]models.LineSymbolGroup, 0) + sql := "SELECT * FROM line_symbol_group WHERE FIND_IN_SET( ? , symbol) AND type = ? AND deleted_at is NULL;" + e.Orm.Model(&models.LineSymbolGroup{}).Raw(sql, c.Symbol, c.Type).Scan(&groups) + + for _, group := range groups { + if group.Id > 0 { + split := strings.Split(group.Symbol, ",") + value := utility.RemoveByValue(split, c.Symbol) + join := strings.Join(value, ",") + e.Orm.Model(&models.LineSymbolGroup{}).Where("id = ?", group.Id).Update("symbol", join) + } + } + + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineSymbolBlackService Insert error:%s \r\n", err) + return err + } + + return e.reloadSymbol(c.Type) +} + +func (e *LineSymbolBlack) reloadSymbol(symbolType string) error { + symbolService := LineSymbol{Service: e.Service} + + if symbolType == "1" { + if err := symbolService.ResetSpotSymbol(); err != nil { + return errors.New("黑名单添加成功 更新现货交易对失败") + } + } else if symbolType == "2" { + if err := symbolService.ResetFuturesSymbol(); err != nil { + return errors.New("黑名单添加成功 更新合约交易对失败") + } + } + + return nil +} + +// Update 修改LineSymbolBlack对象 +func (e *LineSymbolBlack) Update(c *dto.LineSymbolBlackUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineSymbolBlack{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + //判断是否已经在用这个交易对 + groups := make([]models.LineSymbolGroup, 0) + sql := "SELECT * FROM line_symbol_group WHERE FIND_IN_SET( ? , symbol) AND type = ? AND deleted_at is NULL;" + e.Orm.Model(&models.LineSymbolGroup{}).Raw(sql, c.Symbol, c.Type).Scan(&groups) + + for _, group := range groups { + if group.Id > 0 { + split := strings.Split(group.Symbol, ",") + value := utility.RemoveByValue(split, c.Symbol) + join := strings.Join(value, ",") + e.Orm.Model(&models.LineSymbolGroup{}).Where("id = ?", group.Id).Update("symbol", join) + } + } + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineSymbolBlackService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return e.reloadSymbol(c.Type) +} + +// Remove 删除LineSymbolBlack +func (e *LineSymbolBlack) Remove(d *dto.LineSymbolBlackDeleteReq, p *actions.DataPermission) error { + var data models.LineSymbolBlack + types := make([]string, 0) + + e.Orm.Model(&data).Where("id in ?", d.GetId()).Select("type").Distinct().Find(&types) + + for _, v := range types { + if v != "" { + e.reloadSymbol(v) + } + } + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineSymbolBlack error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} diff --git a/app/admin/service/line_symbol_group.go b/app/admin/service/line_symbol_group.go new file mode 100644 index 0000000..1dc0bb9 --- /dev/null +++ b/app/admin/service/line_symbol_group.go @@ -0,0 +1,116 @@ +package service + +import ( + "errors" + "strings" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type LineSymbolGroup struct { + service.Service +} + +// GetPage 获取LineSymbolGroup列表 +func (e *LineSymbolGroup) GetPage(c *dto.LineSymbolGroupGetPageReq, p *actions.DataPermission, list *[]models.LineSymbolGroup, count *int64) error { + var err error + var data models.LineSymbolGroup + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineSymbolGroupService GetPage error:%s \r\n", err) + return err + } + + for index := range *list { + symbols := strings.Split((*list)[index].Symbol, ",") + + (*list)[index].Count = len(symbols) + } + return nil +} + +// Get 获取LineSymbolGroup对象 +func (e *LineSymbolGroup) Get(d *dto.LineSymbolGroupGetReq, p *actions.DataPermission, model *models.LineSymbolGroup) error { + var data models.LineSymbolGroup + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineSymbolGroup error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineSymbolGroup对象 +func (e *LineSymbolGroup) Insert(c *dto.LineSymbolGroupInsertReq) error { + var err error + var data models.LineSymbolGroup + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineSymbolGroupService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改LineSymbolGroup对象 +func (e *LineSymbolGroup) Update(c *dto.LineSymbolGroupUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineSymbolGroup{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineSymbolGroupService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除LineSymbolGroup +func (e *LineSymbolGroup) Remove(d *dto.LineSymbolGroupDeleteReq, p *actions.DataPermission) error { + var data models.LineSymbolGroup + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineSymbolGroup error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} diff --git a/app/admin/service/line_system_setting.go b/app/admin/service/line_system_setting.go new file mode 100644 index 0000000..3aa9baa --- /dev/null +++ b/app/admin/service/line_system_setting.go @@ -0,0 +1,131 @@ +package service + +import ( + "errors" + + "github.com/bytedance/sonic" + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + "go-admin/common/const/rediskey" + cDto "go-admin/common/dto" + "go-admin/common/helper" +) + +type LineSystemSetting struct { + service.Service +} + +// GetPage 获取LineSystemSetting列表 +func (e *LineSystemSetting) GetPage(c *dto.LineSystemSettingGetPageReq, p *actions.DataPermission, list *[]models.LineSystemSetting, count *int64) error { + var err error + var data models.LineSystemSetting + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineSystemSettingService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LineSystemSetting对象 +func (e *LineSystemSetting) Get(d *dto.LineSystemSettingGetReq, p *actions.DataPermission, model *models.LineSystemSetting) error { + var data models.LineSystemSetting + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineSystemSetting error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineSystemSetting对象 +func (e *LineSystemSetting) Insert(c *dto.LineSystemSettingInsertReq) error { + var err error + var data models.LineSystemSetting + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineSystemSettingService Insert error:%s \r\n", err) + return err + } + + val, _ := sonic.MarshalString(data) + + if val != "" { + if err := helper.DefaultRedis.SetString(rediskey.SystemSetting, val); err != nil { + e.Log.Error("LineSystemSettingService Update redis set error:%s \r\n", err) + } + } + + return nil +} + +// Update 修改LineSystemSetting对象 +func (e *LineSystemSetting) Update(c *dto.LineSystemSettingUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineSystemSetting{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineSystemSettingService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + + val, _ := sonic.MarshalString(data) + + if val != "" { + if err := helper.DefaultRedis.SetString(rediskey.SystemSetting, val); err != nil { + e.Log.Error("LineSystemSettingService Update redis set error:%s \r\n", err) + } + } + + return nil +} + +// Remove 删除LineSystemSetting +func (e *LineSystemSetting) Remove(d *dto.LineSystemSettingDeleteReq, p *actions.DataPermission) error { + var data models.LineSystemSetting + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineSystemSetting error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + + return nil +} diff --git a/app/admin/service/line_uduncoin.go b/app/admin/service/line_uduncoin.go new file mode 100644 index 0000000..5c5e3b6 --- /dev/null +++ b/app/admin/service/line_uduncoin.go @@ -0,0 +1,109 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type LineUduncoin struct { + service.Service +} + +// GetPage 获取LineUduncoin列表 +func (e *LineUduncoin) GetPage(c *dto.LineUduncoinGetPageReq, p *actions.DataPermission, list *[]models.LineUduncoin, count *int64) error { + var err error + var data models.LineUduncoin + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineUduncoinService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LineUduncoin对象 +func (e *LineUduncoin) Get(d *dto.LineUduncoinGetReq, p *actions.DataPermission, model *models.LineUduncoin) error { + var data models.LineUduncoin + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineUduncoin error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineUduncoin对象 +func (e *LineUduncoin) Insert(c *dto.LineUduncoinInsertReq) error { + var err error + var data models.LineUduncoin + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineUduncoinService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改LineUduncoin对象 +func (e *LineUduncoin) Update(c *dto.LineUduncoinUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineUduncoin{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineUduncoinService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除LineUduncoin +func (e *LineUduncoin) Remove(d *dto.LineUduncoinDeleteReq, p *actions.DataPermission) error { + var data models.LineUduncoin + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineUduncoin error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} diff --git a/app/admin/service/line_user.go b/app/admin/service/line_user.go new file mode 100644 index 0000000..363b4bc --- /dev/null +++ b/app/admin/service/line_user.go @@ -0,0 +1,564 @@ +package service + +import ( + "errors" + "fmt" + "go-admin/common/const/rediskey" + "go-admin/common/global" + "go-admin/common/helper" + statuscode "go-admin/common/status_code" + ext "go-admin/config" + "go-admin/models/coingatedto" + "go-admin/pkg/coingate" + "go-admin/pkg/cryptohelper/aeshelper" + "go-admin/pkg/timehelper" + "go-admin/pkg/udunhelper" + "go-admin/pkg/utility" + "go-admin/pkg/utility/seqs" + "go-admin/services/binanceservice" + "strconv" + "strings" + "time" + + "github.com/bytedance/sonic" + "github.com/go-admin-team/go-admin-core/sdk/service" + "github.com/shopspring/decimal" + "go.uber.org/zap" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type LineUser struct { + service.Service +} + +// GetPage 获取LineUser列表 +func (e *LineUser) GetPage(c *dto.LineUserGetPageReq, p *actions.DataPermission, list *[]models.LineUser, count *int64) error { + var err error + var data models.LineUser + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineUserService GetPage error:%s \r\n", err) + return err + } + for i, user := range *list { + var apiUserinfo models.LineApiUser + e.Orm.Model(&models.LineApiUser{}).Where("user_id = ?", user.Id).Find(&apiUserinfo) + (*list)[i].OpenStatus = int(apiUserinfo.OpenStatus) + } + return nil +} + +// Get 获取LineUser对象 +func (e *LineUser) Get(d *dto.LineUserGetReq, p *actions.DataPermission, model *models.LineUser) error { + var data models.LineUser + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineUser error:%s \r\n", err) + return err + } + var apiUserinfo models.LineApiUser + e.Orm.Model(&models.LineApiUser{}).Where("user_id = ?", d.GetId()).Find(&apiUserinfo) + model.OpenStatus = int(apiUserinfo.OpenStatus) + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineUser对象 +func (e *LineUser) Insert(c *dto.LineUserInsertReq) error { + var err error + var data models.LineUser + c.Generate(&data) + data.LoginTime = time.Now() + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineUserService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改LineUser对象 +func (e *LineUser) Update(c *dto.LineUserUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineUser{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + if c.OpenStatus >= 0 { + e.Orm.Model(&models.LineApiUser{}).Where("user_id = ?", c.GetId()).Update("open_status", c.OpenStatus) + } + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineUserService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除LineUser +func (e *LineUser) Remove(d *dto.LineUserDeleteReq, p *actions.DataPermission) error { + var data models.LineUser + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineUser error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} + +// UserVerifyEmail 用户核验邮箱 +func (e *LineUser) UserVerifyEmail(email string) (code int) { + //修改用户的状态 + err := e.Orm.Model(&models.LineUser{}).Where("email = ? AND status = 'verify' ", email).Update("status", "normal").Error + if err != nil { + e.Log.Errorf("Service UserVerifyEmail error:%s \r\n", err) + return statuscode.ServerError + } + return statuscode.OK +} + +// AddApiKey 用户添加apikey +func (e *LineUser) AddApiKey(userId int, req *dto.AddApiKeyReq) int { + var apiUser models.LineApiUser + err2 := e.Orm.Model(&models.LineApiUser{}).Where("user_id = ?", userId).Find(&apiUser).Error + if err2 != nil && !errors.Is(err2, gorm.ErrRecordNotFound) { + e.Log.Errorf("Service AddApiKey error:%s \r\n", err2) + return statuscode.ServerError + } + if apiUser.ApiKey != "" && apiUser.ApiSecret != "" { + return statuscode.UserApiKeyExists + } + + var proxyUrl, proxyType string + if ext.ExtConfig.ProxyUrl != "" { + proxyUrl = "127.0.0.1:7890" + proxyType = "http" + } + ////验证api是否可用 + binanceClient, err := helper.NewBinanceClient(req.ApiKey, req.ApiSecret, proxyType, proxyUrl) + //binanceClient := binanceservice.GetClient(&apiUser) + + params := map[string]int64{ + "recvWindow": 10000, + } + + httpResp, _, err2 := binanceClient.SendSpotAuth("/sapi/v1/account/apiRestrictions", "GET", params) + if err2 != nil { + e.Log.Errorf("Service AddApiKey SendSpotAuth error:", err2) + return statuscode.UserApiKeyInvalid + } + var apiRestrictions dto.ApiRestrictions + sonic.Unmarshal(httpResp, &apiRestrictions) + if !apiRestrictions.EnableSpotAndMarginTrading { + return statuscode.UserApiKeyPermissionError + } + + if !apiRestrictions.EnableFutures { + return statuscode.UserApiKeyPermissionError + } + + err = e.Orm.Model(&models.LineApiUser{}).Create(&models.LineApiUser{ + UserId: int64(userId), + ApiName: req.ApiName, + ApiKey: req.ApiKey, + ApiSecret: req.ApiSecret, + Affiliation: 3, + AdminShow: 0, + Site: "3", + Subordinate: "0", + OpenStatus: 0, + }).Error + + if err != nil { + e.Log.Errorf("Service AddApiKey error:%s \r\n", err) + return statuscode.ServerError + } + var data models.LineApiUser + e.Orm.Model(&models.LineApiUser{}).Where("user_id = ?", userId).Find(&data) + + apiUserService := LineApiUser{Service: e.Service} + apiUserService.restartWebsocket(data) + + return statuscode.OK +} + +// UpdateApiKey 修改apikey +func (e *LineUser) UpdateApiKey(userId int, req *dto.AddApiKeyReq) int { + var apiUser models.LineApiUser + err2 := e.Orm.Model(&models.LineApiUser{}).Where("user_id = ?", userId).Find(&apiUser).Error + if err2 != nil && !errors.Is(err2, gorm.ErrRecordNotFound) { + e.Log.Errorf("Service UpdateApiKey error:%s \r\n", err2) + return statuscode.ServerError + } + if apiUser.ApiKey == "" || apiUser.ApiSecret == "" { + return statuscode.UserApiKeyNotExists + } + + if apiUser.ApiKey == req.ApiKey || apiUser.ApiSecret == req.ApiSecret { + return statuscode.UserApiKeyExists + } + + var proxyUrl, proxyType string + if ext.ExtConfig.ProxyUrl != "" { + proxyUrl = "127.0.0.1:7890" + proxyType = "http" + } + ////验证api是否可用 + client, _ := helper.NewBinanceClient(req.ApiKey, req.ApiSecret, proxyType, proxyUrl) + + //client := binanceservice.GetClient(&apiUser) + params := map[string]int64{ + "recvWindow": 10000, + } + httpResp, _, err2 := client.SendSpotAuth("/sapi/v1/account/apiRestrictions", "GET", params) + if err2 != nil { + e.Log.Errorf("Service AddApiKey SendSpotAuth error:", err2) + return statuscode.UserApiKeyInvalid + } + var apiRestrictions dto.ApiRestrictions + sonic.Unmarshal(httpResp, &apiRestrictions) + if !apiRestrictions.EnableSpotAndMarginTrading { + return statuscode.UserApiKeyPermissionError + } + + if !apiRestrictions.EnableFutures { + return statuscode.UserApiKeyPermissionError + } + + err2 = e.Orm.Model(&models.LineApiUser{}).Where("id = ?", apiUser.Id).Updates(map[string]interface{}{ + "api_key": req.ApiKey, + "api_secret": req.ApiSecret, + "api_name": req.ApiName, + }).Error + if err2 != nil { + e.Log.Errorf("Service UpdateApiKey error:%s \r\n", err2) + return statuscode.ServerError + } + apiUser.ApiKey = req.ApiKey + apiUser.ApiSecret = req.ApiSecret + apiUser.ApiName = req.ApiName + + apiUserService := LineApiUser{Service: e.Service} + apiUserService.restartWebsocket(apiUser) + return statuscode.OK +} + +func (e *LineUser) RechargeNetworkList(req *dto.RechargeListReq) (data []models.VtsCoinToNetWorkResp, code int) { + //获取充值网络列表 + sql := `SELECT coin_id,coin_code,detail_code,network_id,network_name,token_name,transfer_fee FROM line_cointonetwork WHERE is_deposit = 3 AND coin_code = ?` + var list []models.VtsCoinToNetWorkDB + + err := e.Orm.Table("line_cointonetwork").Raw(sql, req.Coin).Find(&list).Error + if err != nil { + e.Log.Errorf("Service RechargeNetworkList error:%s \r\n", err) + return data, statuscode.ServerError + } + coin := req.Coin + resp := make([]models.VtsCoinToNetWorkResp, 0) + var tickerData []binanceservice.Ticker + val := helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val() + if val != "" { + sonic.Unmarshal([]byte(val), &tickerData) + } + + for _, v1 := range list { + if strings.EqualFold(coin, "USDT") { + res := models.VtsCoinToNetWorkResp{ + CoinId: v1.CoinId, + CoinCode: v1.CoinCode, + DetailCode: v1.DetailCode, + NetWorkId: v1.NetworkId, + NetWorkName: v1.NetWorkName, + TokenName: v1.TokenName, + TransferFee: utility.FloatCutStr(v1.TransferFee, 8), + } + res.TransferFeeUsdt = res.TransferFee //utility.FloatCutStr(v1.TransferFee, 8) + resp = append(resp, res) + continue + } + + for _, v2 := range tickerData { + if v1.CoinCode+"USDT" == v2.Symbol { + res := models.VtsCoinToNetWorkResp{ + CoinId: v1.CoinId, + CoinCode: v1.CoinCode, + DetailCode: v1.DetailCode, + NetWorkId: v1.NetworkId, + NetWorkName: v1.NetWorkName, + TokenName: v1.TokenName, + TransferFee: utility.FloatCutStr(v1.TransferFee, 8), + //TransferFeeUsdt string `json:"transfer_fee_usdt"` + } + res.TransferFeeUsdt = utility.FloatCutStr(v1.TransferFee*utility.StringToFloat64(v2.Price), 8) + resp = append(resp, res) + } + } + } + return resp, statuscode.OK +} + +func (e *LineUser) RechargeNetworkAddress(req *dto.RechargeAddressListReq) (data models.RechargeAddressListResp, code int) { + //查询区块确认数 + sql := `SELECT id,network_name,token_name,arrival_num,unlock_num,unlock_time,fee FROM line_coinnetwork where id = ?` + var networkInfo models.VtsCoinNetWorkDB + err := e.Orm.Model(&models.LineCoinnetwork{}).Exec(sql, req.NetWorkId).Find(&networkInfo).Error + if err != nil { + e.Log.Errorf("Service line_coinnetwork sql error:%s \r\n", err) + return data, statuscode.ServerError + } + //查询地址 + addressSql := `SELECT address FROM line_wallet WHERE user_id = ? AND coin_network_id = ?` + //e.Orm.Model(&models.) + var address string + err = e.Orm.Table("line_wallet").Exec(addressSql, req.UserId, req.NetWorkId).Scan(&address).Error + if err != nil { + e.Log.Errorf("Service addressSql error:%s \r\n", err) + return data, statuscode.ServerError + } + if len(address) == 0 { + //创建一个地址并且写入到数据库 + //通过币获取第三方钱包先关信息 + var uduncionInfo models.LineUduncoin + err := e.Orm.Model(&models.LineUduncoin{}).Where("main_symbol = ?", networkInfo.NetworkName).Find(&uduncionInfo).Error + if err != nil { + e.Log.Errorf("Service query uduncionInfo error:%s \r\n", err) + return data, statuscode.ServerError + } + base := udunhelper.BaseRequest{Timestamp: time.Now().Unix(), Nonce: seqs.Rand().RandInt(600000)} + call := udunhelper.CallbackRequest{MainCoinType: utility.StringAsInteger(uduncionInfo.MainCoinType)} + ad := udunhelper.CreateAddress(base, []*udunhelper.CallbackRequest{&call}) + if ad.Code != 200 || ad.Message != "SUCCESS" { + fmt.Println("ad:", ad) + e.Log.Errorf("创建地址出错:%s \r\n", err) + return data, statuscode.ServerError + } + address = ad.Data.Address + //写入数据库 + + err = e.Orm.Model(&models.LineWallet{}).Create(&models.LineWallet{ + UserId: int64(req.UserId), + CoinId: 0, + CoinCode: req.CoinCode, + Address: address, + CoinNetworkId: req.NetWorkId, + }).Error + if err != nil { + e.Log.Errorf("RechargeNetworkAddress AddWalletItem", zap.Error(err)) + } + } + + return models.RechargeAddressListResp{ + Address: address, + MinNum: utility.FloatCutStr(1, 8), + ArrivalNum: networkInfo.ArrivalNum, //充值区块确认数 + UnlockNum: networkInfo.UnlockNum, //提现解锁确认数 + }, statuscode.OK +} + +// FundingTrend 资金走势 +func (e *LineUser) FundingTrend(userId int) (resp []models.LineUserFundingTrendResp, code int) { + var data []models.LineUserFundingTrend + err := e.Orm.Model(&models.LineUserFundingTrend{}).Where("user_id = ?", userId).Order("id desc").Limit(10).Find(&data).Error + if err != nil { + e.Log.Errorf("RechargeNetworkAddress AddWalletItem", zap.Error(err)) + return []models.LineUserFundingTrendResp{}, statuscode.ServerError + } + //resps := make([]models.LineUserFundingTrendResp, len(data)) + for _, datum := range data { + trendResp := models.LineUserFundingTrendResp{ + UserId: datum.UserId, + Funding: datum.Funding, + CreatedAt: timehelper.ConvertTimeToString(datum.CreatedAt), + } + resp = append(resp, trendResp) + } + return resp, statuscode.OK +} + +// PreOrder 预下单 +func (e *LineUser) PreOrder(userId int, req *dto.VtsRechargePreOrderReq, resp *dto.VtsRechargePreOrderResp) error { + sysConfig := SysConfig{Service: e.Service} + configReq := []string{global.SYS_CONFIG_CALLBACK, global.COINGATE_STATUS_CANCELED, global.SYS_CONFIG_SUCCESS} + configResp := make(map[string]dto.GetSysConfigByKEYForServiceResp) + err := sysConfig.GetWithKeys(&configReq, &configResp) + + if err != nil { + e.Log.Error("获取支付配置失败", err) + return err + } + callBack, exits := configResp[global.SYS_CONFIG_CALLBACK] + + if !exits { + e.Log.Errorf("获取支付回调地址不存在") + return errors.New("获取支付回调地址不存在") + } + preOrder := models.VtsRecharge{ + Amount: req.Amount, + AdUserId: userId, + CoinCode: req.CoinCode, + PriceCurrency: req.CoinCode, + OrderNo: helper.GetOrderNo(), + TranType: global.TRAN_TYPE_WALLET, + Status: global.COINGATE_STATUS_DEFAULT, + WalletCreateAt: time.Now(), + CallbackAt: time.Now(), + } + err = e.Orm.Save(&preOrder).Error + + if err != nil { + e.Log.Errorf("保存订单错误:", err) + return err + } + coinGateReq := coingatedto.OrderRequest{} + coinGateReq.OrderID = preOrder.OrderNo + coinGateReq.PriceAmount = preOrder.Amount + coinGateReq.PriceCurrency = preOrder.CoinCode + coinGateReq.ReceiveCurrency = preOrder.CoinCode + coinGateReq.Title = fmt.Sprintf("recharge_%s", strconv.Itoa(userId)) + coinGateReq.CallbackURL = callBack.ConfigValue + token, _ := aeshelper.PswEncrypt(fmt.Sprintf("%s:%s:%s", coinGateReq.OrderID, coinGateReq.PriceAmount, coinGateReq.PriceCurrency)) + coinGateReq.Token = token + + if cancel, exits := configResp[global.SYS_CONFIG_CANCECL]; exits { + coinGateReq.CancelUrl = cancel.ConfigValue + } + + if success, exits := configResp[global.SYS_CONFIG_SUCCESS]; exits { + coinGateReq.SuccessUrl = success.ConfigValue + } + + paymentResp, err := coingate.GetPaymentUrl(ext.ExtConfig.CoinGate.Endpoint, ext.ExtConfig.CoinGate.Auth, &coinGateReq) + + if err != nil { + e.Log.Error("发起支付失败", err) + e.Orm.Model(preOrder).Update("remark", utility.StringCut(err.Error(), 500)) + return err + } + + // 解析格式:RFC3339 + t, err := time.Parse(time.RFC3339, paymentResp.CreatedAt) + + if err != nil { + t = time.Now() + } + + err = e.Orm.Model(preOrder).Updates(&models.VtsRecharge{WalletCreateAt: t, WalletId: paymentResp.ID, Status: paymentResp.Status}).Error + + if err != nil { + e.Log.Error("修改订单状态失败", err) + return err + } + + resp.PaymentUrl = paymentResp.PaymentURL + resp.PriceAmount = paymentResp.PriceAmount + resp.PriceCurrency = paymentResp.PriceCurrency + + return nil +} + +func (e *LineUser) CallBack(req *coingatedto.OrderCallBackResponse) error { + token, err := aeshelper.PswEncrypt(fmt.Sprintf("%s:%s:%s", req.OrderID, utility.StringAsDecimal(req.PriceAmount), req.PriceCurrency)) + + if err != nil { + return err + } + + if token != req.Token { + return errors.New("token 和参数不一致") + } + recharge := models.VtsRecharge{} + err = e.Orm.Model(recharge).Where("order_no=?", req.OrderID).First(&recharge).Error + + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return err + } + + if recharge.Id == 0 { + return errors.New("不存在的充值记录:" + req.OrderID) + } + + switch recharge.Status { + case global.COINGATE_STATUS_PAID: + //退款可以修改 + if req.Status != global.COINGATE_STATUS_PARTIALLY_REFUNDED && req.Status != global.COINGATE_STATUS_REFUNDED { + return errors.New("已成功的订单无法修改:" + req.OrderID) + } + } + + fee := decimal.Zero + + for _, item := range req.Fees { + fee = fee.Add(utility.StrToDecimal(item.Amount)) + } + + err = e.Orm.Transaction(func(tx *gorm.DB) error { + receiveAmount := utility.StrToDecimal(req.ReceiveAmount) + confirmStatus := "" + + if req.Status == global.COINGATE_STATUS_PAID { + + err = tx.Model(&models.LineUser{}).Where("id = ?", recharge.AdUserId).Update("money", gorm.Expr("money + ?", req.PayAmount)).Error + if err != nil { + return err + } + + confirmStatus = global.RECHARGE_CONFIRM_STATUS_UNCONFIRMED + } + + err = tx.Model(recharge).Updates(map[string]interface{}{ + "wallet_id": req.ID, + "receive_amount": receiveAmount, + "pay_amount": utility.StrToDecimal(req.PayAmount), + "pay_currency": req.PayCurrency, + "underpaid_amount": utility.StrToDecimal(req.UnderpaidAmount), + "overpaid_amount": utility.StrToDecimal(req.OverpaidAmount), + "is_refundable": req.IsRefundable, + "fee": fee, + "status": req.Status, + "confirm_status": confirmStatus, + }).Error + + if err != nil { + return err + } + + return nil + }) + + return err +} diff --git a/app/admin/service/line_user_funding_trend.go b/app/admin/service/line_user_funding_trend.go new file mode 100644 index 0000000..fd013ad --- /dev/null +++ b/app/admin/service/line_user_funding_trend.go @@ -0,0 +1,109 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type LineUserFundingTrend struct { + service.Service +} + +// GetPage 获取LineUserFundingTrend列表 +func (e *LineUserFundingTrend) GetPage(c *dto.LineUserFundingTrendGetPageReq, p *actions.DataPermission, list *[]models.LineUserFundingTrend, count *int64) error { + var err error + var data models.LineUserFundingTrend + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineUserFundingTrendService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LineUserFundingTrend对象 +func (e *LineUserFundingTrend) Get(d *dto.LineUserFundingTrendGetReq, p *actions.DataPermission, model *models.LineUserFundingTrend) error { + var data models.LineUserFundingTrend + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineUserFundingTrend error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineUserFundingTrend对象 +func (e *LineUserFundingTrend) Insert(c *dto.LineUserFundingTrendInsertReq) error { + var err error + var data models.LineUserFundingTrend + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineUserFundingTrendService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改LineUserFundingTrend对象 +func (e *LineUserFundingTrend) Update(c *dto.LineUserFundingTrendUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineUserFundingTrend{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineUserFundingTrendService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除LineUserFundingTrend +func (e *LineUserFundingTrend) Remove(d *dto.LineUserFundingTrendDeleteReq, p *actions.DataPermission) error { + var data models.LineUserFundingTrend + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineUserFundingTrend error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} diff --git a/app/admin/service/line_user_profit_logs.go b/app/admin/service/line_user_profit_logs.go new file mode 100644 index 0000000..2326ae6 --- /dev/null +++ b/app/admin/service/line_user_profit_logs.go @@ -0,0 +1,133 @@ +package service + +import ( + "database/sql" + "errors" + "time" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type LineUserProfitLogs struct { + service.Service +} + +// GetPage 获取LineUserProfitLogs列表 +func (e *LineUserProfitLogs) GetPage(c *dto.LineUserProfitLogsGetPageReq, p *actions.DataPermission, list *[]models.LineUserProfitLogs, count *int64) error { + var err error + var data models.LineUserProfitLogs + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineUserProfitLogsService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LineUserProfitLogs对象 +func (e *LineUserProfitLogs) Get(d *dto.LineUserProfitLogsGetReq, p *actions.DataPermission, model *models.LineUserProfitLogs) error { + var data models.LineUserProfitLogs + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineUserProfitLogs error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineUserProfitLogs对象 +func (e *LineUserProfitLogs) Insert(c *dto.LineUserProfitLogsInsertReq) error { + var err error + var data models.LineUserProfitLogs + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineUserProfitLogsService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改LineUserProfitLogs对象 +func (e *LineUserProfitLogs) Update(c *dto.LineUserProfitLogsUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineUserProfitLogs{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineUserProfitLogsService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除LineUserProfitLogs +func (e *LineUserProfitLogs) Remove(d *dto.LineUserProfitLogsDeleteReq, p *actions.DataPermission) error { + var data models.LineUserProfitLogs + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineUserProfitLogs error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} + +// GetProfitInfoByApiId 获取盈利统计 +// apiId api-user 的id +// totalProfit 总盈利 +// todayProfit 今日盈利 +func (e *LineUserProfitLogs) GetProfitInfoByApiId(apiId int) (totalProfit, todayProfit sql.NullFloat64) { + //var totalProfit, todayProfit decimal.Decimal + e.Orm.Model(&models.LineUserProfitLogs{}).Where("api_id = ?", apiId).Select("SUM(amount) as total_profit").Scan(&totalProfit) + e.Orm.Model(&models.LineUserProfitLogs{}).Where("api_id = ? AND created_at >= ?", apiId, time.Now().Truncate(24*time.Hour)).Select("SUM(amount) as today_profit").Scan(&todayProfit) + return +} + +// GetProfitInfoByUserId 获取盈利统计 +// apiId api-user 的id +// totalProfit 总盈利 +// todayProfit 今日盈利 +func (e *LineUserProfitLogs) GetProfitInfoByUserId(userId int) (totalProfit, todayProfit sql.NullFloat64) { + //var totalProfit, todayProfit decimal.Decimal + e.Orm.Model(&models.LineUserProfitLogs{}).Where("user_id = ?", userId).Select("SUM(amount) as total_profit").Scan(&totalProfit) + e.Orm.Model(&models.LineUserProfitLogs{}).Where("user_id = ? AND created_at >= ?", userId, time.Now().Truncate(24*time.Hour)).Select("SUM(amount) as today_profit").Scan(&todayProfit) + return +} diff --git a/app/admin/service/line_wallet.go b/app/admin/service/line_wallet.go new file mode 100644 index 0000000..5def123 --- /dev/null +++ b/app/admin/service/line_wallet.go @@ -0,0 +1,109 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type LineWallet struct { + service.Service +} + +// GetPage 获取LineWallet列表 +func (e *LineWallet) GetPage(c *dto.LineWalletGetPageReq, p *actions.DataPermission, list *[]models.LineWallet, count *int64) error { + var err error + var data models.LineWallet + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("LineWalletService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取LineWallet对象 +func (e *LineWallet) Get(d *dto.LineWalletGetReq, p *actions.DataPermission, model *models.LineWallet) error { + var data models.LineWallet + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetLineWallet error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建LineWallet对象 +func (e *LineWallet) Insert(c *dto.LineWalletInsertReq) error { + var err error + var data models.LineWallet + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("LineWalletService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改LineWallet对象 +func (e *LineWallet) Update(c *dto.LineWalletUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.LineWallet{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("LineWalletService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除LineWallet +func (e *LineWallet) Remove(d *dto.LineWalletDeleteReq, p *actions.DataPermission) error { + var data models.LineWallet + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveLineWallet error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} diff --git a/app/admin/service/sys_api.go b/app/admin/service/sys_api.go new file mode 100644 index 0000000..b098022 --- /dev/null +++ b/app/admin/service/sys_api.go @@ -0,0 +1,121 @@ +package service + +import ( + "errors" + "fmt" + + "github.com/go-admin-team/go-admin-core/sdk/runtime" + "github.com/go-admin-team/go-admin-core/sdk/service" + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" + "go-admin/common/global" +) + +type SysApi struct { + service.Service +} + +// GetPage 获取SysApi列表 +func (e *SysApi) GetPage(c *dto.SysApiGetPageReq, p *actions.DataPermission, list *[]models.SysApi, count *int64) error { + var err error + var data models.SysApi + + orm := e.Orm.Debug().Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ) + if c.Type != "" { + qType := c.Type + if qType == "暂无" { + qType = "" + } + if global.Driver == "postgres" { + orm = orm.Where("type = ?", qType) + } else { + orm = orm.Where("`type` = ?", qType) + } + + } + err = orm.Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("Service GetSysApiPage error:%s", err) + return err + } + return nil +} + +// Get 获取SysApi对象with id +func (e *SysApi) Get(d *dto.SysApiGetReq, p *actions.DataPermission, model *models.SysApi) *SysApi { + var data models.SysApi + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + FirstOrInit(model, d.GetId()).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + return e + } + if model.Id == 0 { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetSysApi error: %s", err) + _ = e.AddError(err) + return e + } + return e +} + +// Update 修改SysApi对象 +func (e *SysApi) Update(c *dto.SysApiUpdateReq, p *actions.DataPermission) error { + var model = models.SysApi{} + db := e.Orm.Debug().First(&model, c.GetId()) + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + c.Generate(&model) + db = e.Orm.Save(&model) + if err := db.Error; err != nil { + e.Log.Errorf("Service UpdateSysApi error:%s", err) + return err + } + + return nil +} + +// Remove 删除SysApi +func (e *SysApi) Remove(d *dto.SysApiDeleteReq, p *actions.DataPermission) error { + var data models.SysApi + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveSysApi error:%s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} + +// CheckStorageSysApi 创建SysApi对象 +func (e *SysApi) CheckStorageSysApi(c *[]runtime.Router) error { + for _, v := range *c { + err := e.Orm.Debug().Where(models.SysApi{Path: v.RelativePath, Action: v.HttpMethod}). + Attrs(models.SysApi{Handle: v.Handler}). + FirstOrCreate(&models.SysApi{}).Error + if err != nil { + err := fmt.Errorf("Service CheckStorageSysApi error: %s \r\n ", err.Error()) + return err + } + } + return nil +} diff --git a/app/admin/service/sys_config.go b/app/admin/service/sys_config.go new file mode 100644 index 0000000..e90490c --- /dev/null +++ b/app/admin/service/sys_config.go @@ -0,0 +1,226 @@ +package service + +import ( + "errors" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + cDto "go-admin/common/dto" + + "github.com/go-admin-team/go-admin-core/sdk/service" +) + +type SysConfig struct { + service.Service +} + +// GetPage 获取SysConfig列表 +func (e *SysConfig) GetPage(c *dto.SysConfigGetPageReq, list *[]models.SysConfig, count *int64) error { + err := e.Orm. + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("Service GetSysConfigPage error:%s", err) + return err + } + return nil +} + +// Get 获取SysConfig对象 +func (e *SysConfig) Get(d *dto.SysConfigGetReq, model *models.SysConfig) error { + err := e.Orm. + FirstOrInit(model, d.GetId()). + Error + if err != nil { + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + return err + } + if model.Id == 0 { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetSysApi error: %s", err) + _ = e.AddError(err) + return err + } + return nil +} + +// Insert 创建SysConfig对象 +func (e *SysConfig) Insert(c *dto.SysConfigControl) error { + var err error + var data models.SysConfig + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("Service InsertSysConfig error:%s", err) + return err + } + return nil +} + +// Update 修改SysConfig对象 +func (e *SysConfig) Update(c *dto.SysConfigControl) error { + var err error + var model = models.SysConfig{} + e.Orm.First(&model, c.GetId()) + c.Generate(&model) + db := e.Orm.Save(&model) + err = db.Error + if err != nil { + e.Log.Errorf("Service UpdateSysConfig error:%s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + + } + return nil +} + +// SetSysConfig 修改SysConfig对象 +func (e *SysConfig) SetSysConfig(c *[]dto.GetSetSysConfigReq) error { + var err error + for _, req := range *c { + var model = models.SysConfig{} + e.Orm.Where("config_key = ?", req.ConfigKey).First(&model) + if model.Id != 0 { + req.Generate(&model) + db := e.Orm.Save(&model) + err = db.Error + if err != nil { + e.Log.Errorf("Service SetSysConfig error:%s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + } + } + return nil +} + +func (e *SysConfig) GetForSet(c *[]dto.GetSetSysConfigReq) error { + var err error + var data models.SysConfig + + err = e.Orm.Model(&data). + Find(c).Error + if err != nil { + e.Log.Errorf("Service GetSysConfigPage error:%s", err) + return err + } + return nil +} + +func (e *SysConfig) UpdateForSet(c *[]dto.GetSetSysConfigReq) error { + m := *c + for _, req := range m { + var data models.SysConfig + if err := e.Orm.Where("config_key = ?", req.ConfigKey). + First(&data).Error; err != nil { + e.Log.Errorf("Service GetSysConfigPage error:%s", err) + return err + } + if data.ConfigValue != req.ConfigValue { + data.ConfigValue = req.ConfigValue + + if err := e.Orm.Save(&data).Error; err != nil { + e.Log.Errorf("Service GetSysConfigPage error:%s", err) + return err + } + } + + } + + return nil +} + +// Remove 删除SysConfig +func (e *SysConfig) Remove(d *dto.SysConfigDeleteReq) error { + var err error + var data models.SysConfig + + db := e.Orm.Delete(&data, d.Ids) + if err = db.Error; err != nil { + e.Log.Errorf("Service RemoveSysConfig error:%s", err) + return err + } + if db.RowsAffected == 0 { + err = errors.New("无权删除该数据") + return err + } + return nil +} + +// GetWithKey 根据Key获取SysConfig +func (e *SysConfig) GetWithKey(c *dto.SysConfigByKeyReq, resp *dto.GetSysConfigByKEYForServiceResp) error { + var err error + var data models.SysConfig + err = e.Orm.Table(data.TableName()).Where("config_key = ?", c.ConfigKey).First(resp).Error + if err != nil { + e.Log.Errorf("At Service GetSysConfigByKEY Error:%s", err) + return err + } + + return nil +} + +func (e *SysConfig) GetWithKeyList(c *dto.SysConfigGetToSysAppReq, list *[]models.SysConfig) error { + err := e.Orm. + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + ). + Find(list).Error + if err != nil { + e.Log.Errorf("Service GetSysConfigByKey error:%s", err) + return err + } + return nil +} + +// 根据keys +func (e *SysConfig) GetWithKeys(keys *[]string, resp *map[string]dto.GetSysConfigByKEYForServiceResp) error { + // noCache := []string{} + + // for _, item := range *keys { + // key := fmt.Sprintf("%s:%s", global.SYS_CONFIG, item) + // val, err := helper.DefaultRedis.GetString(key) + + // if err != nil || len(val) <= 0 { + // noCache = append(noCache, item) + // continue + // } + + // data := dto.GetSysConfigByKEYForServiceResp{} + // err = sonic.Unmarshal([]byte(val), &data) + + // if err != nil { + // noCache = append(noCache, item) + // continue + // } + // *resp = append(*resp, data) + // } + + // if len(noCache) > 0 { + list := make([]models.SysConfig, 0) + err := e.Orm.Model(&models.SysConfig{}).Where("config_key in ?", *keys).Find(&list).Error + if err != nil { + e.Log.Errorf("Service GetSysConfigByKey error:%s", err) + return err + } + + for _, item := range list { + itemResp := dto.GetSysConfigByKEYForServiceResp{ + ConfigKey: item.ConfigKey, + ConfigValue: item.ConfigValue, + } + + (*resp)[item.ConfigKey] = itemResp + } + + return nil +} diff --git a/app/admin/service/sys_dept.go b/app/admin/service/sys_dept.go new file mode 100644 index 0000000..81ed451 --- /dev/null +++ b/app/admin/service/sys_dept.go @@ -0,0 +1,294 @@ +package service + +import ( + "errors" + "go-admin/app/admin/models" + + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + + "go-admin/app/admin/service/dto" + cDto "go-admin/common/dto" + + "github.com/go-admin-team/go-admin-core/sdk/service" +) + +type SysDept struct { + service.Service +} + +// GetPage 获取SysDept列表 +//func (e *SysDept) GetPage(c *dto.SysDeptGetPageReq, list *[]models.SysDept) error { +// var err error +// var data models.SysDept +// +// err = e.Orm.Model(&data). +// Scopes( +// cDto.MakeCondition(c.GetNeedSearch()), +// ). +// Find(list).Error +// if err != nil { +// e.Log.Errorf("db error:%s", err) +// return err +// } +// return nil +//} + +// Get 获取SysDept对象 +func (e *SysDept) Get(d *dto.SysDeptGetReq, model *models.SysDept) error { + var err error + var data models.SysDept + + err = e.Orm.Model(&data). + FirstOrInit(model, d.GetId()). + Error + if err != nil { + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + return err + } + if model.DeptId == 0 { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetSysApi error: %s", err) + _ = e.AddError(err) + return err + } + return nil +} + +// Insert 创建SysDept对象 +func (e *SysDept) Insert(c *dto.SysDeptInsertReq) error { + var err error + var data models.SysDept + c.Generate(&data) + tx := e.Orm.Debug().Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + err = tx.Create(&data).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + deptPath := pkg.IntToString(data.DeptId) + "/" + if data.ParentId != 0 { + var deptP models.SysDept + tx.First(&deptP, data.ParentId) + deptPath = deptP.DeptPath + deptPath + } else { + deptPath = "/0/" + deptPath + } + var mp = map[string]string{} + mp["dept_path"] = deptPath + if err := tx.Model(&data).Update("dept_path", deptPath).Error; err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Update 修改SysDept对象 +func (e *SysDept) Update(c *dto.SysDeptUpdateReq) error { + var err error + var model = models.SysDept{} + tx := e.Orm.Debug().Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + tx.First(&model, c.GetId()) + c.Generate(&model) + + deptPath := pkg.IntToString(model.DeptId) + "/" + if model.ParentId != 0 { + var deptP models.SysDept + tx.First(&deptP, model.ParentId) + deptPath = deptP.DeptPath + deptPath + } else { + deptPath = "/0/" + deptPath + } + model.DeptPath = deptPath + db := tx.Save(&model) + if err = db.Error; err != nil { + e.Log.Errorf("UpdateSysDept error:%s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除SysDept +func (e *SysDept) Remove(d *dto.SysDeptDeleteReq) error { + var err error + var data models.SysDept + + db := e.Orm.Model(&data).Delete(&data, d.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Delete error: %s", err) + return err + } + if db.RowsAffected == 0 { + err = errors.New("无权删除该数据") + return err + } + return nil +} + +// GetSysDeptList 获取组织数据 +func (e *SysDept) getList(c *dto.SysDeptGetPageReq, list *[]models.SysDept) error { + var err error + var data models.SysDept + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + ). + Find(list).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// SetDeptTree 设置组织数据 +func (e *SysDept) SetDeptTree(c *dto.SysDeptGetPageReq) (m []dto.DeptLabel, err error) { + var list []models.SysDept + err = e.getList(c, &list) + + m = make([]dto.DeptLabel, 0) + for i := 0; i < len(list); i++ { + if list[i].ParentId != 0 { + continue + } + e := dto.DeptLabel{} + e.Id = list[i].DeptId + e.Label = list[i].DeptName + deptsInfo := deptTreeCall(&list, e) + + m = append(m, deptsInfo) + } + return +} + +// Call 递归构造组织数据 +func deptTreeCall(deptList *[]models.SysDept, dept dto.DeptLabel) dto.DeptLabel { + list := *deptList + childrenList := make([]dto.DeptLabel, 0) + for j := 0; j < len(list); j++ { + if dept.Id != list[j].ParentId { + continue + } + mi := dto.DeptLabel{Id: list[j].DeptId, Label: list[j].DeptName, Children: []dto.DeptLabel{}} + ms := deptTreeCall(deptList, mi) + childrenList = append(childrenList, ms) + } + dept.Children = childrenList + return dept +} + +// SetDeptPage 设置dept页面数据 +func (e *SysDept) SetDeptPage(c *dto.SysDeptGetPageReq) (m []models.SysDept, err error) { + var list []models.SysDept + err = e.getList(c, &list) + for i := 0; i < len(list); i++ { + if list[i].ParentId != 0 { + continue + } + info := e.deptPageCall(&list, list[i]) + m = append(m, info) + } + return +} + +func (e *SysDept) deptPageCall(deptlist *[]models.SysDept, menu models.SysDept) models.SysDept { + list := *deptlist + childrenList := make([]models.SysDept, 0) + for j := 0; j < len(list); j++ { + if menu.DeptId != list[j].ParentId { + continue + } + mi := models.SysDept{} + mi.DeptId = list[j].DeptId + mi.ParentId = list[j].ParentId + mi.DeptPath = list[j].DeptPath + mi.DeptName = list[j].DeptName + mi.Sort = list[j].Sort + mi.Leader = list[j].Leader + mi.Phone = list[j].Phone + mi.Email = list[j].Email + mi.Status = list[j].Status + mi.CreatedAt = list[j].CreatedAt + mi.Children = []models.SysDept{} + ms := e.deptPageCall(deptlist, mi) + childrenList = append(childrenList, ms) + } + menu.Children = childrenList + return menu +} + +// GetWithRoleId 获取角色的部门ID集合 +func (e *SysDept) GetWithRoleId(roleId int) ([]int, error) { + deptIds := make([]int, 0) + deptList := make([]dto.DeptIdList, 0) + if err := e.Orm.Table("sys_role_dept"). + Select("sys_role_dept.dept_id"). + Joins("LEFT JOIN sys_dept on sys_dept.dept_id=sys_role_dept.dept_id"). + Where("role_id = ? ", roleId). + Where(" sys_role_dept.dept_id not in(select sys_dept.parent_id from sys_role_dept LEFT JOIN sys_dept on sys_dept.dept_id=sys_role_dept.dept_id where role_id =? )", roleId). + Find(&deptList).Error; err != nil { + return nil, err + } + for i := 0; i < len(deptList); i++ { + deptIds = append(deptIds, deptList[i].DeptId) + } + return deptIds, nil +} + +func (e *SysDept) SetDeptLabel() (m []dto.DeptLabel, err error) { + list := make([]models.SysDept, 0) + err = e.Orm.Find(&list).Error + if err != nil { + log.Error("find dept list error, %s", err.Error()) + return + } + m = make([]dto.DeptLabel, 0) + var item dto.DeptLabel + for i := range list { + if list[i].ParentId != 0 { + continue + } + item = dto.DeptLabel{} + item.Id = list[i].DeptId + item.Label = list[i].DeptName + deptInfo := deptLabelCall(&list, item) + m = append(m, deptInfo) + } + return +} + +// deptLabelCall +func deptLabelCall(deptList *[]models.SysDept, dept dto.DeptLabel) dto.DeptLabel { + list := *deptList + var mi dto.DeptLabel + childrenList := make([]dto.DeptLabel, 0) + for j := 0; j < len(list); j++ { + if dept.Id != list[j].ParentId { + continue + } + mi = dto.DeptLabel{Id: list[j].DeptId, Label: list[j].DeptName, Children: []dto.DeptLabel{}} + ms := deptLabelCall(deptList, mi) + childrenList = append(childrenList, ms) + } + dept.Children = childrenList + return dept +} diff --git a/app/admin/service/sys_dict_data.go b/app/admin/service/sys_dict_data.go new file mode 100644 index 0000000..7b2f6dc --- /dev/null +++ b/app/admin/service/sys_dict_data.go @@ -0,0 +1,120 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + cDto "go-admin/common/dto" +) + +type SysDictData struct { + service.Service +} + +// GetPage 获取列表 +func (e *SysDictData) GetPage(c *dto.SysDictDataGetPageReq, list *[]models.SysDictData, count *int64) error { + var err error + var data models.SysDictData + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} + +// Get 获取对象 +func (e *SysDictData) Get(d *dto.SysDictDataGetReq, model *models.SysDictData) error { + var err error + var data models.SysDictData + + db := e.Orm.Model(&data). + First(model, d.GetId()) + err = db.Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("db error: %s", err) + return err + } + if err = db.Error; err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} + +// Insert 创建对象 +func (e *SysDictData) Insert(c *dto.SysDictDataInsertReq) error { + var err error + var data = new(models.SysDictData) + c.Generate(data) + err = e.Orm.Create(data).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} + +// Update 修改对象 +func (e *SysDictData) Update(c *dto.SysDictDataUpdateReq) error { + var err error + var model = models.SysDictData{} + e.Orm.First(&model, c.GetId()) + c.Generate(&model) + db := e.Orm.Save(model) + if err = db.Error; err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + + } + return nil +} + +// Remove 删除 +func (e *SysDictData) Remove(c *dto.SysDictDataDeleteReq) error { + var err error + var data models.SysDictData + + db := e.Orm.Delete(&data, c.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Delete error: %s", err) + return err + } + if db.RowsAffected == 0 { + err = errors.New("无权删除该数据") + return err + } + return nil +} + +// GetAll 获取所有 +func (e *SysDictData) GetAll(c *dto.SysDictDataGetPageReq, list *[]models.SysDictData) error { + var err error + var data models.SysDictData + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + ). + Find(list).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} diff --git a/app/admin/service/sys_dict_type.go b/app/admin/service/sys_dict_type.go new file mode 100644 index 0000000..bc45fe2 --- /dev/null +++ b/app/admin/service/sys_dict_type.go @@ -0,0 +1,124 @@ +package service + +import ( + "errors" + "fmt" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + cDto "go-admin/common/dto" +) + +type SysDictType struct { + service.Service +} + +// GetPage 获取列表 +func (e *SysDictType) GetPage(c *dto.SysDictTypeGetPageReq, list *[]models.SysDictType, count *int64) error { + var err error + var data models.SysDictType + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} + +// Get 获取对象 +func (e *SysDictType) Get(d *dto.SysDictTypeGetReq, model *models.SysDictType) error { + var err error + + db := e.Orm.First(model, d.GetId()) + err = db.Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("db error: %s", err) + return err + } + if err = db.Error; err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} + +// Insert 创建对象 +func (e *SysDictType) Insert(c *dto.SysDictTypeInsertReq) error { + var err error + var data models.SysDictType + c.Generate(&data) + var count int64 + e.Orm.Model(&data).Where("dict_type = ?", data.DictType).Count(&count) + if count > 0 { + return fmt.Errorf("当前字典类型[%s]已经存在!", data.DictType) + } + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} + +// Update 修改对象 +func (e *SysDictType) Update(c *dto.SysDictTypeUpdateReq) error { + var err error + var model = models.SysDictType{} + e.Orm.First(&model, c.GetId()) + c.Generate(&model) + db := e.Orm.Save(&model) + if err = db.Error; err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + + } + return nil +} + +// Remove 删除 +func (e *SysDictType) Remove(d *dto.SysDictTypeDeleteReq) error { + var err error + var data models.SysDictType + + db := e.Orm.Delete(&data, d.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Delete error: %s", err) + return err + } + if db.RowsAffected == 0 { + err = errors.New("无权删除该数据") + return err + } + return nil +} + +// GetAll 获取所有 +func (e *SysDictType) GetAll(c *dto.SysDictTypeGetPageReq, list *[]models.SysDictType) error { + var err error + var data models.SysDictType + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + ). + Find(list).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} diff --git a/app/admin/service/sys_login_log.go b/app/admin/service/sys_login_log.go new file mode 100644 index 0000000..3e9f9b8 --- /dev/null +++ b/app/admin/service/sys_login_log.go @@ -0,0 +1,69 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + cDto "go-admin/common/dto" +) + +type SysLoginLog struct { + service.Service +} + +// GetPage 获取SysLoginLog列表 +func (e *SysLoginLog) GetPage(c *dto.SysLoginLogGetPageReq, list *[]models.SysLoginLog, count *int64) error { + var err error + var data models.SysLoginLog + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Get 获取SysLoginLog对象 +func (e *SysLoginLog) Get(d *dto.SysLoginLogGetReq, model *models.SysLoginLog) error { + var err error + db := e.Orm.First(model, d.GetId()) + err = db.Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("db error:%s", err) + return err + } + if err = db.Error; err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Remove 删除SysLoginLog +func (e *SysLoginLog) Remove(c *dto.SysLoginLogDeleteReq) error { + var err error + var data models.SysLoginLog + + db := e.Orm.Delete(&data, c.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Delete error: %s", err) + return err + } + if db.RowsAffected == 0 { + err = errors.New("无权删除该数据") + return err + } + return nil +} diff --git a/app/admin/service/sys_menu.go b/app/admin/service/sys_menu.go new file mode 100644 index 0000000..7f2b48f --- /dev/null +++ b/app/admin/service/sys_menu.go @@ -0,0 +1,422 @@ +package service + +import ( + "fmt" + "sort" + "strings" + + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/pkg/errors" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + cDto "go-admin/common/dto" + cModels "go-admin/common/models" + + "github.com/go-admin-team/go-admin-core/sdk/service" +) + +type SysMenu struct { + service.Service +} + +// GetPage 获取SysMenu列表 +func (e *SysMenu) GetPage(c *dto.SysMenuGetPageReq, menus *[]models.SysMenu) *SysMenu { + var menu = make([]models.SysMenu, 0) + err := e.getPage(c, &menu).Error + if err != nil { + _ = e.AddError(err) + return e + } + for i := 0; i < len(menu); i++ { + if menu[i].ParentId != 0 { + continue + } + menusInfo := menuCall(&menu, menu[i]) + *menus = append(*menus, menusInfo) + } + return e +} + +// getPage 菜单分页列表 +func (e *SysMenu) getPage(c *dto.SysMenuGetPageReq, list *[]models.SysMenu) *SysMenu { + var err error + var data models.SysMenu + + err = e.Orm.Model(&data). + Scopes( + cDto.OrderDest("sort", false), + cDto.MakeCondition(c.GetNeedSearch()), + ).Preload("SysApi"). + Find(list).Error + if err != nil { + e.Log.Errorf("getSysMenuPage error:%s", err) + _ = e.AddError(err) + return e + } + return e +} + +// Get 获取SysMenu对象 +func (e *SysMenu) Get(d *dto.SysMenuGetReq, model *models.SysMenu) *SysMenu { + var err error + var data models.SysMenu + + db := e.Orm.Model(&data).Preload("SysApi"). + First(model, d.GetId()) + err = db.Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("GetSysMenu error:%s", err) + _ = e.AddError(err) + return e + } + if err != nil { + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + return e + } + apis := make([]int, 0) + for _, v := range model.SysApi { + apis = append(apis, v.Id) + } + model.Apis = apis + return e +} + +// Insert 创建SysMenu对象 +func (e *SysMenu) Insert(c *dto.SysMenuInsertReq) *SysMenu { + var err error + var data models.SysMenu + c.Generate(&data) + tx := e.Orm.Debug().Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + err = tx.Where("id in ?", c.Apis).Find(&data.SysApi).Error + if err != nil { + tx.Rollback() + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + } + err = tx.Create(&data).Error + if err != nil { + tx.Rollback() + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + } + c.MenuId = data.MenuId + err = e.initPaths(tx, &data) + if err != nil { + tx.Rollback() + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + } + tx.Commit() + return e +} + +func (e *SysMenu) initPaths(tx *gorm.DB, menu *models.SysMenu) error { + var err error + var data models.SysMenu + parentMenu := new(models.SysMenu) + if menu.ParentId != 0 { + err = tx.Model(&data).First(parentMenu, menu.ParentId).Error + if err != nil { + return err + } + if parentMenu.Paths == "" { + err = errors.New("父级paths异常,请尝试对当前节点父级菜单进行更新操作!") + return err + } + menu.Paths = parentMenu.Paths + "/" + pkg.IntToString(menu.MenuId) + } else { + menu.Paths = "/0/" + pkg.IntToString(menu.MenuId) + } + err = tx.Model(&data).Where("menu_id = ?", menu.MenuId).Update("paths", menu.Paths).Error + return err +} + +// Update 修改SysMenu对象 +func (e *SysMenu) Update(c *dto.SysMenuUpdateReq) *SysMenu { + var err error + tx := e.Orm.Debug().Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + var alist = make([]models.SysApi, 0) + var model = models.SysMenu{} + tx.Preload("SysApi").First(&model, c.GetId()) + oldPath := model.Paths + tx.Where("id in ?", c.Apis).Find(&alist) + err = tx.Model(&model).Association("SysApi").Delete(model.SysApi) + if err != nil { + e.Log.Errorf("delete policy error:%s", err) + _ = e.AddError(err) + return e + } + c.Generate(&model) + model.SysApi = alist + db := tx.Model(&model).Session(&gorm.Session{FullSaveAssociations: true}).Debug().Save(&model) + if err = db.Error; err != nil { + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + return e + } + if db.RowsAffected == 0 { + _ = e.AddError(errors.New("无权更新该数据")) + return e + } + var menuList []models.SysMenu + tx.Where("paths like ?", oldPath+"%").Find(&menuList) + for _, v := range menuList { + v.Paths = strings.Replace(v.Paths, oldPath, model.Paths, 1) + tx.Model(&v).Update("paths", v.Paths) + } + return e +} + +// Remove 删除SysMenu +func (e *SysMenu) Remove(d *dto.SysMenuDeleteReq) *SysMenu { + var err error + var data models.SysMenu + + db := e.Orm.Model(&data).Delete(&data, d.Ids) + if err = db.Error; err != nil { + e.Log.Errorf("Delete error: %s", err) + _ = e.AddError(err) + } + if db.RowsAffected == 0 { + err = errors.New("无权删除该数据") + _ = e.AddError(err) + } + return e +} + +// GetList 获取菜单数据 +func (e *SysMenu) GetList(c *dto.SysMenuGetPageReq, list *[]models.SysMenu) error { + var err error + var data models.SysMenu + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + ). + Find(list).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// SetLabel 修改角色中 设置菜单基础数据 +func (e *SysMenu) SetLabel() (m []dto.MenuLabel, err error) { + var list []models.SysMenu + err = e.GetList(&dto.SysMenuGetPageReq{}, &list) + + m = make([]dto.MenuLabel, 0) + for i := 0; i < len(list); i++ { + if list[i].ParentId != 0 { + continue + } + e := dto.MenuLabel{} + e.Id = list[i].MenuId + e.Label = list[i].Title + deptsInfo := menuLabelCall(&list, e) + + m = append(m, deptsInfo) + } + return +} + +// GetSysMenuByRoleName 左侧菜单 +func (e *SysMenu) GetSysMenuByRoleName(roleName ...string) ([]models.SysMenu, error) { + var MenuList []models.SysMenu + var role models.SysRole + var err error + admin := false + for _, s := range roleName { + if s == "admin" { + admin = true + } + } + + if len(roleName) > 0 && admin { + var data []models.SysMenu + err = e.Orm.Where(" menu_type in ('M','C')"). + Order("sort"). + Find(&data). + Error + MenuList = data + } else { + err = e.Orm.Model(&role).Preload("SysMenu", func(db *gorm.DB) *gorm.DB { + return db.Where(" menu_type in ('M','C')").Order("sort") + }).Where("role_name in ?", roleName).Find(&role). + Error + MenuList = *role.SysMenu + } + + if err != nil { + e.Log.Errorf("db error:%s", err) + } + return MenuList, err +} + +// menuLabelCall 递归构造组织数据 +func menuLabelCall(eList *[]models.SysMenu, dept dto.MenuLabel) dto.MenuLabel { + list := *eList + + min := make([]dto.MenuLabel, 0) + for j := 0; j < len(list); j++ { + + if dept.Id != list[j].ParentId { + continue + } + mi := dto.MenuLabel{} + mi.Id = list[j].MenuId + mi.Label = list[j].Title + mi.Children = []dto.MenuLabel{} + if list[j].MenuType != "F" { + ms := menuLabelCall(eList, mi) + min = append(min, ms) + } else { + min = append(min, mi) + } + } + if len(min) > 0 { + dept.Children = min + } else { + dept.Children = nil + } + return dept +} + +// menuCall 构建菜单树 +func menuCall(menuList *[]models.SysMenu, menu models.SysMenu) models.SysMenu { + list := *menuList + + min := make([]models.SysMenu, 0) + for j := 0; j < len(list); j++ { + + if menu.MenuId != list[j].ParentId { + continue + } + mi := models.SysMenu{} + mi.MenuId = list[j].MenuId + mi.MenuName = list[j].MenuName + mi.Title = list[j].Title + mi.Icon = list[j].Icon + mi.Path = list[j].Path + mi.MenuType = list[j].MenuType + mi.Action = list[j].Action + mi.Permission = list[j].Permission + mi.ParentId = list[j].ParentId + mi.NoCache = list[j].NoCache + mi.Breadcrumb = list[j].Breadcrumb + mi.Component = list[j].Component + mi.Sort = list[j].Sort + mi.Visible = list[j].Visible + mi.CreatedAt = list[j].CreatedAt + mi.SysApi = list[j].SysApi + mi.Children = []models.SysMenu{} + + if mi.MenuType != cModels.Button { + ms := menuCall(menuList, mi) + min = append(min, ms) + } else { + min = append(min, mi) + } + } + menu.Children = min + return menu +} + +func menuDistinct(menuList []models.SysMenu) (result []models.SysMenu) { + distinctMap := make(map[int]struct{}, len(menuList)) + for _, menu := range menuList { + if _, ok := distinctMap[menu.MenuId]; !ok { + distinctMap[menu.MenuId] = struct{}{} + result = append(result, menu) + } + } + return result +} + +func recursiveSetMenu(orm *gorm.DB, mIds []int, menus *[]models.SysMenu) error { + if len(mIds) == 0 || menus == nil { + return nil + } + var subMenus []models.SysMenu + err := orm.Where(fmt.Sprintf(" menu_type in ('%s', '%s', '%s') and menu_id in ?", + cModels.Directory, cModels.Menu, cModels.Button), mIds).Order("sort").Find(&subMenus).Error + if err != nil { + return err + } + + subIds := make([]int, 0) + for _, menu := range subMenus { + if menu.ParentId != 0 { + subIds = append(subIds, menu.ParentId) + } + if menu.MenuType != cModels.Button { + *menus = append(*menus, menu) + } + } + return recursiveSetMenu(orm, subIds, menus) +} + +// SetMenuRole 获取左侧菜单树使用 +func (e *SysMenu) SetMenuRole(roleName string) (m []models.SysMenu, err error) { + menus, err := e.getByRoleName(roleName) + m = make([]models.SysMenu, 0) + for i := 0; i < len(menus); i++ { + if menus[i].ParentId != 0 { + continue + } + menusInfo := menuCall(&menus, menus[i]) + m = append(m, menusInfo) + } + return +} + +func (e *SysMenu) getByRoleName(roleName string) ([]models.SysMenu, error) { + var role models.SysRole + var err error + data := make([]models.SysMenu, 0) + + if roleName == "admin" { + err = e.Orm.Where(" menu_type in ('M','C') and deleted_at is null"). + Order("sort"). + Find(&data). + Error + err = errors.WithStack(err) + } else { + role.RoleKey = roleName + err = e.Orm.Model(&role).Where("role_key = ? ", roleName).Preload("SysMenu").First(&role).Error + + if role.SysMenu != nil { + mIds := make([]int, 0) + for _, menu := range *role.SysMenu { + mIds = append(mIds, menu.MenuId) + } + if err := recursiveSetMenu(e.Orm, mIds, &data); err != nil { + return nil, err + } + + data = menuDistinct(data) + } + } + + sort.Sort(models.SysMenuSlice(data)) + return data, err +} diff --git a/app/admin/service/sys_opera_log.go b/app/admin/service/sys_opera_log.go new file mode 100644 index 0000000..c94b82c --- /dev/null +++ b/app/admin/service/sys_opera_log.go @@ -0,0 +1,83 @@ +package service + +import ( + "errors" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + cDto "go-admin/common/dto" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" +) + +type SysOperaLog struct { + service.Service +} + +// GetPage 获取SysOperaLog列表 +func (e *SysOperaLog) GetPage(c *dto.SysOperaLogGetPageReq, list *[]models.SysOperaLog, count *int64) error { + var err error + var data models.SysOperaLog + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("Service GetSysOperaLogPage error:%s", err.Error()) + return err + } + return nil +} + +// Get 获取SysOperaLog对象 +func (e *SysOperaLog) Get(d *dto.SysOperaLogGetReq, model *models.SysOperaLog) error { + var data models.SysOperaLog + + err := e.Orm.Model(&data). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetSysOperaLog error:%s", err.Error()) + return err + } + if err != nil { + e.Log.Errorf("Service GetSysOperaLog error:%s", err.Error()) + return err + } + return nil +} + +// Insert 创建SysOperaLog对象 +func (e *SysOperaLog) Insert(model *models.SysOperaLog) error { + var err error + var data models.SysOperaLog + + err = e.Orm.Model(&data). + Create(model).Error + if err != nil { + e.Log.Errorf("Service InsertSysOperaLog error:%s", err.Error()) + return err + } + return nil +} + +// Remove 删除SysOperaLog +func (e *SysOperaLog) Remove(d *dto.SysOperaLogDeleteReq) error { + var err error + var data models.SysOperaLog + + db := e.Orm.Model(&data).Delete(&data, d.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Service RemoveSysOperaLog error:%s", err.Error()) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} diff --git a/app/admin/service/sys_post.go b/app/admin/service/sys_post.go new file mode 100644 index 0000000..04cc0c1 --- /dev/null +++ b/app/admin/service/sys_post.go @@ -0,0 +1,104 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + cDto "go-admin/common/dto" +) + +type SysPost struct { + service.Service +} + +// GetPage 获取SysPost列表 +func (e *SysPost) GetPage(c *dto.SysPostPageReq, list *[]models.SysPost, count *int64) error { + var err error + var data models.SysPost + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("db error:%s \r", err) + return err + } + return nil +} + +// Get 获取SysPost对象 +func (e *SysPost) Get(d *dto.SysPostGetReq, model *models.SysPost) error { + var err error + var data models.SysPost + + db := e.Orm.Model(&data). + First(model, d.GetId()) + err = db.Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("db error:%s", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建SysPost对象 +func (e *SysPost) Insert(c *dto.SysPostInsertReq) error { + var err error + var data models.SysPost + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Update 修改SysPost对象 +func (e *SysPost) Update(c *dto.SysPostUpdateReq) error { + var err error + var model = models.SysPost{} + e.Orm.First(&model, c.GetId()) + c.Generate(&model) + + db := e.Orm.Save(&model) + if err = db.Error; err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + + } + return nil +} + +// Remove 删除SysPost +func (e *SysPost) Remove(d *dto.SysPostDeleteReq) error { + var err error + var data models.SysPost + + db := e.Orm.Model(&data).Delete(&data, d.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Delete error: %s", err) + return err + } + if db.RowsAffected == 0 { + err = errors.New("无权删除该数据") + return err + } + return nil +} diff --git a/app/admin/service/sys_role.go b/app/admin/service/sys_role.go new file mode 100644 index 0000000..f1fee74 --- /dev/null +++ b/app/admin/service/sys_role.go @@ -0,0 +1,352 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/config" + "gorm.io/gorm/clause" + + "github.com/casbin/casbin/v2" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + cDto "go-admin/common/dto" +) + +type SysRole struct { + service.Service +} + +// GetPage 获取SysRole列表 +func (e *SysRole) GetPage(c *dto.SysRoleGetPageReq, list *[]models.SysRole, count *int64) error { + var err error + var data models.SysRole + + err = e.Orm.Model(&data).Preload("SysMenu"). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Get 获取SysRole对象 +func (e *SysRole) Get(d *dto.SysRoleGetReq, model *models.SysRole) error { + var err error + db := e.Orm.First(model, d.GetId()) + err = db.Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("db error:%s", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + model.MenuIds, err = e.GetRoleMenuId(model.RoleId) + if err != nil { + e.Log.Errorf("get menuIds error, %s", err.Error()) + return err + } + return nil +} + +// Insert 创建SysRole对象 +func (e *SysRole) Insert(c *dto.SysRoleInsertReq, cb *casbin.SyncedEnforcer) error { + var err error + var data models.SysRole + var dataMenu []models.SysMenu + err = e.Orm.Preload("SysApi").Where("menu_id in ?", c.MenuIds).Find(&dataMenu).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + c.SysMenu = dataMenu + c.Generate(&data) + tx := e.Orm + if config.DatabaseConfig.Driver != "sqlite3" { + tx := e.Orm.Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + } + var count int64 + err = tx.Model(&data).Where("role_key = ?", c.RoleKey).Count(&count).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + + if count > 0 { + err = errors.New("roleKey已存在,需更换在提交!") + e.Log.Errorf("db error:%s", err) + return err + } + + err = tx.Create(&data).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + + mp := make(map[string]interface{}, 0) + polices := make([][]string, 0) + for _, menu := range dataMenu { + for _, api := range menu.SysApi { + if mp[data.RoleKey+"-"+api.Path+"-"+api.Action] != "" { + mp[data.RoleKey+"-"+api.Path+"-"+api.Action] = "" + polices = append(polices, []string{data.RoleKey, api.Path, api.Action}) + } + } + } + + if len(polices) <= 0 { + return nil + } + + // 写入 sys_casbin_rule 权限表里 当前角色数据的记录 + _, err = cb.AddNamedPolicies("p", polices) + if err != nil { + return err + } + + return nil +} + +// Update 修改SysRole对象 +func (e *SysRole) Update(c *dto.SysRoleUpdateReq, cb *casbin.SyncedEnforcer) error { + var err error + tx := e.Orm + if config.DatabaseConfig.Driver != "sqlite3" { + tx := e.Orm.Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + } + var model = models.SysRole{} + var mlist = make([]models.SysMenu, 0) + tx.Preload("SysMenu").First(&model, c.GetId()) + tx.Preload("SysApi").Where("menu_id in ?", c.MenuIds).Find(&mlist) + err = tx.Model(&model).Association("SysMenu").Delete(model.SysMenu) + if err != nil { + e.Log.Errorf("delete policy error:%s", err) + return err + } + c.Generate(&model) + model.SysMenu = &mlist + // 更新关联的数据,使用 FullSaveAssociations 模式 + db := tx.Session(&gorm.Session{FullSaveAssociations: true}).Debug().Save(&model) + + if err = db.Error; err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + + // 清除 sys_casbin_rule 权限表里 当前角色的所有记录 + _, err = cb.RemoveFilteredPolicy(0, model.RoleKey) + if err != nil { + e.Log.Errorf("delete policy error:%s", err) + return err + } + mp := make(map[string]interface{}, 0) + polices := make([][]string, 0) + for _, menu := range mlist { + for _, api := range menu.SysApi { + if mp[model.RoleKey+"-"+api.Path+"-"+api.Action] != "" { + mp[model.RoleKey+"-"+api.Path+"-"+api.Action] = "" + //_, err = cb.AddNamedPolicy("p", model.RoleKey, api.Path, api.Action) + polices = append(polices, []string{model.RoleKey, api.Path, api.Action}) + } + } + } + if len(polices) <= 0 { + return nil + } + + // 写入 sys_casbin_rule 权限表里 当前角色数据的记录 + _, err = cb.AddNamedPolicies("p", polices) + if err != nil { + return err + } + return nil +} + +// Remove 删除SysRole +func (e *SysRole) Remove(c *dto.SysRoleDeleteReq, cb *casbin.SyncedEnforcer) error { + var err error + tx := e.Orm + if config.DatabaseConfig.Driver != "sqlite3" { + tx := e.Orm.Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + } + var model = models.SysRole{} + tx.Preload("SysMenu").Preload("SysDept").First(&model, c.GetId()) + //删除 SysRole 时,同时删除角色所有 关联其它表 记录 (SysMenu 和 SysMenu) + db := tx.Select(clause.Associations).Delete(&model) + + if err = db.Error; err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + + // 清除 sys_casbin_rule 权限表里 当前角色的所有记录 + _, _ = cb.RemoveFilteredPolicy(0, model.RoleKey) + + return nil +} + +// GetRoleMenuId 获取角色对应的菜单ids +func (e *SysRole) GetRoleMenuId(roleId int) ([]int, error) { + menuIds := make([]int, 0) + model := models.SysRole{} + model.RoleId = roleId + if err := e.Orm.Model(&model).Preload("SysMenu").First(&model).Error; err != nil { + return nil, err + } + l := *model.SysMenu + for i := 0; i < len(l); i++ { + menuIds = append(menuIds, l[i].MenuId) + } + return menuIds, nil +} + +func (e *SysRole) UpdateDataScope(c *dto.RoleDataScopeReq) *SysRole { + var err error + tx := e.Orm + if config.DatabaseConfig.Driver != "sqlite3" { + tx := e.Orm.Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + } + var dlist = make([]models.SysDept, 0) + var model = models.SysRole{} + tx.Preload("SysDept").First(&model, c.RoleId) + tx.Where("dept_id in ?", c.DeptIds).Find(&dlist) + // 删除SysRole 和 SysDept 的关联关系 + err = tx.Model(&model).Association("SysDept").Delete(model.SysDept) + if err != nil { + e.Log.Errorf("delete SysDept error:%s", err) + _ = e.AddError(err) + return e + } + c.Generate(&model) + model.SysDept = dlist + // 更新关联的数据,使用 FullSaveAssociations 模式 + db := tx.Model(&model).Session(&gorm.Session{FullSaveAssociations: true}).Debug().Save(&model) + if err = db.Error; err != nil { + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + return e + } + if db.RowsAffected == 0 { + _ = e.AddError(errors.New("无权更新该数据")) + return e + } + return e +} + +// UpdateStatus 修改SysRole对象status +func (e *SysRole) UpdateStatus(c *dto.UpdateStatusReq) error { + var err error + tx := e.Orm + if config.DatabaseConfig.Driver != "sqlite3" { + tx := e.Orm.Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + } + var model = models.SysRole{} + tx.First(&model, c.GetId()) + c.Generate(&model) + // 更新关联的数据,使用 FullSaveAssociations 模式 + db := tx.Session(&gorm.Session{FullSaveAssociations: true}).Debug().Save(&model) + if err = db.Error; err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// GetWithName 获取SysRole对象 +func (e *SysRole) GetWithName(d *dto.SysRoleByName, model *models.SysRole) *SysRole { + var err error + db := e.Orm.Where("role_name = ?", d.RoleName).First(model) + err = db.Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + return e + } + if err != nil { + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + return e + } + model.MenuIds, err = e.GetRoleMenuId(model.RoleId) + if err != nil { + e.Log.Errorf("get menuIds error, %s", err.Error()) + _ = e.AddError(err) + return e + } + return e +} + +// GetById 获取SysRole对象 +func (e *SysRole) GetById(roleId int) ([]string, error) { + permissions := make([]string, 0) + model := models.SysRole{} + model.RoleId = roleId + if err := e.Orm.Model(&model).Preload("SysMenu").First(&model).Error; err != nil { + return nil, err + } + l := *model.SysMenu + for i := 0; i < len(l); i++ { + if l[i].Permission != "" { + permissions = append(permissions, l[i].Permission) + } + } + return permissions, nil +} diff --git a/app/admin/service/sys_role_menu.go b/app/admin/service/sys_role_menu.go new file mode 100644 index 0000000..9d5d38b --- /dev/null +++ b/app/admin/service/sys_role_menu.go @@ -0,0 +1,110 @@ +package service + +import ( + "github.com/go-admin-team/go-admin-core/sdk/service" +) + +// SysRoleMenu 即将弃用结构体 +type SysRoleMenu struct { + service.Service +} + +//func (e *SysRoleMenu) ReloadRule(tx *gorm.DB, roleId int, menuId []int) (err error) { +// var role models.SysRole +// +// msgID := e.MsgID +// +// menu := make([]models.Menu, 0) +// roleMenu := make([]models.RoleMenu, len(menuId)) +// casbinRule := make([]models.CasbinRule, 0) +// //先删除所有的 +// err = e.DeleteRoleMenu(tx, roleId) +// if err != nil { +// return +// } +// +// // 在事务中做一些数据库操作(从这一点使用'tx',而不是'db') +// err = tx.Where("role_id = ?", roleId).First(&role).Error +// if err != nil { +// log.Errorf("msgID[%s] get role error, %s", msgID, err.Error()) +// return +// } +// err = tx.Where("menu_id in (?)", menuId). +// //Select("path, action, menu_id, menu_type"). +// Find(&menu).Error +// if err != nil { +// log.Errorf("msgID[%s] get menu error, %s", msgID, err.Error()) +// return +// } +// for i := range menu { +// roleMenu[i] = models.RoleMenu{ +// RoleId: role.RoleId, +// MenuId: menu[i].MenuId, +// RoleName: role.RoleKey, +// } +// if menu[i].MenuType == "A" { +// casbinRule = append(casbinRule, models.CasbinRule{ +// PType: "p", +// V0: role.RoleKey, +// V1: menu[i].Path, +// V2: menu[i].Action, +// }) +// } +// } +// err = tx.Create(&roleMenu).Error +// if err != nil { +// log.Errorf("msgID[%s] batch create role's menu error, %s", msgID, err.Error()) +// return +// } +// if len(casbinRule) > 0 { +// err = tx.Create(&casbinRule).Error +// if err != nil { +// log.Errorf("msgID[%s] batch create casbin rule error, %s", msgID, err.Error()) +// return +// } +// } +// +// return +//} + +//func (e *SysRoleMenu) DeleteRoleMenu(tx *gorm.DB, roleId int) (err error) { +// msgID := e.MsgID +// err = tx.Where("role_id = ?", roleId). +// Delete(&models.SysRoleDept{}).Error +// if err != nil { +// log.Errorf("msgID[%s] delete role's dept error, %s", msgID, err.Error()) +// return +// } +// err = tx.Where("role_id = ?", roleId). +// Delete(&models.RoleMenu{}).Error +// if err != nil { +// log.Errorf("msgID[%s] delete role's menu error, %s", msgID, err.Error()) +// return +// } +// var role models.SysRole +// err = tx.Where("role_id = ?", roleId). +// First(&role).Error +// if err != nil { +// log.Errorf("msgID[%s] get role error, %s", msgID, err.Error()) +// return +// } +// err = tx.Where("v0 = ?", role.RoleKey). +// Delete(&models.CasbinRule{}).Error +// if err != nil { +// log.Errorf("msgID[%s] delete casbin rule error, %s", msgID, err.Error()) +// return +// } +// return +//} +// +//func (e *SysRoleMenu) GetIDS(tx *gorm.DB, roleName string) ([]models.MenuPath, error) { +// var r []models.MenuPath +// table := tx.Select("sys_menu.path").Table("sys_role_menu") +// table = table.Joins("left join sys_role on sys_role.role_id=sys_role_menu.role_id") +// table = table.Joins("left join sys_menu on sys_menu.id=sys_role_menu.menu_id") +// table = table.Where("sys_role.role_name = ? and sys_menu.type=1", roleName) +// if err := table.Find(&r).Error; err != nil { +// return nil, err +// } +// return r, nil +//} \ No newline at end of file diff --git a/app/admin/service/sys_user.go b/app/admin/service/sys_user.go new file mode 100644 index 0000000..2a03c03 --- /dev/null +++ b/app/admin/service/sys_user.go @@ -0,0 +1,266 @@ +package service + +import ( + "errors" + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type SysUser struct { + service.Service +} + +// GetPage 获取SysUser列表 +func (e *SysUser) GetPage(c *dto.SysUserGetPageReq, p *actions.DataPermission, list *[]models.SysUser, count *int64) error { + var err error + var data models.SysUser + + err = e.Orm.Debug().Preload("Dept"). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} + +// Get 获取SysUser对象 +func (e *SysUser) Get(d *dto.SysUserById, p *actions.DataPermission, model *models.SysUser) error { + var data models.SysUser + + err := e.Orm.Model(&data).Debug(). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("db error: %s", err) + return err + } + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} + +// Insert 创建SysUser对象 +func (e *SysUser) Insert(c *dto.SysUserInsertReq) error { + var err error + var data models.SysUser + var i int64 + err = e.Orm.Model(&data).Where("username = ?", c.Username).Count(&i).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + if i > 0 { + err := errors.New("用户名已存在!") + e.Log.Errorf("db error: %s", err) + return err + } + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} + +// Update 修改SysUser对象 +func (e *SysUser) Update(c *dto.SysUserUpdateReq, p *actions.DataPermission) error { + var err error + var model models.SysUser + db := e.Orm.Scopes( + actions.Permission(model.TableName(), p), + ).First(&model, c.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Service UpdateSysUser error: %s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + + } + c.Generate(&model) + update := e.Orm.Model(&model).Where("user_id = ?", &model.UserId).Omit("password", "salt").Updates(&model) + if err = update.Error; err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + if update.RowsAffected == 0 { + err = errors.New("update userinfo error") + log.Warnf("db update error") + return err + } + return nil +} + +// UpdateAvatar 更新用户头像 +func (e *SysUser) UpdateAvatar(c *dto.UpdateSysUserAvatarReq, p *actions.DataPermission) error { + var err error + var model models.SysUser + db := e.Orm.Scopes( + actions.Permission(model.TableName(), p), + ).First(&model, c.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Service UpdateSysUser error: %s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + + } + err = e.Orm.Table(model.TableName()).Where("user_id =? ", c.UserId).Updates(c).Error + if err != nil { + e.Log.Errorf("Service UpdateSysUser error: %s", err) + return err + } + return nil +} + +// UpdateStatus 更新用户状态 +func (e *SysUser) UpdateStatus(c *dto.UpdateSysUserStatusReq, p *actions.DataPermission) error { + var err error + var model models.SysUser + db := e.Orm.Scopes( + actions.Permission(model.TableName(), p), + ).First(&model, c.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Service UpdateSysUser error: %s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + + } + err = e.Orm.Table(model.TableName()).Where("user_id =? ", c.UserId).Updates(c).Error + if err != nil { + e.Log.Errorf("Service UpdateSysUser error: %s", err) + return err + } + return nil +} + +// ResetPwd 重置用户密码 +func (e *SysUser) ResetPwd(c *dto.ResetSysUserPwdReq, p *actions.DataPermission) error { + var err error + var model models.SysUser + db := e.Orm.Scopes( + actions.Permission(model.TableName(), p), + ).First(&model, c.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("At Service ResetSysUserPwd error: %s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + c.Generate(&model) + err = e.Orm.Omit("username", "nick_name", "phone", "role_id", "avatar", "sex").Save(&model).Error + if err != nil { + e.Log.Errorf("At Service ResetSysUserPwd error: %s", err) + return err + } + return nil +} + +// Remove 删除SysUser +func (e *SysUser) Remove(c *dto.SysUserById, p *actions.DataPermission) error { + var err error + var data models.SysUser + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, c.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Error found in RemoveSysUser : %s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} + +// UpdatePwd 修改SysUser对象密码 +func (e *SysUser) UpdatePwd(id int, oldPassword, newPassword string, p *actions.DataPermission) error { + var err error + + if newPassword == "" { + return nil + } + c := &models.SysUser{} + + err = e.Orm.Model(c). + Scopes( + actions.Permission(c.TableName(), p), + ).Select("UserId", "Password", "Salt"). + First(c, id).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("无权更新该数据") + } + e.Log.Errorf("db error: %s", err) + return err + } + var ok bool + ok, err = pkg.CompareHashAndPassword(c.Password, oldPassword) + if err != nil { + e.Log.Errorf("CompareHashAndPassword error, %s", err.Error()) + return err + } + if !ok { + err = errors.New("incorrect Password") + e.Log.Warnf("user[%d] %s", id, err.Error()) + return err + } + c.Password = newPassword + db := e.Orm.Model(c).Where("user_id = ?", id). + Select("Password", "Salt"). + Updates(c) + if err = db.Error; err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + if db.RowsAffected == 0 { + err = errors.New("set password error") + log.Warnf("db update error") + return err + } + return nil +} + +func (e *SysUser) GetProfile(c *dto.SysUserById, user *models.SysUser, roles *[]models.SysRole, posts *[]models.SysPost) error { + err := e.Orm.Preload("Dept").First(user, c.GetId()).Error + if err != nil { + return err + } + err = e.Orm.Find(roles, user.RoleId).Error + if err != nil { + return err + } + err = e.Orm.Find(posts, user.PostIds).Error + if err != nil { + return err + } + + return nil +} diff --git a/app/admin/service/sysdb/sys_status_code.go b/app/admin/service/sysdb/sys_status_code.go new file mode 100644 index 0000000..fdca061 --- /dev/null +++ b/app/admin/service/sysdb/sys_status_code.go @@ -0,0 +1,18 @@ +package sysdb + +import ( + "go-admin/app/admin/models/sysmodel" + "log" + + "gorm.io/gorm" +) + +func GetAllStatusCode(orm *gorm.DB) []sysmodel.StatusCode { + // sql := `SELECT code, explain, zh_cn, zh_hk, en FROM sys_status_code` + var statusCodeList []sysmodel.StatusCode + if err := orm.Table("sys_status_code").Find(&statusCodeList).Error; err != nil { + log.Println("InitStatusCodeLanguage GetAllStatusCode Error:", err) + return statusCodeList + } + return statusCodeList +} diff --git a/app/admin/service/vts_recharge.go b/app/admin/service/vts_recharge.go new file mode 100644 index 0000000..c21ee44 --- /dev/null +++ b/app/admin/service/vts_recharge.go @@ -0,0 +1,109 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type VtsRecharge struct { + service.Service +} + +// GetPage 获取VtsRecharge列表 +func (e *VtsRecharge) GetPage(c *dto.VtsRechargeGetPageReq, p *actions.DataPermission, list *[]models.VtsRecharge, count *int64) error { + var err error + var data models.VtsRecharge + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("VtsRechargeService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取VtsRecharge对象 +func (e *VtsRecharge) Get(d *dto.VtsRechargeGetReq, p *actions.DataPermission, model *models.VtsRecharge) error { + var data models.VtsRecharge + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetVtsRecharge error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建VtsRecharge对象 +func (e *VtsRecharge) Insert(c *dto.VtsRechargeInsertReq) error { + var err error + var data models.VtsRecharge + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("VtsRechargeService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改VtsRecharge对象 +func (e *VtsRecharge) Update(c *dto.VtsRechargeUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.VtsRecharge{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("VtsRechargeService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除VtsRecharge +func (e *VtsRecharge) Remove(d *dto.VtsRechargeDeleteReq, p *actions.DataPermission) error { + var data models.VtsRecharge + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveVtsRecharge error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} diff --git a/app/jobs/apis/sys_job.go b/app/jobs/apis/sys_job.go new file mode 100644 index 0000000..296138d --- /dev/null +++ b/app/jobs/apis/sys_job.go @@ -0,0 +1,69 @@ +package apis + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/sdk/api" + + "go-admin/app/jobs/service" + "go-admin/common/dto" +) + +type SysJob struct { + api.Api +} + +// RemoveJobForService 调用service实现 +func (e SysJob) RemoveJobForService(c *gin.Context) { + v := dto.GeneralDelDto{} + s := service.SysJob{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&v). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + return + } + + s.Cron = sdk.Runtime.GetCrontabKey(c.Request.Host) + err = s.RemoveJob(&v) + if err != nil { + e.Logger.Errorf("RemoveJob error, %s", err.Error()) + e.Error(500, err, "") + return + } + e.OK(nil, s.Msg) +} + +// StartJobForService 启动job service实现 +func (e SysJob) StartJobForService(c *gin.Context) { + e.MakeContext(c) + log := e.GetLogger() + db, err := e.GetOrm() + if err != nil { + log.Error(err) + return + } + var v dto.GeneralGetDto + err = c.BindUri(&v) + if err != nil { + log.Warnf("参数验证错误, error: %s", err) + e.Error(http.StatusUnprocessableEntity, err, "参数验证失败") + return + } + s := service.SysJob{} + s.Orm = db + s.Log = log + s.Cron = sdk.Runtime.GetCrontabKey(c.Request.Host) + err = s.StartJob(&v) + if err != nil { + log.Errorf("GetCrontabKey error, %s", err.Error()) + e.Error(500, err, err.Error()) + return + } + e.OK(nil, s.Msg) +} diff --git a/app/jobs/examples.go b/app/jobs/examples.go new file mode 100644 index 0000000..73c03dc --- /dev/null +++ b/app/jobs/examples.go @@ -0,0 +1,122 @@ +package jobs + +import ( + "errors" + "fmt" + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/const/rediskey" + "go-admin/common/helper" + "time" + + "github.com/bytedance/sonic" + "github.com/go-admin-team/go-admin-core/sdk" + sysservice "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" +) + +// InitJob +// 需要将定义的struct 添加到字典中; +// 字典 key 可以配置到 自动任务 调用目标 中; +func InitJob() { + jobList = map[string]JobExec{ + "ExamplesOne": ExamplesOne{}, + // ... + "DeleteExpireOrder": DeleteExpireOrder{}, //定时删除过期数据 + "UpRange": UpRange{}, //更新涨跌幅 + "InitFuturesSymbol": InitFuturesSymbol{}, //定时更新合约交易对 + "InitSpotSymbol": InitSpotSymbol{}, //定时更新现货交易对 + "ClearLogJob": ClearLogJob{}, //定时清理日志 + "AutoPlaceOrder": AutoPlaceOrder{}, //定时下单 + } +} + +// ExamplesOne +// 新添加的job 必须按照以下格式定义,并实现Exec函数 +type ExamplesOne struct { +} + +func (t ExamplesOne) Exec(arg interface{}) error { + str := time.Now().Format(timeFormat) + " [INFO] JobCore ExamplesOne exec success" + // TODO: 这里需要注意 Examples 传入参数是 string 所以 arg.(string);请根据对应的类型进行转化; + switch arg.(type) { + + case string: + if arg.(string) != "" { + fmt.Println("string", arg.(string)) + fmt.Println(str, arg.(string)) + } else { + fmt.Println("arg is nil") + fmt.Println(str, "arg is nil") + } + break + } + + return nil +} + +// DeleteExpireOrder 清理过期订单 +type DeleteExpireOrder struct { +} + +func (receiver DeleteExpireOrder) Exec(arg interface{}) error { + dbs := sdk.Runtime.GetDb() + var db *gorm.DB + + for _, item := range dbs { + db = item + break + } + orders := make([]models.LinePreOrder, 0) + err := db.Model(&models.LinePreOrder{}).Where("status = '0' AND expire_time <= ? AND (order_type = 1 or order_type = 2)", time.Now()).Find(&orders).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return err + } + for _, order := range orders { + //删除止盈止损数据 + db.Model(&models.LinePreOrder{}).Where("pid = ?", order.Id).Unscoped().Delete(&models.LinePreOrder{}) + //删除缓存 + list := dto.PreOrderRedisList{ + Id: order.Id, + Symbol: order.Symbol, + Price: order.Price, + Site: order.Site, + ApiId: order.ApiId, + OrderSn: order.OrderSn, + QuoteSymbol: order.QuoteSymbol, + } + marshal, _ := sonic.Marshal(&list) + if order.SymbolType == 1 { + helper.DefaultRedis.LRem(fmt.Sprintf(rediskey.PreSpotOrderList, order.ExchangeType), string(marshal)) + } else { + helper.DefaultRedis.LRem(fmt.Sprintf(rediskey.PreFutOrderList, order.ExchangeType), string(marshal)) + } + //删除主单 + db.Model(&models.LinePreOrder{}).Where("id = ?", order.Id).Unscoped().Delete(&models.LinePreOrder{}) + } + return nil +} + +// UpRange 更新涨跌幅 +type UpRange struct { +} + +func (receiver UpRange) Exec(arg interface{}) error { + dbs := sdk.Runtime.GetDb() + var db *gorm.DB + + for _, item := range dbs { + db = item + break + } + + limit := service.LinePriceLimit{ + Service: sysservice.Service{Orm: db}, + } + err := limit.UpRange() + if err != nil { + return err + } + return nil +} diff --git a/app/jobs/jobbase.go b/app/jobs/jobbase.go new file mode 100644 index 0000000..ea6fe3d --- /dev/null +++ b/app/jobs/jobbase.go @@ -0,0 +1,203 @@ +package jobs + +import ( + "fmt" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk" + models2 "go-admin/app/jobs/models" + "gorm.io/gorm" + "time" + + "github.com/robfig/cron/v3" + + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/cronjob" +) + +var timeFormat = "2006-01-02 15:04:05" +var retryCount = 3 + +var jobList map[string]JobExec + +//var lock sync.Mutex + +type JobCore struct { + InvokeTarget string + Name string + JobId int + EntryId int + CronExpression string + Args string +} + +// HttpJob 任务类型 http +type HttpJob struct { + JobCore +} + +type ExecJob struct { + JobCore +} + +func (e *ExecJob) Run() { + startTime := time.Now() + var obj = jobList[e.InvokeTarget] + if obj == nil { + log.Warn("[Job] ExecJob Run job nil") + return + } + err := CallExec(obj.(JobExec), e.Args) + if err != nil { + // 如果失败暂停一段时间重试 + fmt.Println(time.Now().Format(timeFormat), " [ERROR] mission failed! ", err) + } + // 结束时间 + endTime := time.Now() + + // 执行时间 + latencyTime := endTime.Sub(startTime) + //TODO: 待完善部分 + //str := time.Now().Format(timeFormat) + " [INFO] JobCore " + string(e.EntryId) + "exec success , spend :" + latencyTime.String() + //ws.SendAll(str) + log.Infof("[Job] JobCore %s exec success , spend :%v", e.Name, latencyTime) + return +} + +// Run http 任务接口 +func (h *HttpJob) Run() { + + startTime := time.Now() + var count = 0 + var err error + var str string + /* 循环 */ +LOOP: + if count < retryCount { + /* 跳过迭代 */ + str, err = pkg.Get(h.InvokeTarget) + if err != nil { + // 如果失败暂停一段时间重试 + fmt.Println(time.Now().Format(timeFormat), " [ERROR] mission failed! ", err) + fmt.Printf(time.Now().Format(timeFormat)+" [INFO] Retry after the task fails %d seconds! %s \n", (count+1)*5, str) + time.Sleep(time.Duration(count+1) * 5 * time.Second) + count = count + 1 + goto LOOP + } + } + // 结束时间 + endTime := time.Now() + + // 执行时间 + latencyTime := endTime.Sub(startTime) + //TODO: 待完善部分 + + log.Infof("[Job] JobCore %s exec success , spend :%v", h.Name, latencyTime) + return +} + +// Setup 初始化 +func Setup(dbs map[string]*gorm.DB) { + + fmt.Println(time.Now().Format(timeFormat), " [INFO] JobCore Starting...") + + for k, db := range dbs { + sdk.Runtime.SetCrontab(k, cronjob.NewWithSeconds()) + setup(k, db) + } +} + +func setup(key string, db *gorm.DB) { + crontab := sdk.Runtime.GetCrontabKey(key) + sysJob := models2.SysJob{} + jobList := make([]models2.SysJob, 0) + err := sysJob.GetList(db, &jobList) + if err != nil { + fmt.Println(time.Now().Format(timeFormat), " [ERROR] JobCore init error", err) + } + if len(jobList) == 0 { + fmt.Println(time.Now().Format(timeFormat), " [INFO] JobCore total:0") + } + + _, err = sysJob.RemoveAllEntryID(db) + if err != nil { + fmt.Println(time.Now().Format(timeFormat), " [ERROR] JobCore remove entry_id error", err) + } + + for i := 0; i < len(jobList); i++ { + if jobList[i].JobType == 1 { + j := &HttpJob{} + j.InvokeTarget = jobList[i].InvokeTarget + j.CronExpression = jobList[i].CronExpression + j.JobId = jobList[i].JobId + j.Name = jobList[i].JobName + + sysJob.EntryId, err = AddJob(crontab, j) + } else if jobList[i].JobType == 2 { + j := &ExecJob{} + j.InvokeTarget = jobList[i].InvokeTarget + j.CronExpression = jobList[i].CronExpression + j.JobId = jobList[i].JobId + j.Name = jobList[i].JobName + j.Args = jobList[i].Args + sysJob.EntryId, err = AddJob(crontab, j) + } + err = sysJob.Update(db, jobList[i].JobId) + } + + // 其中任务 + crontab.Start() + fmt.Println(time.Now().Format(timeFormat), " [INFO] JobCore start success.") + // 关闭任务 + defer crontab.Stop() + select {} +} + +// AddJob 添加任务 AddJob(invokeTarget string, jobId int, jobName string, cronExpression string) +func AddJob(c *cron.Cron, job Job) (int, error) { + if job == nil { + fmt.Println("unknown") + return 0, nil + } + return job.addJob(c) +} + +func (h *HttpJob) addJob(c *cron.Cron) (int, error) { + id, err := c.AddJob(h.CronExpression, h) + if err != nil { + fmt.Println(time.Now().Format(timeFormat), " [ERROR] JobCore AddJob error", err) + return 0, err + } + EntryId := int(id) + return EntryId, nil +} + +func (e *ExecJob) addJob(c *cron.Cron) (int, error) { + id, err := c.AddJob(e.CronExpression, e) + if err != nil { + fmt.Println(time.Now().Format(timeFormat), " [ERROR] JobCore AddJob error", err) + return 0, err + } + EntryId := int(id) + return EntryId, nil +} + +// Remove 移除任务 +func Remove(c *cron.Cron, entryID int) chan bool { + ch := make(chan bool) + go func() { + c.Remove(cron.EntryID(entryID)) + fmt.Println(time.Now().Format(timeFormat), " [INFO] JobCore Remove success ,info entryID :", entryID) + ch <- true + }() + return ch +} + +// 任务停止 +//func Stop() chan bool { +// ch := make(chan bool) +// go func() { +// global.GADMCron.Stop() +// ch <- true +// }() +// return ch +//} diff --git a/app/jobs/jobs.go b/app/jobs/jobs.go new file mode 100644 index 0000000..06603b2 --- /dev/null +++ b/app/jobs/jobs.go @@ -0,0 +1,125 @@ +package jobs + +import ( + "errors" + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/services/fileservice" + "strconv" + "strings" + "time" + + "github.com/go-admin-team/go-admin-core/sdk" + "gorm.io/gorm" + + "github.com/go-admin-team/go-admin-core/logger" +) + +type InitFuturesSymbol struct { +} + +type InitSpotSymbol struct { +} + +type ClearLogJob struct { +} + +type AutoPlaceOrder struct { +} + +// 初始化合约交易对 +func (t InitFuturesSymbol) Exec(arg interface{}) error { + str := time.Now().Format(timeFormat) + " [INFO] JobCore InitFuturesSymbol exec success" + defer logger.Info(str) + dbs := sdk.Runtime.GetDb() + var db *gorm.DB + + for _, item := range dbs { + db = item + break + } + + symbolService := service.LineSymbol{} + symbolService.Orm = db + + //重置合约交易对 + symbolService.ResetFuturesSymbol() + + return nil +} + +// 初始化现货交易对 +func (t InitSpotSymbol) Exec(arg interface{}) error { + str := time.Now().Format(timeFormat) + " [INFO] JobCore InitSpotSymbol exec success" + defer logger.Info(str) + + dbs := sdk.Runtime.GetDb() + var db *gorm.DB + + for _, item := range dbs { + db = item + break + } + + symbolService := service.LineSymbol{} + symbolService.Orm = db + + //重置现货交易对 + symbolService.ResetSpotSymbol() + + return nil +} + +// 删除过期日志 +func (t ClearLogJob) Exec(arg interface{}) error { + str := time.Now().Format(timeFormat) + " [INFO] JobCore ClearLogJob exec success" + defer logger.Info(str) + var db *gorm.DB + + for _, item := range sdk.Runtime.GetDb() { + db = item + break + } + + fileservice.ClearLogs(db) + + return nil +} + +// Exec 执行自动下单 +func (t AutoPlaceOrder) Exec(arg interface{}) error { + str := time.Now().Format(timeFormat) + " [INFO] JobCore ClearLogJob exec success" + defer logger.Info(str) + var db *gorm.DB + + for _, item := range sdk.Runtime.GetDb() { + db = item + break + } + var templateLogs []models.LineOrderTemplateLogs + db.Model(&models.LineOrderTemplateLogs{}).Where("switch = '1'").Find(&templateLogs) + if len(templateLogs) > 0 { + ids := make([]string, len(templateLogs)) + for _, log := range templateLogs { + ids = append(ids, strconv.Itoa(log.Id)) + } + req := dto.QuickAddPreOrderReq{Ids: strings.Join(ids, ",")} + preOrderService := service.LinePreOrder{} + preOrderService.Orm = db + errs := make([]error, 0) + errStr := make([]string, 0) + err := preOrderService.QuickAddPreOrder(&req, nil, &errs) + if err != nil { + return err + } + if len(errs) > 0 { + //e.Logger.Error(err) + for _, err2 := range errs { + errStr = append(errStr, err2.Error()) + } + return errors.New(strings.Join(errStr, ",")) + } + } + return nil +} diff --git a/app/jobs/models/sys_job.go b/app/jobs/models/sys_job.go new file mode 100644 index 0000000..a8cc6bf --- /dev/null +++ b/app/jobs/models/sys_job.go @@ -0,0 +1,61 @@ +package models + +import ( + "go-admin/common/models" + "gorm.io/gorm" +) + +type SysJob struct { + JobId int `json:"jobId" gorm:"primaryKey;autoIncrement"` // 编码 + JobName string `json:"jobName" gorm:"size:255;"` // 名称 + JobGroup string `json:"jobGroup" gorm:"size:255;"` // 任务分组 + JobType int `json:"jobType" gorm:"size:1;"` // 任务类型 + CronExpression string `json:"cronExpression" gorm:"size:255;"` // cron表达式 + InvokeTarget string `json:"invokeTarget" gorm:"size:255;"` // 调用目标 + Args string `json:"args" gorm:"size:255;"` // 目标参数 + MisfirePolicy int `json:"misfirePolicy" gorm:"size:255;"` // 执行策略 + Concurrent int `json:"concurrent" gorm:"size:1;"` // 是否并发 + Status int `json:"status" gorm:"size:1;"` // 状态 + EntryId int `json:"entry_id" gorm:"size:11;"` // job启动时返回的id + models.ControlBy + models.ModelTime + + DataScope string `json:"dataScope" gorm:"-"` +} + +func (*SysJob) TableName() string { + return "sys_job" +} + +func (e *SysJob) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysJob) GetId() interface{} { + return e.JobId +} + +func (e *SysJob) SetCreateBy(createBy int) { + e.CreateBy = createBy +} + +func (e *SysJob) SetUpdateBy(updateBy int) { + e.UpdateBy = updateBy +} + +func (e *SysJob) GetList(tx *gorm.DB, list interface{}) (err error) { + return tx.Table(e.TableName()).Where("status = ?", 2).Find(list).Error +} + +// Update 更新SysJob +func (e *SysJob) Update(tx *gorm.DB, id interface{}) (err error) { + return tx.Table(e.TableName()).Where(id).Updates(&e).Error +} + +func (e *SysJob) RemoveAllEntryID(tx *gorm.DB) (update SysJob, err error) { + if err = tx.Table(e.TableName()).Where("entry_id > ?", 0).Update("entry_id", 0).Error; err != nil { + return + } + return +} diff --git a/app/jobs/router/int_router.go b/app/jobs/router/int_router.go new file mode 100644 index 0000000..ab65faa --- /dev/null +++ b/app/jobs/router/int_router.go @@ -0,0 +1,36 @@ +package router + +import ( + //"github.com/go-admin-team/go-admin-core/sdk/pkg" + "os" + + "github.com/gin-gonic/gin" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk" + common "go-admin/common/middleware" +) + +// InitRouter 路由初始化,不要怀疑,这里用到了 +func InitRouter() { + var r *gin.Engine + h := sdk.Runtime.GetEngine() + if h == nil { + log.Fatal("not found engine...") + os.Exit(-1) + } + switch h.(type) { + case *gin.Engine: + r = h.(*gin.Engine) + default: + log.Fatal("not support other engine") + os.Exit(-1) + } + + authMiddleware, err := common.AuthInit() + if err != nil { + log.Fatalf("JWT Init Error, %s", err.Error()) + } + + // 注册业务路由 + initRouter(r, authMiddleware) +} diff --git a/app/jobs/router/router.go b/app/jobs/router/router.go new file mode 100644 index 0000000..8c893a2 --- /dev/null +++ b/app/jobs/router/router.go @@ -0,0 +1,42 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" +) + +var ( + routerNoCheckRole = make([]func(*gin.RouterGroup), 0) + routerCheckRole = make([]func(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware), 0) +) + +// initRouter 路由示例 +func initRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gin.Engine { + + // 无需认证的路由 + noCheckRoleRouter(r) + // 需要认证的路由 + checkRoleRouter(r, authMiddleware) + + return r +} + +// noCheckRoleRouter 无需认证的路由示例 +func noCheckRoleRouter(r *gin.Engine) { + // 可根据业务需求来设置接口版本 + v1 := r.Group("/api/v1") + + for _, f := range routerNoCheckRole { + f(v1) + } +} + +// checkRoleRouter 需要认证的路由示例 +func checkRoleRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) { + // 可根据业务需求来设置接口版本 + v1 := r.Group("/api/v1") + + for _, f := range routerCheckRole { + f(v1, authMiddleware) + } +} diff --git a/app/jobs/router/sys_job.go b/app/jobs/router/sys_job.go new file mode 100644 index 0000000..89723d4 --- /dev/null +++ b/app/jobs/router/sys_job.go @@ -0,0 +1,38 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "go-admin/app/jobs/apis" + models2 "go-admin/app/jobs/models" + dto2 "go-admin/app/jobs/service/dto" + "go-admin/common/actions" + "go-admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSysJobRouter) +} + +// 需认证的路由代码 +func registerSysJobRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + + r := v1.Group("/sysjob").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + sysJob := &models2.SysJob{} + r.GET("", actions.PermissionAction(), actions.IndexAction(sysJob, new(dto2.SysJobSearch), func() interface{} { + list := make([]models2.SysJob, 0) + return &list + })) + r.GET("/:id", actions.PermissionAction(), actions.ViewAction(new(dto2.SysJobById), func() interface{} { + return &dto2.SysJobItem{} + })) + r.POST("", actions.CreateAction(new(dto2.SysJobControl))) + r.PUT("", actions.PermissionAction(), actions.UpdateAction(new(dto2.SysJobControl))) + r.DELETE("", actions.PermissionAction(), actions.DeleteAction(new(dto2.SysJobById))) + } + sysJob := apis.SysJob{} + + v1.GET("/job/remove/:id", sysJob.RemoveJobForService) + v1.GET("/job/start/:id", sysJob.StartJobForService) +} diff --git a/app/jobs/service/dto/sys_job.go b/app/jobs/service/dto/sys_job.go new file mode 100644 index 0000000..c66bcbd --- /dev/null +++ b/app/jobs/service/dto/sys_job.go @@ -0,0 +1,108 @@ +package dto + +import ( + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "go-admin/app/jobs/models" + + "go-admin/common/dto" + common "go-admin/common/models" +) + +type SysJobSearch struct { + dto.Pagination `search:"-"` + JobId int `form:"jobId" search:"type:exact;column:job_id;table:sys_job"` + JobName string `form:"jobName" search:"type:icontains;column:job_name;table:sys_job"` + JobGroup string `form:"jobGroup" search:"type:exact;column:job_group;table:sys_job"` + CronExpression string `form:"cronExpression" search:"type:exact;column:cron_expression;table:sys_job"` + InvokeTarget string `form:"invokeTarget" search:"type:exact;column:invoke_target;table:sys_job"` + Status int `form:"status" search:"type:exact;column:status;table:sys_job"` +} + +func (m *SysJobSearch) GetNeedSearch() interface{} { + return *m +} + +func (m *SysJobSearch) Bind(ctx *gin.Context) error { + log := api.GetRequestLogger(ctx) + err := ctx.ShouldBind(m) + if err != nil { + log.Errorf("Bind error: %s", err) + } + return err +} + +func (m *SysJobSearch) Generate() dto.Index { + o := *m + return &o +} + +type SysJobControl struct { + JobId int `json:"jobId"` + JobName string `json:"jobName" validate:"required"` // 名称 + JobGroup string `json:"jobGroup"` // 任务分组 + JobType int `json:"jobType"` // 任务类型 + CronExpression string `json:"cronExpression"` // cron表达式 + InvokeTarget string `json:"invokeTarget"` // 调用目标 + Args string `json:"args"` // 目标参数 + MisfirePolicy int `json:"misfirePolicy"` // 执行策略 + Concurrent int `json:"concurrent"` // 是否并发 + Status int `json:"status"` // 状态 + EntryId int `json:"entryId"` // job启动时返回的id +} + +func (s *SysJobControl) Bind(ctx *gin.Context) error { + return ctx.ShouldBind(s) +} + +func (s *SysJobControl) Generate() dto.Control { + cp := *s + return &cp +} + +func (s *SysJobControl) GenerateM() (common.ActiveRecord, error) { + return &models.SysJob{ + JobId: s.JobId, + JobName: s.JobName, + JobGroup: s.JobGroup, + JobType: s.JobType, + CronExpression: s.CronExpression, + InvokeTarget: s.InvokeTarget, + Args: s.Args, + MisfirePolicy: s.MisfirePolicy, + Concurrent: s.Concurrent, + Status: s.Status, + EntryId: s.EntryId, + }, nil +} + +func (s *SysJobControl) GetId() interface{} { + return s.JobId +} + +type SysJobById struct { + dto.ObjectById +} + +func (s *SysJobById) Generate() dto.Control { + cp := *s + return &cp +} + +func (s *SysJobById) GenerateM() (common.ActiveRecord, error) { + return &models.SysJob{}, nil +} + +type SysJobItem struct { + JobId int `json:"jobId"` + JobName string `json:"jobName" validate:"required"` // 名称 + JobGroup string `json:"jobGroup"` // 任务分组 + JobType int `json:"jobType"` // 任务类型 + CronExpression string `json:"cronExpression"` // cron表达式 + InvokeTarget string `json:"invokeTarget"` // 调用目标 + Args string `json:"args"` // 目标参数 + MisfirePolicy int `json:"misfirePolicy"` // 执行策略 + Concurrent int `json:"concurrent"` // 是否并发 + Status int `json:"status"` // 状态 + EntryId int `json:"entryId"` // job启动时返回的id +} diff --git a/app/jobs/service/sys_job.go b/app/jobs/service/sys_job.go new file mode 100644 index 0000000..a1ab810 --- /dev/null +++ b/app/jobs/service/sys_job.go @@ -0,0 +1,93 @@ +package service + +import ( + "errors" + "time" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "github.com/robfig/cron/v3" + + "go-admin/app/jobs" + "go-admin/app/jobs/models" + "go-admin/common/dto" +) + +type SysJob struct { + service.Service + Cron *cron.Cron +} + +// RemoveJob 删除job +func (e *SysJob) RemoveJob(c *dto.GeneralDelDto) error { + var err error + var data models.SysJob + err = e.Orm.Table(data.TableName()).First(&data, c.Id).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + cn := jobs.Remove(e.Cron, data.EntryId) + + select { + case res := <-cn: + if res { + err = e.Orm.Table(data.TableName()).Where("entry_id = ?", data.EntryId).Update("entry_id", 0).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + } + return err + } + case <-time.After(time.Second * 1): + e.Msg = "操作超时!" + return nil + } + return nil +} + +// StartJob 启动任务 +func (e *SysJob) StartJob(c *dto.GeneralGetDto) error { + var data models.SysJob + var err error + err = e.Orm.Table(data.TableName()).First(&data, c.Id).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + + if data.Status == 1 { + err = errors.New("当前Job是关闭状态不能被启动,请先启用。") + return err + } + + if data.JobType == 1 { + var j = &jobs.HttpJob{} + j.InvokeTarget = data.InvokeTarget + j.CronExpression = data.CronExpression + j.JobId = data.JobId + j.Name = data.JobName + data.EntryId, err = jobs.AddJob(e.Cron, j) + if err != nil { + e.Log.Errorf("jobs AddJob[HttpJob] error: %s", err) + } + } else { + var j = &jobs.ExecJob{} + j.InvokeTarget = data.InvokeTarget + j.CronExpression = data.CronExpression + j.JobId = data.JobId + j.Name = data.JobName + j.Args = data.Args + data.EntryId, err = jobs.AddJob(e.Cron, j) + if err != nil { + e.Log.Errorf("jobs AddJob[ExecJob] error: %s", err) + } + } + if err != nil { + return err + } + + err = e.Orm.Table(data.TableName()).Where(c.Id).Updates(&data).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + } + return err +} diff --git a/app/jobs/type.go b/app/jobs/type.go new file mode 100644 index 0000000..1b5c30b --- /dev/null +++ b/app/jobs/type.go @@ -0,0 +1,16 @@ +package jobs + +import "github.com/robfig/cron/v3" + +type Job interface { + Run() + addJob(*cron.Cron) (int, error) +} + +type JobExec interface { + Exec(arg interface{}) error +} + +func CallExec(e JobExec, arg interface{}) error { + return e.Exec(arg) +} diff --git a/app/other/apis/file.go b/app/other/apis/file.go new file mode 100644 index 0000000..e0d77f4 --- /dev/null +++ b/app/other/apis/file.go @@ -0,0 +1,204 @@ +package apis + +import ( + "encoding/base64" + "errors" + "fmt" + "io/ioutil" + "strings" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/utils" + "github.com/google/uuid" + + "go-admin/common/file_store" +) + +type FileResponse struct { + Size int64 `json:"size"` + Path string `json:"path"` + FullPath string `json:"full_path"` + Name string `json:"name"` + Type string `json:"type"` +} + +const path = "static/uploadfile/" + +type File struct { + api.Api +} + +// UploadFile 上传图片 +// @Summary 上传图片 +// @Description 获取JSON +// @Tags 公共接口 +// @Accept multipart/form-data +// @Param type query string true "type" (1:单图,2:多图, 3:base64图片) +// @Param file formData file true "file" +// @Success 200 {string} string "{"code": 200, "message": "添加成功"}" +// @Success 200 {string} string "{"code": -1, "message": "添加失败"}" +// @Router /api/v1/public/uploadFile [post] +// @Security Bearer +func (e File) UploadFile(c *gin.Context) { + e.MakeContext(c) + tag, _ := c.GetPostForm("type") + urlPrefix := fmt.Sprintf("%s://%s/", "http", c.Request.Host) + var fileResponse FileResponse + + switch tag { + case "1": // 单图 + var done bool + fileResponse, done = e.singleFile(c, fileResponse, urlPrefix) + if done { + return + } + e.OK(fileResponse, "上传成功") + return + case "2": // 多图 + multipartFile := e.multipleFile(c, urlPrefix) + e.OK(multipartFile, "上传成功") + return + case "3": // base64 + fileResponse = e.baseImg(c, fileResponse, urlPrefix) + e.OK(fileResponse, "上传成功") + default: + var done bool + fileResponse, done = e.singleFile(c, fileResponse, urlPrefix) + if done { + return + } + e.OK(fileResponse, "上传成功") + return + } + +} + +func (e File) baseImg(c *gin.Context, fileResponse FileResponse, urlPerfix string) FileResponse { + files, _ := c.GetPostForm("file") + file2list := strings.Split(files, ",") + ddd, _ := base64.StdEncoding.DecodeString(file2list[1]) + guid := uuid.New().String() + fileName := guid + ".jpg" + err := utils.IsNotExistMkDir(path) + if err != nil { + e.Error(500, errors.New(""), "初始化文件路径失败") + } + base64File := path + fileName + _ = ioutil.WriteFile(base64File, ddd, 0666) + typeStr := strings.Replace(strings.Replace(file2list[0], "data:", "", -1), ";base64", "", -1) + fileResponse = FileResponse{ + Size: pkg.GetFileSize(base64File), + Path: base64File, + FullPath: urlPerfix + base64File, + Name: "", + Type: typeStr, + } + source, _ := c.GetPostForm("source") + err = thirdUpload(source, fileName, base64File) + if err != nil { + e.Error(200, errors.New(""), "上传第三方失败") + return fileResponse + } + if source != "1" { + fileResponse.Path = "/static/uploadfile/" + fileName + fileResponse.FullPath = "/static/uploadfile/" + fileName + } + return fileResponse +} + +func (e File) multipleFile(c *gin.Context, urlPerfix string) []FileResponse { + files := c.Request.MultipartForm.File["file"] + source, _ := c.GetPostForm("source") + var multipartFile []FileResponse + for _, f := range files { + guid := uuid.New().String() + fileName := guid + utils.GetExt(f.Filename) + + err := utils.IsNotExistMkDir(path) + if err != nil { + e.Error(500, errors.New(""), "初始化文件路径失败") + } + multipartFileName := path + fileName + err1 := c.SaveUploadedFile(f, multipartFileName) + fileType, _ := utils.GetType(multipartFileName) + if err1 == nil { + err := thirdUpload(source, fileName, multipartFileName) + if err != nil { + e.Error(500, errors.New(""), "上传第三方失败") + } else { + fileResponse := FileResponse{ + Size: pkg.GetFileSize(multipartFileName), + Path: multipartFileName, + FullPath: urlPerfix + multipartFileName, + Name: f.Filename, + Type: fileType, + } + if source != "1" { + fileResponse.Path = "/static/uploadfile/" + fileName + fileResponse.FullPath = "/static/uploadfile/" + fileName + } + multipartFile = append(multipartFile, fileResponse) + } + } + } + return multipartFile +} + +func (e File) singleFile(c *gin.Context, fileResponse FileResponse, urlPerfix string) (FileResponse, bool) { + files, err := c.FormFile("file") + + if err != nil { + e.Error(200, errors.New(""), "图片不能为空") + return FileResponse{}, true + } + // 上传文件至指定目录 + guid := uuid.New().String() + + fileName := guid + utils.GetExt(files.Filename) + + err = utils.IsNotExistMkDir(path) + if err != nil { + e.Error(500, errors.New(""), "初始化文件路径失败") + } + singleFile := path + fileName + _ = c.SaveUploadedFile(files, singleFile) + fileType, _ := utils.GetType(singleFile) + fileResponse = FileResponse{ + Size: pkg.GetFileSize(singleFile), + Path: singleFile, + FullPath: urlPerfix + singleFile, + Name: files.Filename, + Type: fileType, + } + //source, _ := c.GetPostForm("source") + //err = thirdUpload(source, fileName, singleFile) + //if err != nil { + // e.Error(200, errors.New(""), "上传第三方失败") + // return FileResponse{}, true + //} + fileResponse.Path = "/static/uploadfile/" + fileName + fileResponse.FullPath = "/static/uploadfile/" + fileName + return fileResponse, false +} + +func thirdUpload(source string, name string, path string) error { + switch source { + case "2": + return ossUpload("img/"+name, path) + case "3": + return qiniuUpload("img/"+name, path) + } + return nil +} + +func ossUpload(name string, path string) error { + oss := file_store.ALiYunOSS{} + return oss.UpLoad(name, path) +} + +func qiniuUpload(name string, path string) error { + oss := file_store.ALiYunOSS{} + return oss.UpLoad(name, path) +} diff --git a/app/other/apis/sys_server_monitor.go b/app/other/apis/sys_server_monitor.go new file mode 100644 index 0000000..c9aee1d --- /dev/null +++ b/app/other/apis/sys_server_monitor.go @@ -0,0 +1,186 @@ +package apis + +import ( + "fmt" + "github.com/shirou/gopsutil/v3/net" + "runtime" + "strconv" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "github.com/shirou/gopsutil/v3/cpu" + "github.com/shirou/gopsutil/v3/disk" + "github.com/shirou/gopsutil/v3/host" + "github.com/shirou/gopsutil/v3/mem" +) + +const ( + B = 1 + KB = 1024 * B + MB = 1024 * KB + GB = 1024 * MB +) + +var ( + //Version string + //expectDiskFsTypes = []string{ + // "apfs", "ext4", "ext3", "ext2", "f2fs", "reiserfs", "jfs", "btrfs", + // "fuseblk", "zfs", "simfs", "ntfs", "fat32", "exfat", "xfs", "fuse.rclone", + //} + excludeNetInterfaces = []string{ + "lo", "tun", "docker", "veth", "br-", "vmbr", "vnet", "kube", + } + //getMacDiskNo = regexp.MustCompile(`\/dev\/disk(\d)s.*`) +) + +var ( + netInSpeed, netOutSpeed, netInTransfer, netOutTransfer, lastUpdateNetStats uint64 + cachedBootTime time.Time +) + +type ServerMonitor struct { + api.Api +} + +// GetHourDiffer 获取相差时间 +func GetHourDiffer(startTime, endTime string) int64 { + var hour int64 + t1, err := time.ParseInLocation("2006-01-02 15:04:05", startTime, time.Local) + t2, err := time.ParseInLocation("2006-01-02 15:04:05", endTime, time.Local) + if err == nil && t1.Before(t2) { + diff := t2.Unix() - t1.Unix() // + hour = diff / 3600 + return hour + } else { + return hour + } +} + +// ServerInfo 获取系统信息 +// @Summary 系统信息 +// @Description 获取JSON +// @Tags 系统信息 +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/server-monitor [get] +// @Security Bearer +func (e ServerMonitor) ServerInfo(c *gin.Context) { + e.Context = c + + sysInfo, err := host.Info() + osDic := make(map[string]interface{}, 0) + osDic["goOs"] = runtime.GOOS + osDic["arch"] = runtime.GOARCH + osDic["mem"] = runtime.MemProfileRate + osDic["compiler"] = runtime.Compiler + osDic["version"] = runtime.Version() + osDic["numGoroutine"] = runtime.NumGoroutine() + osDic["ip"] = pkg.GetLocaHonst() + osDic["projectDir"] = pkg.GetCurrentPath() + osDic["hostName"] = sysInfo.Hostname + osDic["time"] = time.Now().Format("2006-01-02 15:04:05") + + memory, _ := mem.VirtualMemory() + memDic := make(map[string]interface{}, 0) + memDic["used"] = memory.Used / MB + memDic["total"] = memory.Total / MB + + fmt.Println("mem", int(memory.Total/memory.Used*100)) + memDic["percent"] = pkg.Round(memory.UsedPercent, 2) + + swapDic := make(map[string]interface{}, 0) + swapDic["used"] = memory.SwapTotal - memory.SwapFree + swapDic["total"] = memory.SwapTotal + + cpuDic := make(map[string]interface{}, 0) + cpuDic["cpuInfo"], _ = cpu.Info() + percent, _ := cpu.Percent(0, false) + cpuDic["percent"] = pkg.Round(percent[0], 2) + cpuDic["cpuNum"], _ = cpu.Counts(false) + + //服务器磁盘信息 + disklist := make([]disk.UsageStat, 0) + //所有分区 + var diskTotal, diskUsed, diskUsedPercent float64 + diskInfo, err := disk.Partitions(true) + if err == nil { + for _, p := range diskInfo { + diskDetail, err := disk.Usage(p.Mountpoint) + if err == nil { + diskDetail.UsedPercent, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", diskDetail.UsedPercent), 64) + diskDetail.Total = diskDetail.Total / 1024 / 1024 + diskDetail.Used = diskDetail.Used / 1024 / 1024 + diskDetail.Free = diskDetail.Free / 1024 / 1024 + disklist = append(disklist, *diskDetail) + + } + } + } + + d, _ := disk.Usage("/") + + diskTotal = float64(d.Total / GB) + diskUsed = float64(d.Used / GB) + diskUsedPercent, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", d.UsedPercent), 64) + + diskDic := make(map[string]interface{}, 0) + diskDic["total"] = diskTotal + diskDic["used"] = diskUsed + diskDic["percent"] = diskUsedPercent + + bootTime, _ := host.BootTime() + cachedBootTime = time.Unix(int64(bootTime), 0) + + TrackNetworkSpeed() + netDic := make(map[string]interface{}, 0) + netDic["in"] = pkg.Round(float64(netInSpeed/KB), 2) + netDic["out"] = pkg.Round(float64(netOutSpeed/KB), 2) + e.Custom(gin.H{ + "code": 200, + "os": osDic, + "mem": memDic, + "cpu": cpuDic, + "disk": diskDic, + "net": netDic, + "swap": swapDic, + "location": "Aliyun", + "bootTime": GetHourDiffer(cachedBootTime.Format("2006-01-02 15:04:05"), time.Now().Format("2006-01-02 15:04:05")), + }) +} + +func TrackNetworkSpeed() { + var innerNetInTransfer, innerNetOutTransfer uint64 + nc, err := net.IOCounters(true) + if err == nil { + for _, v := range nc { + if isListContainsStr(excludeNetInterfaces, v.Name) { + continue + } + innerNetInTransfer += v.BytesRecv + innerNetOutTransfer += v.BytesSent + } + now := uint64(time.Now().Unix()) + diff := now - lastUpdateNetStats + if diff > 0 { + netInSpeed = (innerNetInTransfer - netInTransfer) / diff + fmt.Println("netInSpeed", netInSpeed) + netOutSpeed = (innerNetOutTransfer - netOutTransfer) / diff + fmt.Println("netOutSpeed", netOutSpeed) + } + netInTransfer = innerNetInTransfer + netOutTransfer = innerNetOutTransfer + lastUpdateNetStats = now + } +} + +func isListContainsStr(list []string, str string) bool { + for i := 0; i < len(list); i++ { + if strings.Contains(str, list[i]) { + return true + } + } + return false +} diff --git a/app/other/apis/tools/db_columns.go b/app/other/apis/tools/db_columns.go new file mode 100644 index 0000000..e0b8390 --- /dev/null +++ b/app/other/apis/tools/db_columns.go @@ -0,0 +1,52 @@ +package tools + +import ( + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/other/models/tools" +) + +// GetDBColumnList 分页列表数据 +// @Summary 分页列表数据 / page list data +// @Description 数据库表列分页列表 / database table column page list +// @Tags 工具 / 生成工具 +// @Param tableName query string false "tableName / 数据表名称" +// @Param pageSize query int false "pageSize / 页条数" +// @Param pageIndex query int false "pageIndex / 页码" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/db/columns/page [get] +func (e Gen) GetDBColumnList(c *gin.Context) { + e.Context = c + log := e.GetLogger() + var data tools.DBColumns + var err error + var pageSize = 10 + var pageIndex = 1 + + if size := c.Request.FormValue("pageSize"); size != "" { + pageSize, err = pkg.StringToInt(size) + } + + if index := c.Request.FormValue("pageIndex"); index != "" { + pageIndex, err = pkg.StringToInt(index) + } + + db, err := pkg.GetOrm(c) + if err != nil { + log.Errorf("get db connection error, %s", err.Error()) + e.Error(500, err, "数据库连接获取失败") + return + } + + data.TableName = c.Request.FormValue("tableName") + pkg.Assert(data.TableName == "", "table name cannot be empty!", 500) + result, count, err := data.GetPage(db, pageSize, pageIndex) + if err != nil { + log.Errorf("GetPage error, %s", err.Error()) + e.Error(500, err, "") + return + } + e.PageOK(result, count, pageIndex, pageSize, "查询成功") +} diff --git a/app/other/apis/tools/db_tables.go b/app/other/apis/tools/db_tables.go new file mode 100644 index 0000000..d1ec3c4 --- /dev/null +++ b/app/other/apis/tools/db_tables.go @@ -0,0 +1,60 @@ +package tools + +import ( + "errors" + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/config" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/other/models/tools" +) + +// GetDBTableList 分页列表数据 +// @Summary 分页列表数据 / page list data +// @Description 数据库表分页列表 / database table page list +// @Tags 工具 / 生成工具 +// @Param tableName query string false "tableName / 数据表名称" +// @Param pageSize query int false "pageSize / 页条数" +// @Param pageIndex query int false "pageIndex / 页码" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/db/tables/page [get] +func (e Gen) GetDBTableList(c *gin.Context) { + //var res response.Response + var data tools.DBTables + var err error + var pageSize = 10 + var pageIndex = 1 + e.Context = c + log := e.GetLogger() + if config.DatabaseConfig.Driver == "sqlite3" || config.DatabaseConfig.Driver == "postgres" { + err = errors.New("对不起,sqlite3 或 postgres 不支持代码生成!") + log.Warn(err) + e.Error(403, err, "") + return + } + + if size := c.Request.FormValue("pageSize"); size != "" { + pageSize, err = pkg.StringToInt(size) + } + + if index := c.Request.FormValue("pageIndex"); index != "" { + pageIndex, err = pkg.StringToInt(index) + } + + db, err := pkg.GetOrm(c) + if err != nil { + log.Errorf("get db connection error, %s", err.Error()) + e.Error(500, err, "数据库连接获取失败") + return + } + + data.TableName = c.Request.FormValue("tableName") + result, count, err := data.GetPage(db, pageSize, pageIndex) + if err != nil { + log.Errorf("GetPage error, %s", err.Error()) + e.Error(500, err, "") + return + } + e.PageOK(result, count, pageIndex, pageSize, "查询成功") +} diff --git a/app/other/apis/tools/gen.go b/app/other/apis/tools/gen.go new file mode 100644 index 0000000..e9baafc --- /dev/null +++ b/app/other/apis/tools/gen.go @@ -0,0 +1,410 @@ +package tools + +import ( + "bytes" + "fmt" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "strconv" + "strings" + "text/template" + "time" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/config" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + + "go-admin/app/other/models/tools" +) + +type Gen struct { + api.Api +} + +func (e Gen) Preview(c *gin.Context) { + e.Context = c + log := e.GetLogger() + table := tools.SysTables{} + id, err := pkg.StringToInt(c.Param("tableId")) + if err != nil { + log.Error(err) + e.Error(500, err, fmt.Sprintf("tableId接收失败!错误详情:%s", err.Error())) + return + } + table.TableId = id + t1, err := template.ParseFiles("template/v4/model.go.template") + if err != nil { + log.Error(err) + e.Error(500, err, fmt.Sprintf("model模版读取失败!错误详情:%s", err.Error())) + return + } + t2, err := template.ParseFiles("template/v4/no_actions/apis.go.template") + if err != nil { + log.Error(err) + e.Error(500, err, fmt.Sprintf("api模版读取失败!错误详情:%s", err.Error())) + return + } + t3, err := template.ParseFiles("template/v4/js.go.template") + if err != nil { + log.Error(err) + e.Error(500, err, fmt.Sprintf("js模版读取失败!错误详情:%s", err.Error())) + return + } + t4, err := template.ParseFiles("template/v4/vue.go.template") + if err != nil { + log.Error(err) + e.Error(500, err, fmt.Sprintf("vue模版读取失败!错误详情:%s", err.Error())) + return + } + t5, err := template.ParseFiles("template/v4/no_actions/router_check_role.go.template") + if err != nil { + log.Error(err) + e.Error(500, err, fmt.Sprintf("路由模版读取失败!错误详情:%s", err.Error())) + return + } + t6, err := template.ParseFiles("template/v4/dto.go.template") + if err != nil { + log.Error(err) + e.Error(500, err, fmt.Sprintf("dto模版读取失败!错误详情:%s", err.Error())) + return + } + t7, err := template.ParseFiles("template/v4/no_actions/service.go.template") + if err != nil { + log.Error(err) + e.Error(500, err, fmt.Sprintf("service模版读取失败!错误详情:%s", err.Error())) + return + } + + db, err := pkg.GetOrm(c) + if err != nil { + log.Errorf("get db connection error, %s", err.Error()) + e.Error(500, err, fmt.Sprintf("数据库链接获取失败!错误详情:%s", err.Error())) + return + } + + tab, _ := table.Get(db,false) + var b1 bytes.Buffer + err = t1.Execute(&b1, tab) + var b2 bytes.Buffer + err = t2.Execute(&b2, tab) + var b3 bytes.Buffer + err = t3.Execute(&b3, tab) + var b4 bytes.Buffer + err = t4.Execute(&b4, tab) + var b5 bytes.Buffer + err = t5.Execute(&b5, tab) + var b6 bytes.Buffer + err = t6.Execute(&b6, tab) + var b7 bytes.Buffer + err = t7.Execute(&b7, tab) + + mp := make(map[string]interface{}) + mp["template/model.go.template"] = b1.String() + mp["template/api.go.template"] = b2.String() + mp["template/js.go.template"] = b3.String() + mp["template/vue.go.template"] = b4.String() + mp["template/router.go.template"] = b5.String() + mp["template/dto.go.template"] = b6.String() + mp["template/service.go.template"] = b7.String() + e.OK(mp, "") +} + +func (e Gen) GenCode(c *gin.Context) { + e.Context = c + log := e.GetLogger() + table := tools.SysTables{} + id, err := pkg.StringToInt(c.Param("tableId")) + if err != nil { + log.Error(err) + e.Error(500, err, fmt.Sprintf("tableId参数接收失败!错误详情:%s", err.Error())) + return + } + + db, err := pkg.GetOrm(c) + if err != nil { + log.Errorf("get db connection error, %s", err.Error()) + e.Error(500, err, fmt.Sprintf("数据库链接获取失败!错误详情:%s", err.Error())) + return + } + + table.TableId = id + tab, _ := table.Get(db,false) + + e.NOActionsGen(c, tab) + + e.OK("", "Code generated successfully!") +} + +func (e Gen) GenApiToFile(c *gin.Context) { + e.Context = c + log := e.GetLogger() + table := tools.SysTables{} + id, err := pkg.StringToInt(c.Param("tableId")) + if err != nil { + log.Error(err) + e.Error(500, err, fmt.Sprintf("tableId参数获取失败!错误详情:%s", err.Error())) + return + } + + db, err := pkg.GetOrm(c) + if err != nil { + log.Errorf("get db connection error, %s", err.Error()) + e.Error(500, err, fmt.Sprintf("数据库链接获取失败!错误详情:%s", err.Error())) + return + } + + table.TableId = id + tab, _ := table.Get(db,false) + e.genApiToFile(c, tab) + + e.OK("", "Code generated successfully!") +} + +func (e Gen) NOActionsGen(c *gin.Context, tab tools.SysTables) { + e.Context = c + log := e.GetLogger() + tab.MLTBName = strings.Replace(tab.TBName, "_", "-", -1) + + basePath := "template/v4/" + routerFile := basePath + "no_actions/router_check_role.go.template" + + if tab.IsAuth == 2 { + routerFile = basePath + "no_actions/router_no_check_role.go.template" + } + + t1, err := template.ParseFiles(basePath + "model.go.template") + if err != nil { + log.Error(err) + e.Error(500, err, fmt.Sprintf("model模版读取失败!错误详情:%s", err.Error())) + return + } + t2, err := template.ParseFiles(basePath + "no_actions/apis.go.template") + if err != nil { + log.Error(err) + e.Error(500, err, fmt.Sprintf("api模版读取失败!错误详情:%s", err.Error())) + return + } + t3, err := template.ParseFiles(routerFile) + if err != nil { + log.Error(err) + e.Error(500, err, fmt.Sprintf("路由模版失败!错误详情:%s", err.Error())) + return + } + t4, err := template.ParseFiles(basePath + "js.go.template") + if err != nil { + log.Error(err) + e.Error(500, err, fmt.Sprintf("js模版解析失败!错误详情:%s", err.Error())) + return + } + t5, err := template.ParseFiles(basePath + "vue.go.template") + if err != nil { + log.Error(err) + e.Error(500, err, fmt.Sprintf("vue模版解析失败!错误详情:%s", err.Error())) + return + } + t6, err := template.ParseFiles(basePath + "dto.go.template") + if err != nil { + log.Error(err) + e.Error(500, err, fmt.Sprintf("dto模版解析失败失败!错误详情:%s", err.Error())) + return + } + t7, err := template.ParseFiles(basePath + "no_actions/service.go.template") + if err != nil { + log.Error(err) + e.Error(500, err, fmt.Sprintf("service模版失败!错误详情:%s", err.Error())) + return + } + + _ = pkg.PathCreate("./app/" + tab.PackageName + "/apis/") + _ = pkg.PathCreate("./app/" + tab.PackageName + "/models/") + _ = pkg.PathCreate("./app/" + tab.PackageName + "/router/") + _ = pkg.PathCreate("./app/" + tab.PackageName + "/service/dto/") + _ = pkg.PathCreate(config.GenConfig.FrontPath + "/api/" + tab.PackageName + "/") + err = pkg.PathCreate(config.GenConfig.FrontPath + "/views/" + tab.PackageName + "/" + tab.MLTBName + "/") + if err != nil { + log.Error(err) + e.Error(500, err, fmt.Sprintf("views目录创建失败!错误详情:%s", err.Error())) + return + } + + var b1 bytes.Buffer + err = t1.Execute(&b1, tab) + var b2 bytes.Buffer + err = t2.Execute(&b2, tab) + var b3 bytes.Buffer + err = t3.Execute(&b3, tab) + var b4 bytes.Buffer + err = t4.Execute(&b4, tab) + var b5 bytes.Buffer + err = t5.Execute(&b5, tab) + var b6 bytes.Buffer + err = t6.Execute(&b6, tab) + var b7 bytes.Buffer + err = t7.Execute(&b7, tab) + pkg.FileCreate(b1, "./app/"+tab.PackageName+"/models/"+tab.TBName+".go") + pkg.FileCreate(b2, "./app/"+tab.PackageName+"/apis/"+tab.TBName+".go") + pkg.FileCreate(b3, "./app/"+tab.PackageName+"/router/"+tab.TBName+".go") + pkg.FileCreate(b4, config.GenConfig.FrontPath+"/api/"+tab.PackageName+"/"+tab.MLTBName+".js") + pkg.FileCreate(b5, config.GenConfig.FrontPath+"/views/"+tab.PackageName+"/"+tab.MLTBName+"/index.vue") + pkg.FileCreate(b6, "./app/"+tab.PackageName+"/service/dto/"+tab.TBName+".go") + pkg.FileCreate(b7, "./app/"+tab.PackageName+"/service/"+tab.TBName+".go") + +} + +func (e Gen) genApiToFile(c *gin.Context, tab tools.SysTables) { + err := e.MakeContext(c). + MakeOrm(). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + basePath := "template/" + + t1, err := template.ParseFiles(basePath + "api_migrate.template") + if err != nil { + e.Logger.Error(err) + e.Error(500, err, fmt.Sprintf("数据迁移模版解析失败!错误详情:%s", err.Error())) + return + } + i := strconv.FormatInt(time.Now().UnixNano()/1e6, 10) + var b1 bytes.Buffer + err = t1.Execute(&b1, struct { + tools.SysTables + GenerateTime string + }{tab, i}) + + pkg.FileCreate(b1, "./cmd/migrate/migration/version-local/"+i+"_migrate.go") + +} + +func (e Gen) GenMenuAndApi(c *gin.Context) { + s := service.SysMenu{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + table := tools.SysTables{} + id, err := pkg.StringToInt(c.Param("tableId")) + if err != nil { + e.Logger.Error(err) + e.Error(500, err, fmt.Sprintf("tableId参数解析失败!错误详情:%s", err.Error())) + return + } + + table.TableId = id + tab, _ := table.Get(e.Orm,true) + tab.MLTBName = strings.Replace(tab.TBName, "_", "-", -1) + + Mmenu := dto.SysMenuInsertReq{} + Mmenu.Title = tab.TableComment + Mmenu.Icon = "pass" + Mmenu.Path = "/" + tab.MLTBName + Mmenu.MenuType = "M" + Mmenu.Action = "无" + Mmenu.ParentId = 0 + Mmenu.NoCache = false + Mmenu.Component = "Layout" + Mmenu.Sort = 0 + Mmenu.Visible = "0" + Mmenu.IsFrame = "0" + Mmenu.CreateBy = 1 + s.Insert(&Mmenu) + + Cmenu := dto.SysMenuInsertReq{} + Cmenu.MenuName = tab.ClassName + "Manage" + Cmenu.Title = tab.TableComment + Cmenu.Icon = "pass" + Cmenu.Path = "/" + tab.PackageName + "/" + tab.MLTBName + Cmenu.MenuType = "C" + Cmenu.Action = "无" + Cmenu.Permission = tab.PackageName + ":" + tab.BusinessName + ":list" + Cmenu.ParentId = Mmenu.MenuId + Cmenu.NoCache = false + Cmenu.Component = "/" + tab.PackageName + "/" + tab.MLTBName + "/index" + Cmenu.Sort = 0 + Cmenu.Visible = "0" + Cmenu.IsFrame = "0" + Cmenu.CreateBy = 1 + Cmenu.UpdateBy = 1 + s.Insert(&Cmenu) + + MList := dto.SysMenuInsertReq{} + MList.MenuName = "" + MList.Title = "分页获取" + tab.TableComment + MList.Icon = "" + MList.Path = tab.TBName + MList.MenuType = "F" + MList.Action = "无" + MList.Permission = tab.PackageName + ":" + tab.BusinessName + ":query" + MList.ParentId = Cmenu.MenuId + MList.NoCache = false + MList.Sort = 0 + MList.Visible = "0" + MList.IsFrame = "0" + MList.CreateBy = 1 + MList.UpdateBy = 1 + s.Insert(&MList) + + MCreate := dto.SysMenuInsertReq{} + MCreate.MenuName = "" + MCreate.Title = "创建" + tab.TableComment + MCreate.Icon = "" + MCreate.Path = tab.TBName + MCreate.MenuType = "F" + MCreate.Action = "无" + MCreate.Permission = tab.PackageName + ":" + tab.BusinessName + ":add" + MCreate.ParentId = Cmenu.MenuId + MCreate.NoCache = false + MCreate.Sort = 0 + MCreate.Visible = "0" + MCreate.IsFrame = "0" + MCreate.CreateBy = 1 + MCreate.UpdateBy = 1 + s.Insert(&MCreate) + + MUpdate := dto.SysMenuInsertReq{} + MUpdate.MenuName = "" + MUpdate.Title = "修改" + tab.TableComment + MUpdate.Icon = "" + MUpdate.Path = tab.TBName + MUpdate.MenuType = "F" + MUpdate.Action = "无" + MUpdate.Permission = tab.PackageName + ":" + tab.BusinessName + ":edit" + MUpdate.ParentId = Cmenu.MenuId + MUpdate.NoCache = false + MUpdate.Sort = 0 + MUpdate.Visible = "0" + MUpdate.IsFrame = "0" + MUpdate.CreateBy = 1 + MUpdate.UpdateBy = 1 + s.Insert(&MUpdate) + + MDelete := dto.SysMenuInsertReq{} + MDelete.MenuName = "" + MDelete.Title = "删除" + tab.TableComment + MDelete.Icon = "" + MDelete.Path = tab.TBName + MDelete.MenuType = "F" + MDelete.Action = "无" + MDelete.Permission = tab.PackageName + ":" + tab.BusinessName + ":remove" + MDelete.ParentId = Cmenu.MenuId + MDelete.NoCache = false + MDelete.Sort = 0 + MDelete.Visible = "0" + MDelete.IsFrame = "0" + MDelete.CreateBy = 1 + MDelete.UpdateBy = 1 + s.Insert(&MDelete) + + e.OK("", "数据生成成功!") +} diff --git a/app/other/apis/tools/sys_tables.go b/app/other/apis/tools/sys_tables.go new file mode 100644 index 0000000..ea7dee7 --- /dev/null +++ b/app/other/apis/tools/sys_tables.go @@ -0,0 +1,361 @@ +package tools + +import ( + "strings" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "gorm.io/gorm" + + "go-admin/app/other/models/tools" +) + +type SysTable struct { + api.Api +} + +// GetPage 分页列表数据 +// @Summary 分页列表数据 +// @Description 生成表分页列表 +// @Tags 工具 / 生成工具 +// @Param tableName query string false "tableName / 数据表名称" +// @Param pageSize query int false "pageSize / 页条数" +// @Param pageIndex query int false "pageIndex / 页码" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys/tables/page [get] +func (e SysTable) GetPage(c *gin.Context) { + e.Context = c + log := e.GetLogger() + var data tools.SysTables + var err error + var pageSize = 10 + var pageIndex = 1 + + if size := c.Request.FormValue("pageSize"); size != "" { + pageSize, err = pkg.StringToInt(size) + } + + if index := c.Request.FormValue("pageIndex"); index != "" { + pageIndex, err = pkg.StringToInt(index) + } + + db, err := e.GetOrm() + if err != nil { + log.Errorf("get db connection error, %s", err.Error()) + e.Error(500, err, "数据库连接获取失败") + return + } + + data.TBName = c.Request.FormValue("tableName") + data.TableComment = c.Request.FormValue("tableComment") + result, count, err := data.GetPage(db, pageSize, pageIndex) + if err != nil { + log.Errorf("GetPage error, %s", err.Error()) + e.Error(500, err, "") + return + } + e.PageOK(result, count, pageIndex, pageSize, "查询成功") +} + +// Get +// @Summary 获取配置 +// @Description 获取JSON +// @Tags 工具 / 生成工具 +// @Param configKey path int true "configKey" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys/tables/info/{tableId} [get] +// @Security Bearer +func (e SysTable) Get(c *gin.Context) { + e.Context = c + log := e.GetLogger() + db, err := e.GetOrm() + if err != nil { + log.Errorf("get db connection error, %s", err.Error()) + e.Error(500, err, "数据库连接获取失败") + return + } + + var data tools.SysTables + data.TableId, _ = pkg.StringToInt(c.Param("tableId")) + result, err := data.Get(db,true) + if err != nil { + log.Errorf("Get error, %s", err.Error()) + e.Error(500, err, "") + return + } + + mp := make(map[string]interface{}) + mp["list"] = result.Columns + mp["info"] = result + e.OK(mp, "") +} + +func (e SysTable) GetSysTablesInfo(c *gin.Context) { + e.Context = c + log := e.GetLogger() + db, err := e.GetOrm() + if err != nil { + log.Errorf("get db connection error, %s", err.Error()) + e.Error(500, err, "数据库连接获取失败") + return + } + + var data tools.SysTables + if c.Request.FormValue("tableName") != "" { + data.TBName = c.Request.FormValue("tableName") + } + result, err := data.Get(db,true) + if err != nil { + log.Errorf("Get error, %s", err.Error()) + e.Error(500, err, "抱歉未找到相关信息") + return + } + + mp := make(map[string]interface{}) + mp["list"] = result.Columns + mp["info"] = result + e.OK(mp, "") + //res.Data = mp + //c.JSON(http.StatusOK, res.ReturnOK()) +} + +func (e SysTable) GetSysTablesTree(c *gin.Context) { + e.Context = c + log := e.GetLogger() + db, err := e.GetOrm() + if err != nil { + log.Errorf("get db connection error, %s", err.Error()) + e.Error(500, err, "数据库连接获取失败") + return + } + + var data tools.SysTables + result, err := data.GetTree(db) + if err != nil { + log.Errorf("GetTree error, %s", err.Error()) + e.Error(500, err, "抱歉未找到相关信息") + return + } + + e.OK(result, "") +} + +// Insert +// @Summary 添加表结构 +// @Description 添加表结构 +// @Tags 工具 / 生成工具 +// @Accept application/json +// @Product application/json +// @Param tables query string false "tableName / 数据表名称" +// @Success 200 {string} string "{"code": 200, "message": "添加成功"}" +// @Success 200 {string} string "{"code": -1, "message": "添加失败"}" +// @Router /api/v1/sys/tables/info [post] +// @Security Bearer +func (e SysTable) Insert(c *gin.Context) { + e.Context = c + log := e.GetLogger() + db, err := e.GetOrm() + if err != nil { + log.Errorf("get db connection error, %s", err.Error()) + e.Error(500, err, "数据库连接获取失败") + return + } + + tablesList := strings.Split(c.Request.FormValue("tables"), ",") + for i := 0; i < len(tablesList); i++ { + + data, err := genTableInit(db, tablesList, i, c) + if err != nil { + log.Errorf("genTableInit error, %s", err.Error()) + e.Error(500, err, "") + return + } + + _, err = data.Create(db) + if err != nil { + log.Errorf("Create error, %s", err.Error()) + e.Error(500, err, "") + return + } + } + e.OK(nil, "添加成功") + +} + +func genTableInit(tx *gorm.DB, tablesList []string, i int, c *gin.Context) (tools.SysTables, error) { + var data tools.SysTables + var dbTable tools.DBTables + var dbColumn tools.DBColumns + data.TBName = tablesList[i] + data.CreateBy = 0 + + dbTable.TableName = data.TBName + dbtable, err := dbTable.Get(tx) + if err != nil { + return data, err + } + + dbColumn.TableName = data.TBName + tablenamelist := strings.Split(dbColumn.TableName, "_") + for i := 0; i < len(tablenamelist); i++ { + strStart := string([]byte(tablenamelist[i])[:1]) + strend := string([]byte(tablenamelist[i])[1:]) + // 大驼峰表名 结构体使用 + data.ClassName += strings.ToUpper(strStart) + strend + // 小驼峰表名 js函数名和权限标识使用 + if i == 0 { + data.BusinessName += strings.ToLower(strStart) + strend + } else { + data.BusinessName += strings.ToUpper(strStart) + strend + } + //data.PackageName += strings.ToLower(strStart) + strings.ToLower(strend) + //data.ModuleName += strings.ToLower(strStart) + strings.ToLower(strend) + } + //data.ModuleFrontName = strings.ReplaceAll(data.ModuleName, "_", "-") + data.PackageName = "admin" + data.TplCategory = "crud" + data.Crud = true + // 中横线表名称,接口路径、前端文件夹名称和js名称使用 + data.ModuleName = strings.Replace(data.TBName, "_", "-", -1) + dbcolumn, err := dbColumn.GetList(tx) + data.CreateBy = 0 + data.TableComment = dbtable.TableComment + if dbtable.TableComment == "" { + data.TableComment = data.ClassName + } + + data.FunctionName = data.TableComment + //data.BusinessName = data.ModuleName + data.IsLogicalDelete = "1" + data.LogicalDelete = true + data.LogicalDeleteColumn = "is_del" + data.IsActions = 2 + data.IsDataScope = 1 + data.IsAuth = 1 + + data.FunctionAuthor = "wenjianzhang" + for i := 0; i < len(dbcolumn); i++ { + var column tools.SysColumns + column.ColumnComment = dbcolumn[i].ColumnComment + column.ColumnName = dbcolumn[i].ColumnName + column.ColumnType = dbcolumn[i].ColumnType + column.Sort = i + 1 + column.Insert = true + column.IsInsert = "1" + column.QueryType = "EQ" + column.IsPk = "0" + + namelist := strings.Split(dbcolumn[i].ColumnName, "_") + for i := 0; i < len(namelist); i++ { + strStart := string([]byte(namelist[i])[:1]) + strend := string([]byte(namelist[i])[1:]) + column.GoField += strings.ToUpper(strStart) + strend + if i == 0 { + column.JsonField = strings.ToLower(strStart) + strend + } else { + column.JsonField += strings.ToUpper(strStart) + strend + } + } + if strings.Contains(dbcolumn[i].ColumnKey, "PR") { + column.IsPk = "1" + column.Pk = true + data.PkColumn = dbcolumn[i].ColumnName + //column.GoField = strings.ToUpper(column.GoField) + //column.JsonField = strings.ToUpper(column.JsonField) + data.PkGoField = column.GoField + data.PkJsonField = column.JsonField + } + column.IsRequired = "0" + if strings.Contains(dbcolumn[i].IsNullable, "NO") { + column.IsRequired = "1" + column.Required = true + } + + if strings.Contains(dbcolumn[i].ColumnType, "int") { + if strings.Contains(dbcolumn[i].ColumnKey, "PR") { + column.GoType = "int" + } else { + column.GoType = "string" + } + column.HtmlType = "input" + } else if strings.Contains(dbcolumn[i].ColumnType, "timestamp") { + column.GoType = "time.Time" + column.HtmlType = "datetime" + } else if strings.Contains(dbcolumn[i].ColumnType, "datetime") { + column.GoType = "time.Time" + column.HtmlType = "datetime" + } else { + column.GoType = "string" + column.HtmlType = "input" + } + + data.Columns = append(data.Columns, column) + } + return data, err +} + +// Update +// @Summary 修改表结构 +// @Description 修改表结构 +// @Tags 工具 / 生成工具 +// @Accept application/json +// @Product application/json +// @Param data body tools.SysTables true "body" +// @Success 200 {string} string "{"code": 200, "message": "添加成功"}" +// @Success 200 {string} string "{"code": -1, "message": "添加失败"}" +// @Router /api/v1/sys/tables/info [put] +// @Security Bearer +func (e SysTable) Update(c *gin.Context) { + var data tools.SysTables + err := c.Bind(&data) + pkg.HasError(err, "数据解析失败", 500) + + e.Context = c + log := e.GetLogger() + db, err := e.GetOrm() + if err != nil { + log.Errorf("get db connection error, %s", err.Error()) + e.Error(500, err, "数据库连接获取失败") + return + } + + data.UpdateBy = 0 + result, err := data.Update(db) + if err != nil { + log.Errorf("Update error, %s", err.Error()) + e.Error(500, err, "") + return + } + e.OK(result, "修改成功") +} + +// Delete +// @Summary 删除表结构 +// @Description 删除表结构 +// @Tags 工具 / 生成工具 +// @Param tableId path int true "tableId" +// @Success 200 {string} string "{"code": 200, "message": "删除成功"}" +// @Success 200 {string} string "{"code": -1, "message": "删除失败"}" +// @Router /api/v1/sys/tables/info/{tableId} [delete] +func (e SysTable) Delete(c *gin.Context) { + e.Context = c + log := e.GetLogger() + db, err := e.GetOrm() + if err != nil { + log.Errorf("get db connection error, %s", err.Error()) + e.Error(500, err, "数据库连接获取失败") + return + } + + var data tools.SysTables + IDS := pkg.IdsStrToIdsIntGroup("tableId", c) + _, err = data.BatchDelete(db, IDS) + if err != nil { + log.Errorf("BatchDelete error, %s", err.Error()) + e.Error(500, err, "删除失败") + return + } + e.OK(nil, "删除成功") +} diff --git a/app/other/models/tools/db_columns.go b/app/other/models/tools/db_columns.go new file mode 100644 index 0000000..46d9bc6 --- /dev/null +++ b/app/other/models/tools/db_columns.go @@ -0,0 +1,70 @@ +package tools + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/config" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "gorm.io/gorm" +) + +type DBColumns struct { + TableSchema string `gorm:"column:TABLE_SCHEMA" json:"tableSchema"` + TableName string `gorm:"column:TABLE_NAME" json:"tableName"` + ColumnName string `gorm:"column:COLUMN_NAME" json:"columnName"` + ColumnDefault string `gorm:"column:COLUMN_DEFAULT" json:"columnDefault"` + IsNullable string `gorm:"column:IS_NULLABLE" json:"isNullable"` + DataType string `gorm:"column:DATA_TYPE" json:"dataType"` + CharacterMaximumLength string `gorm:"column:CHARACTER_MAXIMUM_LENGTH" json:"characterMaximumLength"` + CharacterSetName string `gorm:"column:CHARACTER_SET_NAME" json:"characterSetName"` + ColumnType string `gorm:"column:COLUMN_TYPE" json:"columnType"` + ColumnKey string `gorm:"column:COLUMN_KEY" json:"columnKey"` + Extra string `gorm:"column:EXTRA" json:"extra"` + ColumnComment string `gorm:"column:COLUMN_COMMENT" json:"columnComment"` +} + +func (e *DBColumns) GetPage(tx *gorm.DB, pageSize int, pageIndex int) ([]DBColumns, int, error) { + var doc []DBColumns + var count int64 + table := new(gorm.DB) + + if config.DatabaseConfig.Driver == "mysql" { + table = tx.Table("information_schema.`COLUMNS`") + table = table.Where("table_schema= ? ", config.GenConfig.DBName) + + if e.TableName != "" { + return nil, 0, errors.New("table name cannot be empty!") + } + + table = table.Where("TABLE_NAME = ?", e.TableName) + } + + if err := table.Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&doc).Offset(-1).Limit(-1).Count(&count).Error; err != nil { + return nil, 0, err + } + //table.Count(&count) + return doc, int(count), nil + +} + +func (e *DBColumns) GetList(tx *gorm.DB) ([]DBColumns, error) { + var doc []DBColumns + table := new(gorm.DB) + + if e.TableName == "" { + return nil, errors.New("table name cannot be empty!") + } + + if config.DatabaseConfig.Driver == "mysql" { + table = tx.Table("information_schema.columns") + table = table.Where("table_schema= ? ", config.GenConfig.DBName) + + table = table.Where("TABLE_NAME = ?", e.TableName).Order("ORDINAL_POSITION asc") + } else { + pkg.Assert(true, "目前只支持mysql数据库", 500) + } + if err := table.Find(&doc).Error; err != nil { + return doc, err + } + return doc, nil +} \ No newline at end of file diff --git a/app/other/models/tools/db_tables.go b/app/other/models/tools/db_tables.go new file mode 100644 index 0000000..77c4927 --- /dev/null +++ b/app/other/models/tools/db_tables.go @@ -0,0 +1,62 @@ +package tools + +import ( + "errors" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + + "gorm.io/gorm" + + config2 "github.com/go-admin-team/go-admin-core/sdk/config" +) + +type DBTables struct { + TableName string `gorm:"column:TABLE_NAME" json:"tableName"` + Engine string `gorm:"column:ENGINE" json:"engine"` + TableRows string `gorm:"column:TABLE_ROWS" json:"tableRows"` + TableCollation string `gorm:"column:TABLE_COLLATION" json:"tableCollation"` + CreateTime string `gorm:"column:CREATE_TIME" json:"createTime"` + UpdateTime string `gorm:"column:UPDATE_TIME" json:"updateTime"` + TableComment string `gorm:"column:TABLE_COMMENT" json:"tableComment"` +} + +func (e *DBTables) GetPage(tx *gorm.DB, pageSize int, pageIndex int) ([]DBTables, int, error) { + var doc []DBTables + table := new(gorm.DB) + var count int64 + + if config2.DatabaseConfig.Driver == "mysql" { + table = tx.Table("information_schema.tables") + table = table.Where("TABLE_NAME not in (select table_name from `" + config2.GenConfig.DBName + "`.sys_tables) ") + table = table.Where("table_schema= ? ", config2.GenConfig.DBName) + + if e.TableName != "" { + table = table.Where("TABLE_NAME = ?", e.TableName) + } + if err := table.Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&doc).Offset(-1).Limit(-1).Count(&count).Error; err != nil { + return nil, 0, err + } + } else { + pkg.Assert(true, "目前只支持mysql数据库", 500) + } + + //table.Count(&count) + return doc, int(count), nil +} + +func (e *DBTables) Get(tx *gorm.DB) (DBTables, error) { + var doc DBTables + if config2.DatabaseConfig.Driver == "mysql" { + table := tx.Table("information_schema.tables") + table = table.Where("table_schema= ? ", config2.GenConfig.DBName) + if e.TableName == "" { + return doc, errors.New("table name cannot be empty!") + } + table = table.Where("TABLE_NAME = ?", e.TableName) + if err := table.First(&doc).Error; err != nil { + return doc, err + } + } else { + pkg.Assert(true, "目前只支持mysql数据库", 500) + } + return doc, nil +} diff --git a/app/other/models/tools/sys_columns.go b/app/other/models/tools/sys_columns.go new file mode 100644 index 0000000..ff6b8dd --- /dev/null +++ b/app/other/models/tools/sys_columns.go @@ -0,0 +1,101 @@ +package tools + +import ( + common "go-admin/common/models" + + "gorm.io/gorm" +) + +type SysColumns struct { + ColumnId int `gorm:"primaryKey;autoIncrement" json:"columnId"` + TableId int `gorm:"" json:"tableId"` + ColumnName string `gorm:"size:128;" json:"columnName"` + ColumnComment string `gorm:"column:column_comment;size:128;" json:"columnComment"` + ColumnType string `gorm:"column:column_type;size:128;" json:"columnType"` + GoType string `gorm:"column:go_type;size:128;" json:"goType"` + GoField string `gorm:"column:go_field;size:128;" json:"goField"` + JsonField string `gorm:"column:json_field;size:128;" json:"jsonField"` + IsPk string `gorm:"column:is_pk;size:4;" json:"isPk"` + IsIncrement string `gorm:"column:is_increment;size:4;" json:"isIncrement"` + IsRequired string `gorm:"column:is_required;size:4;" json:"isRequired"` + IsInsert string `gorm:"column:is_insert;size:4;" json:"isInsert"` + IsEdit string `gorm:"column:is_edit;size:4;" json:"isEdit"` + IsList string `gorm:"column:is_list;size:4;" json:"isList"` + IsQuery string `gorm:"column:is_query;size:4;" json:"isQuery"` + QueryType string `gorm:"column:query_type;size:128;" json:"queryType"` + HtmlType string `gorm:"column:html_type;size:128;" json:"htmlType"` + DictType string `gorm:"column:dict_type;size:128;" json:"dictType"` + Sort int `gorm:"column:sort;" json:"sort"` + List string `gorm:"column:list;size:1;" json:"list"` + Pk bool `gorm:"column:pk;size:1;" json:"pk"` + Required bool `gorm:"column:required;size:1;" json:"required"` + SuperColumn bool `gorm:"column:super_column;size:1;" json:"superColumn"` + UsableColumn bool `gorm:"column:usable_column;size:1;" json:"usableColumn"` + Increment bool `gorm:"column:increment;size:1;" json:"increment"` + Insert bool `gorm:"column:insert;size:1;" json:"insert"` + Edit bool `gorm:"column:edit;size:1;" json:"edit"` + Query bool `gorm:"column:query;size:1;" json:"query"` + Remark string `gorm:"column:remark;size:255;" json:"remark"` + FkTableName string `gorm:"" json:"fkTableName"` + FkTableNameClass string `gorm:"" json:"fkTableNameClass"` + FkTableNamePackage string `gorm:"" json:"fkTableNamePackage"` + FkCol []SysColumns `gorm:"-" json:"fkCol"` + FkLabelId string `gorm:"" json:"fkLabelId"` + FkLabelName string `gorm:"size:255;" json:"fkLabelName"` + CreateBy int `gorm:"column:create_by;size:20;" json:"createBy"` + UpdateBy int `gorm:"column:update_By;size:20;" json:"updateBy"` + + common.ModelTime +} + +func (*SysColumns) TableName() string { + return "sys_columns" +} + +func (e *SysColumns) GetList(tx *gorm.DB, exclude bool) ([]SysColumns, error) { + var doc []SysColumns + table := tx.Table("sys_columns") + table = table.Where("table_id = ? ", e.TableId) + if exclude { + notIn := make([]string, 0, 6) + notIn = append(notIn, "id") + notIn = append(notIn, "create_by") + notIn = append(notIn, "update_by") + notIn = append(notIn, "created_at") + notIn = append(notIn, "updated_at") + notIn = append(notIn, "deleted_at") + table = table.Where(" column_name not in(?)", notIn) + } + + if err := table.Find(&doc).Error; err != nil { + return nil, err + } + return doc, nil +} + +func (e *SysColumns) Create(tx *gorm.DB) (SysColumns, error) { + var doc SysColumns + e.CreateBy = 0 + result := tx.Table("sys_columns").Create(&e) + if result.Error != nil { + err := result.Error + return doc, err + } + doc = *e + return doc, nil +} + +func (e *SysColumns) Update(tx *gorm.DB) (update SysColumns, err error) { + if err = tx.Table("sys_columns").First(&update, e.ColumnId).Error; err != nil { + return + } + + //参数1:是要修改的数据 + //参数2:是修改的数据 + e.UpdateBy = 0 + if err = tx.Table("sys_columns").Model(&update).Updates(&e).Error; err != nil { + return + } + + return +} diff --git a/app/other/models/tools/sys_tables.go b/app/other/models/tools/sys_tables.go new file mode 100644 index 0000000..4417a3e --- /dev/null +++ b/app/other/models/tools/sys_tables.go @@ -0,0 +1,234 @@ +package tools + +import ( + common "go-admin/common/models" + "strings" + + "gorm.io/gorm" +) + +type SysTables struct { + TableId int `gorm:"primaryKey;autoIncrement" json:"tableId"` //表编码 + TBName string `gorm:"column:table_name;size:255;" json:"tableName"` //表名称 + MLTBName string `gorm:"-" json:"-"` //表名称 + TableComment string `gorm:"size:255;" json:"tableComment"` //表备注 + ClassName string `gorm:"size:255;" json:"className"` //类名 + TplCategory string `gorm:"size:255;" json:"tplCategory"` // + PackageName string `gorm:"size:255;" json:"packageName"` //包名 + ModuleName string `gorm:"size:255;" json:"moduleName"` //go文件名 + ModuleFrontName string `gorm:"size:255;comment:前端文件名;" json:"moduleFrontName"` //前端文件名 + BusinessName string `gorm:"size:255;" json:"businessName"` // + FunctionName string `gorm:"size:255;" json:"functionName"` //功能名称 + FunctionAuthor string `gorm:"size:255;" json:"functionAuthor"` //功能作者 + PkColumn string `gorm:"size:255;" json:"pkColumn"` + PkGoField string `gorm:"size:255;" json:"pkGoField"` + PkJsonField string `gorm:"size:255;" json:"pkJsonField"` + Options string `gorm:"size:255;" json:"options"` + TreeCode string `gorm:"size:255;" json:"treeCode"` + TreeParentCode string `gorm:"size:255;" json:"treeParentCode"` + TreeName string `gorm:"size:255;" json:"treeName"` + Tree bool `gorm:"size:1;default:0;" json:"tree"` + Crud bool `gorm:"size:1;default:1;" json:"crud"` + Remark string `gorm:"size:255;" json:"remark"` + IsDataScope int `gorm:"size:1;" json:"isDataScope"` + IsActions int `gorm:"size:1;" json:"isActions"` + IsAuth int `gorm:"size:1;" json:"isAuth"` + IsLogicalDelete string `gorm:"size:1;" json:"isLogicalDelete"` + LogicalDelete bool `gorm:"size:1;" json:"logicalDelete"` + LogicalDeleteColumn string `gorm:"size:128;" json:"logicalDeleteColumn"` + common.ModelTime + common.ControlBy + DataScope string `gorm:"-" json:"dataScope"` + Params Params `gorm:"-" json:"params"` + Columns []SysColumns `gorm:"-" json:"columns"` +} + +func (*SysTables) TableName() string { + return "sys_tables" +} + +type Params struct { + TreeCode string `gorm:"-" json:"treeCode"` + TreeParentCode string `gorm:"-" json:"treeParentCode"` + TreeName string `gorm:"-" json:"treeName"` +} + +func (e *SysTables) GetPage(tx *gorm.DB, pageSize int, pageIndex int) ([]SysTables, int, error) { + var doc []SysTables + + table := tx.Table("sys_tables") + + if e.TBName != "" { + table = table.Where("table_name = ?", e.TBName) + } + if e.TableComment != "" { + table = table.Where("table_comment = ?", e.TableComment) + } + + var count int64 + + if err := table.Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&doc).Offset(-1).Limit(-1).Count(&count).Error; err != nil { + return nil, 0, err + } + //table.Where("`deleted_at` IS NULL").Count(&count) + return doc, int(count), nil +} + +func (e *SysTables) Get(tx *gorm.DB, exclude bool) (SysTables, error) { + var doc SysTables + var err error + table := tx.Table("sys_tables") + + if e.TBName != "" { + table = table.Where("table_name = ?", e.TBName) + } + if e.TableId != 0 { + table = table.Where("table_id = ?", e.TableId) + } + if e.TableComment != "" { + table = table.Where("table_comment = ?", e.TableComment) + } + + if err := table.First(&doc).Error; err != nil { + return doc, err + } + var col SysColumns + col.TableId = doc.TableId + if doc.Columns, err = col.GetList(tx, exclude); err != nil { + return doc, err + } + + return doc, nil +} + +func (e *SysTables) GetTree(tx *gorm.DB) ([]SysTables, error) { + var doc []SysTables + var err error + table := tx.Table("sys_tables") + + if e.TBName != "" { + table = table.Where("table_name = ?", e.TBName) + } + if e.TableId != 0 { + table = table.Where("table_id = ?", e.TableId) + } + if e.TableComment != "" { + table = table.Where("table_comment = ?", e.TableComment) + } + + if err := table.Find(&doc).Error; err != nil { + return doc, err + } + for i := 0; i < len(doc); i++ { + var col SysColumns + //col.FkCol = append(col.FkCol, SysColumns{ColumnId: 0, ColumnName: "请选择"}) + col.TableId = doc[i].TableId + if doc[i].Columns, err = col.GetList(tx, false); err != nil { + return doc, err + } + + } + + return doc, nil +} + +func (e *SysTables) Create(tx *gorm.DB) (SysTables, error) { + var doc SysTables + e.CreateBy = 0 + result := tx.Table("sys_tables").Create(&e) + if result.Error != nil { + err := result.Error + return doc, err + } + doc = *e + for i := 0; i < len(e.Columns); i++ { + e.Columns[i].TableId = doc.TableId + + _, _ = e.Columns[i].Create(tx) + } + + return doc, nil +} + +func (e *SysTables) Update(tx *gorm.DB) (update SysTables, err error) { + //if err = orm.Eloquent.Table("sys_tables").First(&update, e.TableId).Error; err != nil { + // return + //} + + //参数1:是要修改的数据 + //参数2:是修改的数据 + e.UpdateBy = 0 + if err = tx.Table("sys_tables").Where("table_id = ?", e.TableId).Updates(&e).Error; err != nil { + return + } + + tableNames := make([]string, 0) + for i := range e.Columns { + if e.Columns[i].FkTableName != "" { + tableNames = append(tableNames, e.Columns[i].FkTableName) + } + } + + tables := make([]SysTables, 0) + tableMap := make(map[string]*SysTables) + if len(tableNames) > 0 { + if err = tx.Table("sys_tables").Where("table_name in (?)", tableNames).Find(&tables).Error; err != nil { + return + } + for i := range tables { + tableMap[tables[i].TBName] = &tables[i] + } + } + + for i := 0; i < len(e.Columns); i++ { + if e.Columns[i].FkTableName != "" { + t, ok := tableMap[e.Columns[i].FkTableName] + if ok { + e.Columns[i].FkTableNameClass = t.ClassName + t.MLTBName = strings.Replace(t.TBName, "_", "-", -1) + e.Columns[i].FkTableNamePackage = t.MLTBName + } else { + tableNameList := strings.Split(e.Columns[i].FkTableName, "_") + e.Columns[i].FkTableNameClass = "" + //e.Columns[i].FkTableNamePackage = "" + for a := 0; a < len(tableNameList); a++ { + strStart := string([]byte(tableNameList[a])[:1]) + strEnd := string([]byte(tableNameList[a])[1:]) + e.Columns[i].FkTableNameClass += strings.ToUpper(strStart) + strEnd + //e.Columns[i].FkTableNamePackage += strings.ToLower(strStart) + strings.ToLower(strEnd) + } + } + } + _, _ = e.Columns[i].Update(tx) + } + return +} + +func (e *SysTables) Delete(db *gorm.DB) (success bool, err error) { + tx := db.Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + if err = tx.Table("sys_tables").Delete(SysTables{}, "table_id = ?", e.TableId).Error; err != nil { + success = false + return + } + if err = tx.Table("sys_columns").Delete(SysColumns{}, "table_id = ?", e.TableId).Error; err != nil { + success = false + return + } + success = true + return +} + +func (e *SysTables) BatchDelete(tx *gorm.DB, id []int) (Result bool, err error) { + if err = tx.Unscoped().Table(e.TableName()).Where(" table_id in (?)", id).Delete(&SysColumns{}).Error; err != nil { + return + } + Result = true + return +} diff --git a/app/other/router/file.go b/app/other/router/file.go new file mode 100644 index 0000000..c3bd3ac --- /dev/null +++ b/app/other/router/file.go @@ -0,0 +1,20 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "go-admin/app/other/apis" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerFileRouter) +} + +// 需认证的路由代码 +func registerFileRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + var api = apis.File{} + r := v1.Group("").Use(authMiddleware.MiddlewareFunc()) + { + r.POST("/public/uploadFile", api.UploadFile) + } +} diff --git a/app/other/router/gen_router.go b/app/other/router/gen_router.go new file mode 100644 index 0000000..c3c356b --- /dev/null +++ b/app/other/router/gen_router.go @@ -0,0 +1,56 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "go-admin/app/admin/apis" + "go-admin/app/other/apis/tools" +) + +func init() { + routerCheckRole = append(routerCheckRole, sysNoCheckRoleRouter, registerDBRouter, registerSysTableRouter) +} + +func sysNoCheckRoleRouter(v1 *gin.RouterGroup ,authMiddleware *jwt.GinJWTMiddleware) { + r1 := v1.Group("") + { + sys := apis.System{} + r1.GET("/captcha", sys.GenerateCaptchaHandler) + } + + r := v1.Group("").Use(authMiddleware.MiddlewareFunc()) + { + gen := tools.Gen{} + r.GET("/gen/preview/:tableId", gen.Preview) + r.GET("/gen/toproject/:tableId", gen.GenCode) + r.GET("/gen/apitofile/:tableId", gen.GenApiToFile) + r.GET("/gen/todb/:tableId", gen.GenMenuAndApi) + sysTable := tools.SysTable{} + r.GET("/gen/tabletree", sysTable.GetSysTablesTree) + } +} + +func registerDBRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + db := v1.Group("/db").Use(authMiddleware.MiddlewareFunc()) + { + gen := tools.Gen{} + db.GET("/tables/page", gen.GetDBTableList) + db.GET("/columns/page", gen.GetDBColumnList) + } +} + +func registerSysTableRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + tables := v1.Group("/sys/tables") + { + sysTable := tools.SysTable{} + tables.Group("").Use(authMiddleware.MiddlewareFunc()).GET("/page", sysTable.GetPage) + tablesInfo := tables.Group("/info").Use(authMiddleware.MiddlewareFunc()) + { + tablesInfo.POST("", sysTable.Insert) + tablesInfo.PUT("", sysTable.Update) + tablesInfo.DELETE("/:tableId", sysTable.Delete) + tablesInfo.GET("/:tableId", sysTable.Get) + tablesInfo.GET("", sysTable.GetSysTablesInfo) + } + } +} \ No newline at end of file diff --git a/app/other/router/init_router.go b/app/other/router/init_router.go new file mode 100644 index 0000000..708f93e --- /dev/null +++ b/app/other/router/init_router.go @@ -0,0 +1,36 @@ +package router + +import ( + "os" + + "github.com/gin-gonic/gin" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk" + common "go-admin/common/middleware" +) + +// InitRouter 路由初始化,不要怀疑,这里用到了 +func InitRouter() { + var r *gin.Engine + h := sdk.Runtime.GetEngine() + if h == nil { + log.Fatal("not found engine...") + os.Exit(-1) + } + switch h.(type) { + case *gin.Engine: + r = h.(*gin.Engine) + default: + log.Fatal("not support other engine") + os.Exit(-1) + } + // the jwt middleware + authMiddleware, err := common.AuthInit() + if err != nil { + log.Fatalf("JWT Init Error, %s", err.Error()) + } + + // 注册业务路由 + // TODO: 这里可存放业务路由,里边并无实际路由只有演示代码 + initRouter(r, authMiddleware) +} diff --git a/app/other/router/monitor.go b/app/other/router/monitor.go new file mode 100644 index 0000000..cdbe7ad --- /dev/null +++ b/app/other/router/monitor.go @@ -0,0 +1,23 @@ +package router + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/tools/transfer" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +func init() { + routerNoCheckRole = append(routerNoCheckRole, registerMonitorRouter) +} + +// 需认证的路由代码 +func registerMonitorRouter(v1 *gin.RouterGroup) { + v1.GET("/metrics", transfer.Handler(promhttp.Handler())) + //健康检查 + v1.GET("/health", func(c *gin.Context) { + c.Status(http.StatusOK) + }) + +} \ No newline at end of file diff --git a/app/other/router/router.go b/app/other/router/router.go new file mode 100644 index 0000000..8c893a2 --- /dev/null +++ b/app/other/router/router.go @@ -0,0 +1,42 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" +) + +var ( + routerNoCheckRole = make([]func(*gin.RouterGroup), 0) + routerCheckRole = make([]func(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware), 0) +) + +// initRouter 路由示例 +func initRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gin.Engine { + + // 无需认证的路由 + noCheckRoleRouter(r) + // 需要认证的路由 + checkRoleRouter(r, authMiddleware) + + return r +} + +// noCheckRoleRouter 无需认证的路由示例 +func noCheckRoleRouter(r *gin.Engine) { + // 可根据业务需求来设置接口版本 + v1 := r.Group("/api/v1") + + for _, f := range routerNoCheckRole { + f(v1) + } +} + +// checkRoleRouter 需要认证的路由示例 +func checkRoleRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) { + // 可根据业务需求来设置接口版本 + v1 := r.Group("/api/v1") + + for _, f := range routerCheckRole { + f(v1, authMiddleware) + } +} diff --git a/app/other/router/sys_server_monitor.go b/app/other/router/sys_server_monitor.go new file mode 100644 index 0000000..ffa91a1 --- /dev/null +++ b/app/other/router/sys_server_monitor.go @@ -0,0 +1,21 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "go-admin/app/other/apis" + "go-admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSysServerMonitorRouter) +} + +// 需认证的路由代码 +func registerSysServerMonitorRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.ServerMonitor{} + r := v1.Group("/server-monitor").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", api.ServerInfo) + } +} diff --git a/app/other/service/dto/sys_tables.go b/app/other/service/dto/sys_tables.go new file mode 100644 index 0000000..2e8b923 --- /dev/null +++ b/app/other/service/dto/sys_tables.go @@ -0,0 +1,6 @@ +package dto + +type SysTableSearch struct { + TBName string `form:"tableName" search:"type:exact;column:table_name;table:table_name"` + TableComment string `form:"tableComment" search:"type:icontains;column:table_comment;table:table_comment"` +} diff --git a/cmd/api/jobs.go b/cmd/api/jobs.go new file mode 100644 index 0000000..e95caf8 --- /dev/null +++ b/cmd/api/jobs.go @@ -0,0 +1,8 @@ +package api + +import "go-admin/app/jobs/router" + +func init() { + //注册路由 fixme 其他应用的路由,在本目录新建文件放在init方法 + AppRouters = append(AppRouters, router.InitRouter) +} diff --git a/cmd/api/other.go b/cmd/api/other.go new file mode 100644 index 0000000..4c291be --- /dev/null +++ b/cmd/api/other.go @@ -0,0 +1,8 @@ +package api + +import "go-admin/app/other/router" + +func init() { + //注册路由 fixme 其他应用的路由,在本目录新建文件放在init方法 + AppRouters = append(AppRouters, router.InitRouter) +} diff --git a/cmd/api/server.go b/cmd/api/server.go new file mode 100644 index 0000000..ba08d16 --- /dev/null +++ b/cmd/api/server.go @@ -0,0 +1,214 @@ +package api + +import ( + "context" + "fmt" + "go-admin/common/helper" + "go-admin/common/service/sysservice/sysstatuscode" + "go-admin/config/serverinit" + "go-admin/pkg/utility" + "log" + "net/http" + "os" + "os/signal" + "time" + + "github.com/pkg/errors" + "gorm.io/gorm" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/config/source/file" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/config" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/spf13/cobra" + + "go-admin/app/admin/models" + "go-admin/app/admin/router" + "go-admin/app/jobs" + "go-admin/common/database" + "go-admin/common/global" + common "go-admin/common/middleware" + "go-admin/common/middleware/handler" + "go-admin/common/storage" + ext "go-admin/config" +) + +var ( + configYml string + apiCheck bool + StartCmd = &cobra.Command{ + Use: "server", + Short: "Start API server", + Example: "go-admin server -c config/settings.yml", + SilenceUsage: true, + PreRun: func(cmd *cobra.Command, args []string) { + setup() + }, + RunE: func(cmd *cobra.Command, args []string) error { + return run() + }, + } +) + +var AppRouters = make([]func(), 0) + +func init() { + StartCmd.PersistentFlags().StringVarP(&configYml, "config", "c", "config/settings.yml", "Start server with provided configuration file") + StartCmd.PersistentFlags().BoolVarP(&apiCheck, "api", "a", false, "Start server with check api data") + + //注册路由 fixme 其他应用的路由,在本目录新建文件放在init方法 + AppRouters = append(AppRouters, router.InitRouter) +} + +func setup() { + // 注入配置扩展项 + config.ExtendConfig = &ext.ExtConfig + //1. 读取配置 + config.Setup( + file.NewSource(file.WithPath(configYml)), + database.Setup, + storage.Setup, + ) + //注册监听函数 + queue := sdk.Runtime.GetMemoryQueue("") + queue.Register(global.LoginLog, models.SaveLoginLog) + queue.Register(global.OperateLog, models.SaveOperaLog) + queue.Register(global.ApiCheck, models.SaveSysApi) + go queue.Run() + + usageStr := `starting api server...` + log.Println(usageStr) +} + +func run() error { + if config.ApplicationConfig.Mode == pkg.ModeProd.String() { + gin.SetMode(gin.ReleaseMode) + } + initRouter() + dbs := sdk.Runtime.GetDb() + var db *gorm.DB + + for _, item := range dbs { + db = item + break + } + for _, f := range AppRouters { + f() + } + + //初始化 status code 描述 + sysstatuscode.InitStatusCodeLanguage(db) + + //初始redis 链接 + helper.InitDefaultRedis(ext.ExtConfig.Redis.Addr, ext.ExtConfig.Redis.Password, ext.ExtConfig.Redis.Db) + helper.InitLockRedisConn(ext.ExtConfig.Redis.Addr, ext.ExtConfig.Redis.Password, utility.IntToString(ext.ExtConfig.Redis.Db)) + + //初始化雪花算法配置 + err := helper.InitSnowflakeNode() + if err != nil { + log.Printf("InitSnowflakeNode error, %s \n", err.Error()) + } + + //业务需要配置-初始化 + serverinit.BusinessInit(db) + + srv := &http.Server{ + Addr: fmt.Sprintf("%s:%d", config.ApplicationConfig.Host, config.ApplicationConfig.Port), + Handler: sdk.Runtime.GetEngine(), + } + + go func() { + jobs.InitJob() + jobs.Setup(sdk.Runtime.GetDb()) + + }() + + if apiCheck { + var routers = sdk.Runtime.GetRouter() + q := sdk.Runtime.GetMemoryQueue("") + mp := make(map[string]interface{}) + mp["List"] = routers + message, err := sdk.Runtime.GetStreamMessage("", global.ApiCheck, mp) + if err != nil { + log.Printf("GetStreamMessage error, %s \n", err.Error()) + //日志报错错误,不中断请求 + } else { + err = q.Append(message) + if err != nil { + log.Printf("Append message error, %s \n", err.Error()) + } + } + } + + go func() { + // 服务连接 + if config.SslConfig.Enable { + if err := srv.ListenAndServeTLS(config.SslConfig.Pem, config.SslConfig.KeyStr); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Fatal("listen: ", err) + } + } else { + if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Fatal("listen: ", err) + } + } + }() + fmt.Println(pkg.Red(string(global.LogoContent))) + tip() + fmt.Println(pkg.Green("Server run at:")) + fmt.Printf("- Local: %s://localhost:%d/ \r\n", "http", config.ApplicationConfig.Port) + fmt.Printf("- Network: %s://%s:%d/ \r\n", "http", pkg.GetLocaHonst(), config.ApplicationConfig.Port) + fmt.Println(pkg.Green("Swagger run at:")) + fmt.Printf("- Local: http://localhost:%d/swagger/admin/index.html \r\n", config.ApplicationConfig.Port) + fmt.Printf("- Network: %s://%s:%d/swagger/admin/index.html \r\n", "http", pkg.GetLocaHonst(), config.ApplicationConfig.Port) + fmt.Printf("%s Enter Control + C Shutdown Server \r\n", pkg.GetCurrentTimeStr()) + // 等待中断信号以优雅地关闭服务器(设置 5 秒的超时时间) + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + fmt.Printf("%s Shutdown Server ... \r\n", pkg.GetCurrentTimeStr()) + + if err := srv.Shutdown(ctx); err != nil { + log.Fatal("Server Shutdown:", err) + } + log.Println("Server exiting") + + return nil +} + +//var Router runtime.Router + +func tip() { + usageStr := `欢迎使用 ` + pkg.Green(`go-admin `+global.Version) + ` 可以使用 ` + pkg.Red(`-h`) + ` 查看命令` + fmt.Printf("%s \n\n", usageStr) +} + +func initRouter() { + var r *gin.Engine + h := sdk.Runtime.GetEngine() + if h == nil { + h = gin.New() + sdk.Runtime.SetEngine(h) + } + switch h.(type) { + case *gin.Engine: + r = h.(*gin.Engine) + default: + log.Fatal("not support other engine") + //os.Exit(-1) + } + if config.SslConfig.Enable { + r.Use(handler.TlsHandler()) + } + //r.Use(middleware.Metrics()) + r.Use(common.Sentinel()). + Use(common.RequestId(pkg.TrafficKey)). + Use(api.SetRequestLogger) + + common.InitMiddleware(r) + +} diff --git a/cmd/app/server.go b/cmd/app/server.go new file mode 100644 index 0000000..013ac38 --- /dev/null +++ b/cmd/app/server.go @@ -0,0 +1,90 @@ +package app + +import ( + "bytes" + "errors" + "fmt" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/utils" + "github.com/spf13/cobra" + "text/template" +) + +var ( + appName string + StartCmd = &cobra.Command{ + Use: "app", + Short: "Create a new app", + Long: "Use when you need to create a new app", + Example: "go-admin app -n admin", + Run: func(cmd *cobra.Command, args []string) { + run() + }, + } +) + +func init() { + StartCmd.PersistentFlags().StringVarP(&appName, "name", "n", "", "Start server with provided configuration file") +} + +func run() { + + fmt.Println(`start init`) + //1. 读取配置 + + fmt.Println(`generate migration file`) + _ = genFile() + +} + +func genFile() error { + if appName == "" { + return errors.New("arg `name` invalid :name is empty") + } + path := "app/" + appPath := path + appName + err := utils.IsNotExistMkDir(appPath) + if err != nil { + return err + } + apiPath := appPath + "/apis/" + err = utils.IsNotExistMkDir(apiPath) + if err != nil { + return err + } + modelsPath := appPath + "/models/" + err = utils.IsNotExistMkDir(modelsPath) + if err != nil { + return err + } + routerPath := appPath + "/router/" + err = utils.IsNotExistMkDir(routerPath) + if err != nil { + return err + } + servicePath := appPath + "/service/" + err = utils.IsNotExistMkDir(servicePath) + if err != nil { + return err + } + dtoPath := appPath + "/service/dto/" + err = utils.IsNotExistMkDir(dtoPath) + if err != nil { + return err + } + + t1, err := template.ParseFiles("template/cmd_api.template") + if err != nil { + return err + } + m := map[string]string{} + m["appName"] = appName + var b1 bytes.Buffer + err = t1.Execute(&b1, m) + pkg.FileCreate(b1, "./cmd/api/"+appName+".go") + t2, err := template.ParseFiles("template/router.template") + var b2 bytes.Buffer + err = t2.Execute(&b2, nil) + pkg.FileCreate(b2, appPath+"/router/router.go") + return nil +} diff --git a/cmd/cobra.go b/cmd/cobra.go new file mode 100644 index 0000000..6ac1c03 --- /dev/null +++ b/cmd/cobra.go @@ -0,0 +1,60 @@ +package cmd + +import ( + "errors" + "fmt" + "go-admin/cmd/app" + "go-admin/cmd/usersubscribe" + "go-admin/common/global" + "os" + + "github.com/go-admin-team/go-admin-core/sdk/pkg" + + "github.com/spf13/cobra" + + "go-admin/cmd/api" + "go-admin/cmd/config" + "go-admin/cmd/migrate" + "go-admin/cmd/version" +) + +var rootCmd = &cobra.Command{ + Use: "go-admin", + Short: "go-admin", + SilenceUsage: true, + Long: `go-admin`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + tip() + return errors.New(pkg.Red("requires at least one arg")) + } + return nil + }, + PersistentPreRunE: func(*cobra.Command, []string) error { return nil }, + Run: func(cmd *cobra.Command, args []string) { + tip() + }, +} + +func tip() { + usageStr := `欢迎使用 ` + pkg.Green(`go-admin `+global.Version) + ` 可以使用 ` + pkg.Red(`-h`) + ` 查看命令` + usageStr1 := `也可以参考 https://doc.go-admin.dev/guide/ksks 的相关内容` + fmt.Printf("%s\n", usageStr) + fmt.Printf("%s\n", usageStr1) +} + +func init() { + rootCmd.AddCommand(api.StartCmd) + rootCmd.AddCommand(usersubscribe.StartCmd) + rootCmd.AddCommand(migrate.StartCmd) + rootCmd.AddCommand(version.StartCmd) + rootCmd.AddCommand(config.StartCmd) + rootCmd.AddCommand(app.StartCmd) +} + +// Execute : apply commands +func Execute() { + if err := rootCmd.Execute(); err != nil { + os.Exit(-1) + } +} diff --git a/cmd/config/server.go b/cmd/config/server.go new file mode 100644 index 0000000..52ce85b --- /dev/null +++ b/cmd/config/server.go @@ -0,0 +1,63 @@ +package config + +import ( + "encoding/json" + "fmt" + + "github.com/go-admin-team/go-admin-core/config/source/file" + "github.com/spf13/cobra" + + "github.com/go-admin-team/go-admin-core/sdk/config" +) + +var ( + configYml string + StartCmd = &cobra.Command{ + Use: "config", + Short: "Get Application config info", + Example: "go-admin config -c config/settings.yml", + Run: func(cmd *cobra.Command, args []string) { + run() + }, + } +) + +func init() { + StartCmd.PersistentFlags().StringVarP(&configYml, "config", "c", "config/settings.yml", "Start server with provided configuration file") +} + +func run() { + config.Setup(file.NewSource(file.WithPath(configYml))) + + application, errs := json.MarshalIndent(config.ApplicationConfig, "", " ") //转换成JSON返回的是byte[] + if errs != nil { + fmt.Println(errs.Error()) + } + fmt.Println("application:", string(application)) + + jwt, errs := json.MarshalIndent(config.JwtConfig, "", " ") //转换成JSON返回的是byte[] + if errs != nil { + fmt.Println(errs.Error()) + } + fmt.Println("jwt:", string(jwt)) + + // todo 需要兼容 + database, errs := json.MarshalIndent(config.DatabasesConfig, "", " ") //转换成JSON返回的是byte[] + if errs != nil { + fmt.Println(errs.Error()) + } + fmt.Println("database:", string(database)) + + gen, errs := json.MarshalIndent(config.GenConfig, "", " ") //转换成JSON返回的是byte[] + if errs != nil { + fmt.Println(errs.Error()) + } + fmt.Println("gen:", string(gen)) + + loggerConfig, errs := json.MarshalIndent(config.LoggerConfig, "", " ") //转换成JSON返回的是byte[] + if errs != nil { + fmt.Println(errs.Error()) + } + fmt.Println("logger:", string(loggerConfig)) + +} diff --git a/cmd/migrate/migration/init.go b/cmd/migrate/migration/init.go new file mode 100644 index 0000000..739e6b4 --- /dev/null +++ b/cmd/migrate/migration/init.go @@ -0,0 +1,66 @@ +package migration + +import ( + "log" + "path/filepath" + "sort" + "sync" + + "gorm.io/gorm" +) + +var Migrate = &Migration{ + version: make(map[string]func(db *gorm.DB, version string) error), +} + +type Migration struct { + db *gorm.DB + version map[string]func(db *gorm.DB, version string) error + mutex sync.Mutex +} + +func (e *Migration) GetDb() *gorm.DB { + return e.db +} + +func (e *Migration) SetDb(db *gorm.DB) { + e.db = db +} + +func (e *Migration) SetVersion(k string, f func(db *gorm.DB, version string) error) { + e.mutex.Lock() + defer e.mutex.Unlock() + e.version[k] = f +} + +func (e *Migration) Migrate() { + versions := make([]string, 0) + for k := range e.version { + versions = append(versions, k) + } + if !sort.StringsAreSorted(versions) { + sort.Strings(versions) + } + var err error + var count int64 + for _, v := range versions { + err = e.db.Table("sys_migration").Where("version = ?", v).Count(&count).Error + if err != nil { + log.Fatalln(err) + } + if count > 0 { + log.Println(count) + count = 0 + continue + } + err = (e.version[v])(e.db.Debug(), v) + if err != nil { + log.Fatalln(err) + } + } +} + +func GetFilename(s string) string { + s = filepath.Base(s) + return s[:13] +} diff --git a/cmd/migrate/migration/models/by.go b/cmd/migrate/migration/models/by.go new file mode 100644 index 0000000..c9dd906 --- /dev/null +++ b/cmd/migrate/migration/models/by.go @@ -0,0 +1,22 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +type ControlBy struct { + CreateBy int `json:"createBy" gorm:"index;comment:创建者"` + UpdateBy int `json:"updateBy" gorm:"index;comment:更新者"` +} + +type Model struct { + Id int `json:"id" gorm:"primaryKey;autoIncrement;comment:主键编码"` +} + +type ModelTime struct { + CreatedAt time.Time `json:"createdAt" gorm:"comment:创建时间"` + UpdatedAt time.Time `json:"updatedAt" gorm:"comment:最后更新时间"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index;comment:删除时间"` +} diff --git a/cmd/migrate/migration/models/casbin_rule.go b/cmd/migrate/migration/models/casbin_rule.go new file mode 100644 index 0000000..bab7203 --- /dev/null +++ b/cmd/migrate/migration/models/casbin_rule.go @@ -0,0 +1,17 @@ +package models + +// CasbinRule sys_casbin_rule +type CasbinRule struct { + ID uint `gorm:"primaryKey;autoIncrement"` + Ptype string `gorm:"size:512;uniqueIndex:unique_index"` + V0 string `gorm:"size:512;uniqueIndex:unique_index"` + V1 string `gorm:"size:512;uniqueIndex:unique_index"` + V2 string `gorm:"size:512;uniqueIndex:unique_index"` + V3 string `gorm:"size:512;uniqueIndex:unique_index"` + V4 string `gorm:"size:512;uniqueIndex:unique_index"` + V5 string `gorm:"size:512;uniqueIndex:unique_index"` +} + +func (CasbinRule) TableName() string { + return "sys_casbin_rule" +} diff --git a/cmd/migrate/migration/models/initdb.go b/cmd/migrate/migration/models/initdb.go new file mode 100644 index 0000000..ba7f4ca --- /dev/null +++ b/cmd/migrate/migration/models/initdb.go @@ -0,0 +1,72 @@ +package models + +import ( + "fmt" + "go-admin/common/global" + "io/ioutil" + "log" + "strings" + + "gorm.io/gorm" +) + +func InitDb(db *gorm.DB) (err error) { + filePath := "config/db.sql" + if global.Driver == "postgres" { + filePath := "config/db.sql" + if err = ExecSql(db, filePath); err != nil { + return err + } + filePath = "config/pg.sql" + err = ExecSql(db, filePath) + } else if global.Driver == "mysql" { + filePath = "config/db-begin-mysql.sql" + if err = ExecSql(db, filePath); err != nil { + return err + } + filePath = "config/db.sql" + if err = ExecSql(db, filePath); err != nil { + return err + } + filePath = "config/db-end-mysql.sql" + err = ExecSql(db, filePath) + } else { + err = ExecSql(db, filePath) + } + return err +} + +func ExecSql(db *gorm.DB, filePath string) error { + sql, err := Ioutil(filePath) + if err != nil { + fmt.Println("数据库基础数据初始化脚本读取失败!原因:", err.Error()) + return err + } + sqlList := strings.Split(sql, ";") + for i := 0; i < len(sqlList)-1; i++ { + if strings.Contains(sqlList[i], "--") { + fmt.Println(sqlList[i]) + continue + } + sql := strings.Replace(sqlList[i]+";", "\n", "", -1) + sql = strings.TrimSpace(sql) + if err = db.Exec(sql).Error; err != nil { + log.Printf("error sql: %s", sql) + if !strings.Contains(err.Error(), "Query was empty") { + return err + } + } + } + return nil +} + +func Ioutil(filePath string) (string, error) { + if contents, err := ioutil.ReadFile(filePath); err == nil { + //因为contents是[]byte类型,直接转换成string类型后会多一行空格,需要使用strings.Replace替换换行符 + result := strings.Replace(string(contents), "\n", "", 1) + fmt.Println("Use ioutil.ReadFile to read a file:", result) + return result, nil + } else { + return "", err + } +} diff --git a/cmd/migrate/migration/models/model.go b/cmd/migrate/migration/models/model.go new file mode 100644 index 0000000..4a1c439 --- /dev/null +++ b/cmd/migrate/migration/models/model.go @@ -0,0 +1,11 @@ +package models + +import ( + "time" +) + +type BaseModel struct { + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt *time.Time `json:"deletedAt"` +} diff --git a/cmd/migrate/migration/models/role_dept.go b/cmd/migrate/migration/models/role_dept.go new file mode 100644 index 0000000..0aae705 --- /dev/null +++ b/cmd/migrate/migration/models/role_dept.go @@ -0,0 +1,10 @@ +package models + +type SysRoleDept struct { + RoleId int `gorm:"size:11;primaryKey"` + DeptId int `gorm:"size:11;primaryKey"` +} + +func (SysRoleDept) TableName() string { + return "sys_role_dept" +} diff --git a/cmd/migrate/migration/models/sys_api.go b/cmd/migrate/migration/models/sys_api.go new file mode 100644 index 0000000..8d75170 --- /dev/null +++ b/cmd/migrate/migration/models/sys_api.go @@ -0,0 +1,16 @@ +package models + +type SysApi struct { + Id int `json:"id" gorm:"primaryKey;autoIncrement;comment:主键编码"` + Handle string `json:"handle" gorm:"size:128;comment:handle"` + Title string `json:"title" gorm:"size:128;comment:标题"` + Path string `json:"path" gorm:"size:128;comment:地址"` + Type string `json:"type" gorm:"size:16;comment:接口类型"` + Action string `json:"action" gorm:"size:16;comment:请求类型"` + ModelTime + ControlBy +} + +func (SysApi) TableName() string { + return "sys_api" +} \ No newline at end of file diff --git a/cmd/migrate/migration/models/sys_columns.go b/cmd/migrate/migration/models/sys_columns.go new file mode 100644 index 0000000..2c8377e --- /dev/null +++ b/cmd/migrate/migration/models/sys_columns.go @@ -0,0 +1,44 @@ +package models + +type SysColumns struct { + ColumnId int `gorm:"primaryKey;autoIncrement" json:"columnId"` + TableId int `gorm:"" json:"tableId"` + ColumnName string `gorm:"size:128;" json:"columnName"` + ColumnComment string `gorm:"column:column_comment;size:128;" json:"columnComment"` + ColumnType string `gorm:"column:column_type;size:128;" json:"columnType"` + GoType string `gorm:"column:go_type;size:128;" json:"goType"` + GoField string `gorm:"column:go_field;size:128;" json:"goField"` + JsonField string `gorm:"column:json_field;size:128;" json:"jsonField"` + IsPk string `gorm:"column:is_pk;size:4;" json:"isPk"` + IsIncrement string `gorm:"column:is_increment;size:4;" json:"isIncrement"` + IsRequired string `gorm:"column:is_required;size:4;" json:"isRequired"` + IsInsert string `gorm:"column:is_insert;size:4;" json:"isInsert"` + IsEdit string `gorm:"column:is_edit;size:4;" json:"isEdit"` + IsList string `gorm:"column:is_list;size:4;" json:"isList"` + IsQuery string `gorm:"column:is_query;size:4;" json:"isQuery"` + QueryType string `gorm:"column:query_type;size:128;" json:"queryType"` + HtmlType string `gorm:"column:html_type;size:128;" json:"htmlType"` + DictType string `gorm:"column:dict_type;size:128;" json:"dictType"` + Sort int `gorm:"column:sort;" json:"sort"` + List string `gorm:"column:list;size:1;" json:"list"` + Pk bool `gorm:"column:pk;size:1;" json:"pk"` + Required bool `gorm:"column:required;size:1;" json:"required"` + SuperColumn bool `gorm:"column:super_column;size:1;" json:"superColumn"` + UsableColumn bool `gorm:"column:usable_column;size:1;" json:"usableColumn"` + Increment bool `gorm:"column:increment;size:1;" json:"increment"` + Insert bool `gorm:"column:insert;size:1;" json:"insert"` + Edit bool `gorm:"column:edit;size:1;" json:"edit"` + Query bool `gorm:"column:query;size:1;" json:"query"` + Remark string `gorm:"column:remark;size:255;" json:"remark"` + FkTableName string `gorm:"" json:"fkTableName"` + FkTableNameClass string `gorm:"" json:"fkTableNameClass"` + FkTableNamePackage string `gorm:"" json:"fkTableNamePackage"` + FkLabelId string `gorm:"" json:"fkLabelId"` + FkLabelName string `gorm:"size:255;" json:"fkLabelName"` + ModelTime + ControlBy +} + +func (SysColumns) TableName() string { + return "sys_columns" +} diff --git a/cmd/migrate/migration/models/sys_config.go b/cmd/migrate/migration/models/sys_config.go new file mode 100644 index 0000000..bd79da3 --- /dev/null +++ b/cmd/migrate/migration/models/sys_config.go @@ -0,0 +1,17 @@ +package models + +type SysConfig struct { + Model + ConfigName string `json:"configName" gorm:"type:varchar(128);comment:ConfigName"` + ConfigKey string `json:"configKey" gorm:"type:varchar(128);comment:ConfigKey"` + ConfigValue string `json:"configValue" gorm:"type:varchar(255);comment:ConfigValue"` + ConfigType string `json:"configType" gorm:"type:varchar(64);comment:ConfigType"` + IsFrontend int `json:"isFrontend" gorm:"type:varchar(64);comment:是否前台"` + Remark string `json:"remark" gorm:"type:varchar(128);comment:Remark"` + ControlBy + ModelTime +} + +func (SysConfig) TableName() string { + return "sys_config" +} diff --git a/cmd/migrate/migration/models/sys_dept.go b/cmd/migrate/migration/models/sys_dept.go new file mode 100644 index 0000000..5f74c62 --- /dev/null +++ b/cmd/migrate/migration/models/sys_dept.go @@ -0,0 +1,19 @@ +package models + +type SysDept struct { + DeptId int `json:"deptId" gorm:"primaryKey;autoIncrement;"` //部门编码 + ParentId int `json:"parentId" gorm:""` //上级部门 + DeptPath string `json:"deptPath" gorm:"size:255;"` // + DeptName string `json:"deptName" gorm:"size:128;"` //部门名称 + Sort int `json:"sort" gorm:"size:4;"` //排序 + Leader string `json:"leader" gorm:"size:128;"` //负责人 + Phone string `json:"phone" gorm:"size:11;"` //手机 + Email string `json:"email" gorm:"size:64;"` //邮箱 + Status int `json:"status" gorm:"size:4;"` //状态 + ControlBy + ModelTime +} + +func (SysDept) TableName() string { + return "sys_dept" +} diff --git a/cmd/migrate/migration/models/sys_dict_data.go b/cmd/migrate/migration/models/sys_dict_data.go new file mode 100644 index 0000000..c2936c8 --- /dev/null +++ b/cmd/migrate/migration/models/sys_dict_data.go @@ -0,0 +1,21 @@ +package models + +type DictData struct { + DictCode int `gorm:"primaryKey;autoIncrement;" json:"dictCode" example:"1"` //字典编码 + DictSort int `gorm:"" json:"dictSort"` //显示顺序 + DictLabel string `gorm:"size:128;" json:"dictLabel"` //数据标签 + DictValue string `gorm:"size:255;" json:"dictValue"` //数据键值 + DictType string `gorm:"size:64;" json:"dictType"` //字典类型 + CssClass string `gorm:"size:128;" json:"cssClass"` // + ListClass string `gorm:"size:128;" json:"listClass"` // + IsDefault string `gorm:"size:8;" json:"isDefault"` // + Status int `gorm:"size:4;" json:"status"` //状态 + Default string `gorm:"size:8;" json:"default"` // + Remark string `gorm:"size:255;" json:"remark"` //备注 + ControlBy + ModelTime +} + +func (DictData) TableName() string { + return "sys_dict_data" +} diff --git a/cmd/migrate/migration/models/sys_dict_type.go b/cmd/migrate/migration/models/sys_dict_type.go new file mode 100644 index 0000000..4c79e37 --- /dev/null +++ b/cmd/migrate/migration/models/sys_dict_type.go @@ -0,0 +1,15 @@ +package models + +type DictType struct { + DictId int `gorm:"primaryKey;autoIncrement;" json:"dictId"` + DictName string `gorm:"size:128;" json:"dictName"` //字典名称 + DictType string `gorm:"size:128;" json:"dictType"` //字典类型 + Status int `gorm:"size:4;" json:"status"` //状态 + Remark string `gorm:"size:255;" json:"remark"` //备注 + ControlBy + ModelTime +} + +func (DictType) TableName() string { + return "sys_dict_type" +} diff --git a/cmd/migrate/migration/models/sys_job.go b/cmd/migrate/migration/models/sys_job.go new file mode 100644 index 0000000..e826d10 --- /dev/null +++ b/cmd/migrate/migration/models/sys_job.go @@ -0,0 +1,21 @@ +package models + +type SysJob struct { + JobId int `json:"jobId" gorm:"primaryKey;autoIncrement"` // 编码 + JobName string `json:"jobName" gorm:"size:255;"` // 名称 + JobGroup string `json:"jobGroup" gorm:"size:255;"` // 任务分组 + JobType int `json:"jobType" gorm:"size:1;"` // 任务类型 + CronExpression string `json:"cronExpression" gorm:"size:255;"` // cron表达式 + InvokeTarget string `json:"invokeTarget" gorm:"size:255;"` // 调用目标 + Args string `json:"args" gorm:"size:255;"` // 目标参数 + MisfirePolicy int `json:"misfirePolicy" gorm:"size:255;"` // 执行策略 + Concurrent int `json:"concurrent" gorm:"size:1;"` // 是否并发 + Status int `json:"status" gorm:"size:1;"` // 状态 + EntryId int `json:"entry_id" gorm:"size:11;"` // job启动时返回的id + ModelTime + ControlBy +} + +func (SysJob) TableName() string { + return "sys_job" +} diff --git a/cmd/migrate/migration/models/sys_login_log.go b/cmd/migrate/migration/models/sys_login_log.go new file mode 100644 index 0000000..f4a878b --- /dev/null +++ b/cmd/migrate/migration/models/sys_login_log.go @@ -0,0 +1,26 @@ +package models + +import ( + "time" +) + +type SysLoginLog struct { + Model + Username string `json:"username" gorm:"type:varchar(128);comment:用户名"` + Status string `json:"status" gorm:"type:varchar(4);comment:状态"` + Ipaddr string `json:"ipaddr" gorm:"type:varchar(255);comment:ip地址"` + LoginLocation string `json:"loginLocation" gorm:"type:varchar(255);comment:归属地"` + Browser string `json:"browser" gorm:"type:varchar(255);comment:浏览器"` + Os string `json:"os" gorm:"type:varchar(255);comment:系统"` + Platform string `json:"platform" gorm:"type:varchar(255);comment:固件"` + LoginTime time.Time `json:"loginTime" gorm:"type:timestamp;comment:登录时间"` + Remark string `json:"remark" gorm:"type:varchar(255);comment:备注"` + Msg string `json:"msg" gorm:"type:varchar(255);comment:信息"` + CreatedAt time.Time `json:"createdAt" gorm:"comment:创建时间"` + UpdatedAt time.Time `json:"updatedAt" gorm:"comment:最后更新时间"` + ControlBy +} + +func (SysLoginLog) TableName() string { + return "sys_login_log" +} diff --git a/cmd/migrate/migration/models/sys_menu.go b/cmd/migrate/migration/models/sys_menu.go new file mode 100644 index 0000000..c69252f --- /dev/null +++ b/cmd/migrate/migration/models/sys_menu.go @@ -0,0 +1,27 @@ +package models + +type SysMenu struct { + MenuId int `json:"menuId" gorm:"primaryKey;autoIncrement"` + MenuName string `json:"menuName" gorm:"size:128;"` + Title string `json:"title" gorm:"size:128;"` + Icon string `json:"icon" gorm:"size:128;"` + Path string `json:"path" gorm:"size:128;"` + Paths string `json:"paths" gorm:"size:128;"` + MenuType string `json:"menuType" gorm:"size:1;"` + Action string `json:"action" gorm:"size:16;"` + Permission string `json:"permission" gorm:"size:255;"` + ParentId int `json:"parentId" gorm:"size:11;"` + NoCache bool `json:"noCache" gorm:"size:8;"` + Breadcrumb string `json:"breadcrumb" gorm:"size:255;"` + Component string `json:"component" gorm:"size:255;"` + Sort int `json:"sort" gorm:"size:4;"` + Visible string `json:"visible" gorm:"size:1;"` + IsFrame string `json:"isFrame" gorm:"size:1;DEFAULT:0;"` + SysApi []SysApi `json:"sysApi" gorm:"many2many:sys_menu_api_rule"` + ControlBy + ModelTime +} + +func (SysMenu) TableName() string { + return "sys_menu" +} \ No newline at end of file diff --git a/cmd/migrate/migration/models/sys_opera_log.go b/cmd/migrate/migration/models/sys_opera_log.go new file mode 100644 index 0000000..5055b96 --- /dev/null +++ b/cmd/migrate/migration/models/sys_opera_log.go @@ -0,0 +1,34 @@ +package models + +import ( + "time" +) + +type SysOperaLog struct { + Model + Title string `json:"title" gorm:"type:varchar(255);comment:操作模块"` + BusinessType string `json:"businessType" gorm:"type:varchar(128);comment:操作类型"` + BusinessTypes string `json:"businessTypes" gorm:"type:varchar(128);comment:BusinessTypes"` + Method string `json:"method" gorm:"type:varchar(128);comment:函数"` + RequestMethod string `json:"requestMethod" gorm:"type:varchar(128);comment:请求方式: GET POST PUT DELETE"` + OperatorType string `json:"operatorType" gorm:"type:varchar(128);comment:操作类型"` + OperName string `json:"operName" gorm:"type:varchar(128);comment:操作者"` + DeptName string `json:"deptName" gorm:"type:varchar(128);comment:部门名称"` + OperUrl string `json:"operUrl" gorm:"type:varchar(255);comment:访问地址"` + OperIp string `json:"operIp" gorm:"type:varchar(128);comment:客户端ip"` + OperLocation string `json:"operLocation" gorm:"type:varchar(128);comment:访问位置"` + OperParam string `json:"operParam" gorm:"type:text;comment:请求参数"` + Status string `json:"status" gorm:"type:varchar(4);comment:操作状态 1:正常 2:关闭"` + OperTime time.Time `json:"operTime" gorm:"type:timestamp;comment:操作时间"` + JsonResult string `json:"jsonResult" gorm:"type:varchar(255);comment:返回数据"` + Remark string `json:"remark" gorm:"type:varchar(255);comment:备注"` + LatencyTime string `json:"latencyTime" gorm:"type:varchar(128);comment:耗时"` + UserAgent string `json:"userAgent" gorm:"type:varchar(255);comment:ua"` + CreatedAt time.Time `json:"createdAt" gorm:"comment:创建时间"` + UpdatedAt time.Time `json:"updatedAt" gorm:"comment:最后更新时间"` + ControlBy +} + +func (SysOperaLog) TableName() string { + return "sys_opera_log" +} diff --git a/cmd/migrate/migration/models/sys_post.go b/cmd/migrate/migration/models/sys_post.go new file mode 100644 index 0000000..195208a --- /dev/null +++ b/cmd/migrate/migration/models/sys_post.go @@ -0,0 +1,16 @@ +package models + +type SysPost struct { + PostId int `gorm:"primaryKey;autoIncrement" json:"postId"` //岗位编号 + PostName string `gorm:"size:128;" json:"postName"` //岗位名称 + PostCode string `gorm:"size:128;" json:"postCode"` //岗位代码 + Sort int `gorm:"size:4;" json:"sort"` //岗位排序 + Status int `gorm:"size:4;" json:"status"` //状态 + Remark string `gorm:"size:255;" json:"remark"` //描述 + ControlBy + ModelTime +} + +func (SysPost) TableName() string { + return "sys_post" +} \ No newline at end of file diff --git a/cmd/migrate/migration/models/sys_role.go b/cmd/migrate/migration/models/sys_role.go new file mode 100644 index 0000000..0bd5ee3 --- /dev/null +++ b/cmd/migrate/migration/models/sys_role.go @@ -0,0 +1,20 @@ +package models + +type SysRole struct { + RoleId int `json:"roleId" gorm:"primaryKey;autoIncrement"` // 角色编码 + RoleName string `json:"roleName" gorm:"size:128;"` // 角色名称 + Status string `json:"status" gorm:"size:4;"` // + RoleKey string `json:"roleKey" gorm:"size:128;"` //角色代码 + RoleSort int `json:"roleSort" gorm:""` //角色排序 + Flag string `json:"flag" gorm:"size:128;"` // + Remark string `json:"remark" gorm:"size:255;"` //备注 + Admin bool `json:"admin" gorm:"size:4;"` + DataScope string `json:"dataScope" gorm:"size:128;"` + SysMenu []SysMenu `json:"sysMenu" gorm:"many2many:sys_role_menu;foreignKey:RoleId;joinForeignKey:role_id;references:MenuId;joinReferences:menu_id;"` + ControlBy + ModelTime +} + +func (SysRole) TableName() string { + return "sys_role" +} \ No newline at end of file diff --git a/cmd/migrate/migration/models/sys_tables.go b/cmd/migrate/migration/models/sys_tables.go new file mode 100644 index 0000000..0e53032 --- /dev/null +++ b/cmd/migrate/migration/models/sys_tables.go @@ -0,0 +1,37 @@ +package models + +type SysTables struct { + TableId int `gorm:"primaryKey;autoIncrement" json:"tableId"` //表编码 + TBName string `gorm:"column:table_name;size:255;" json:"tableName"` //表名称 + TableComment string `gorm:"size:255;" json:"tableComment"` //表备注 + ClassName string `gorm:"size:255;" json:"className"` //类名 + TplCategory string `gorm:"size:255;" json:"tplCategory"` // + PackageName string `gorm:"size:255;" json:"packageName"` //包名 + ModuleName string `gorm:"size:255;" json:"moduleName"` //go文件名 + ModuleFrontName string `gorm:"size:255;comment:前端文件名;" json:"moduleFrontName"` //前端文件名 + BusinessName string `gorm:"size:255;" json:"businessName"` // + FunctionName string `gorm:"size:255;" json:"functionName"` //功能名称 + FunctionAuthor string `gorm:"size:255;" json:"functionAuthor"` //功能作者 + PkColumn string `gorm:"size:255;" json:"pkColumn"` + PkGoField string `gorm:"size:255;" json:"pkGoField"` + PkJsonField string `gorm:"size:255;" json:"pkJsonField"` + Options string `gorm:"size:255;" json:"options"` + TreeCode string `gorm:"size:255;" json:"treeCode"` + TreeParentCode string `gorm:"size:255;" json:"treeParentCode"` + TreeName string `gorm:"size:255;" json:"treeName"` + Tree bool `gorm:"size:1;default:0;" json:"tree"` + Crud bool `gorm:"size:1;default:1;" json:"crud"` + Remark string `gorm:"size:255;" json:"remark"` + IsDataScope int `gorm:"size:1;" json:"isDataScope"` + IsActions int `gorm:"size:1;" json:"isActions"` + IsAuth int `gorm:"size:1;" json:"isAuth"` + IsLogicalDelete string `gorm:"size:1;" json:"isLogicalDelete"` + LogicalDelete bool `gorm:"size:1;" json:"logicalDelete"` + LogicalDeleteColumn string `gorm:"size:128;" json:"logicalDeleteColumn"` + ModelTime + ControlBy +} + +func (SysTables) TableName() string { + return "sys_tables" +} diff --git a/cmd/migrate/migration/models/sys_user.go b/cmd/migrate/migration/models/sys_user.go new file mode 100644 index 0000000..31e3637 --- /dev/null +++ b/cmd/migrate/migration/models/sys_user.go @@ -0,0 +1,48 @@ +package models + +import ( + "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" +) + +type SysUser struct { + UserId int `gorm:"primaryKey;autoIncrement;comment:编码" json:"userId"` + Username string `json:"username" gorm:"type:varchar(64);comment:用户名"` + Password string `json:"-" gorm:"type:varchar(128);comment:密码"` + NickName string `json:"nickName" gorm:"type:varchar(128);comment:昵称"` + Phone string `json:"phone" gorm:"type:varchar(11);comment:手机号"` + RoleId int `json:"roleId" gorm:"type:bigint;comment:角色ID"` + Salt string `json:"-" gorm:"type:varchar(255);comment:加盐"` + Avatar string `json:"avatar" gorm:"type:varchar(255);comment:头像"` + Sex string `json:"sex" gorm:"type:varchar(255);comment:性别"` + Email string `json:"email" gorm:"type:varchar(128);comment:邮箱"` + DeptId int `json:"deptId" gorm:"type:bigint;comment:部门"` + PostId int `json:"postId" gorm:"type:bigint;comment:岗位"` + Remark string `json:"remark" gorm:"type:varchar(255);comment:备注"` + Status string `json:"status" gorm:"type:varchar(4);comment:状态"` + ControlBy + ModelTime +} + +func (*SysUser) TableName() string { + return "sys_user" +} + +// Encrypt 加密 +func (e *SysUser) Encrypt() (err error) { + if e.Password == "" { + return + } + + var hash []byte + if hash, err = bcrypt.GenerateFromPassword([]byte(e.Password), bcrypt.DefaultCost); err != nil { + return + } else { + e.Password = string(hash) + return + } +} + +func (e *SysUser) BeforeCreate(_ *gorm.DB) error { + return e.Encrypt() +} diff --git a/cmd/migrate/migration/models/tb_demo.go b/cmd/migrate/migration/models/tb_demo.go new file mode 100644 index 0000000..7d30541 --- /dev/null +++ b/cmd/migrate/migration/models/tb_demo.go @@ -0,0 +1,12 @@ +package models + +type TbDemo struct { + Model + Name string `json:"name" gorm:"type:varchar(128);comment:名称"` + ModelTime + ControlBy +} + +func (TbDemo) TableName() string { + return "tb_demo" +} diff --git a/cmd/migrate/migration/version-local/doc.go b/cmd/migrate/migration/version-local/doc.go new file mode 100644 index 0000000..2cec8bc --- /dev/null +++ b/cmd/migrate/migration/version-local/doc.go @@ -0,0 +1,8 @@ +package version_local + +func init() { +} + +/** +开发者项目的迁移脚本放在这个目录里,init写法参考version目录里的migrate或者自动生成 +*/ diff --git a/cmd/migrate/migration/version/1599190683659_tables.go b/cmd/migrate/migration/version/1599190683659_tables.go new file mode 100644 index 0000000..df2dafa --- /dev/null +++ b/cmd/migrate/migration/version/1599190683659_tables.go @@ -0,0 +1,53 @@ +package version + +import ( + "github.com/go-admin-team/go-admin-core/sdk/config" + "runtime" + + "go-admin/cmd/migrate/migration" + "go-admin/cmd/migrate/migration/models" + common "go-admin/common/models" + + "gorm.io/gorm" +) + +func init() { + _, fileName, _, _ := runtime.Caller(0) + migration.Migrate.SetVersion(migration.GetFilename(fileName), _1599190683659Tables) +} + +func _1599190683659Tables(db *gorm.DB, version string) error { + return db.Transaction(func(tx *gorm.DB) error { + if config.DatabaseConfig.Driver == "mysql" { + tx = tx.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4") + } + err := tx.Migrator().AutoMigrate( + new(models.SysDept), + new(models.SysConfig), + new(models.SysTables), + new(models.SysColumns), + new(models.SysMenu), + new(models.SysLoginLog), + new(models.SysOperaLog), + new(models.SysRoleDept), + new(models.SysUser), + new(models.SysRole), + new(models.SysPost), + new(models.DictData), + new(models.DictType), + new(models.SysJob), + new(models.SysConfig), + new(models.SysApi), + new(models.TbDemo), + ) + if err != nil { + return err + } + if err := models.InitDb(tx); err != nil { + return err + } + return tx.Create(&common.Migration{ + Version: version, + }).Error + }) +} diff --git a/cmd/migrate/migration/version/1653638869132_migrate.go b/cmd/migrate/migration/version/1653638869132_migrate.go new file mode 100644 index 0000000..59a8fa0 --- /dev/null +++ b/cmd/migrate/migration/version/1653638869132_migrate.go @@ -0,0 +1,48 @@ +package version + +import ( + "go-admin/cmd/migrate/migration/models" + common "go-admin/common/models" + "gorm.io/gorm" + "runtime" + "strconv" + + "go-admin/cmd/migrate/migration" +) + +func init() { + _, fileName, _, _ := runtime.Caller(0) + migration.Migrate.SetVersion(migration.GetFilename(fileName), _1653638869132Test) +} + +func _1653638869132Test(db *gorm.DB, version string) error { + return db.Transaction(func(tx *gorm.DB) error { + var list []models.SysMenu + err := tx.Model(&models.SysMenu{}).Order("parent_id,menu_id").Find(&list).Error + if err != nil { + return err + } + for _, v := range list { + if v.ParentId == 0 { + v.Paths = "/0/" + strconv.Itoa(v.MenuId) + } else { + var e models.SysMenu + err = tx.Model(&models.SysMenu{}).Where("menu_id=?", v.ParentId).First(&e).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + continue + } + return err + } + v.Paths = e.Paths + "/" + strconv.Itoa(v.MenuId) + } + err = tx.Model(&v).Update("paths", v.Paths).Error + if err != nil { + return err + } + } + return tx.Create(&common.Migration{ + Version: version, + }).Error + }) +} diff --git a/cmd/migrate/server.go b/cmd/migrate/server.go new file mode 100644 index 0000000..b1667ca --- /dev/null +++ b/cmd/migrate/server.go @@ -0,0 +1,118 @@ +package migrate + +import ( + "bytes" + "fmt" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "strconv" + "text/template" + "time" + + "github.com/go-admin-team/go-admin-core/config/source/file" + "github.com/spf13/cobra" + + "github.com/go-admin-team/go-admin-core/sdk/config" + "go-admin/cmd/migrate/migration" + _ "go-admin/cmd/migrate/migration/version" + _ "go-admin/cmd/migrate/migration/version-local" + "go-admin/common/database" + "go-admin/common/models" +) + +var ( + configYml string + generate bool + goAdmin bool + host string + StartCmd = &cobra.Command{ + Use: "migrate", + Short: "Initialize the database", + Example: "go-admin migrate -c config/settings.yml", + Run: func(cmd *cobra.Command, args []string) { + run() + }, + } +) + +// fixme 在您看不见代码的时候运行迁移,我觉得是不安全的,所以编译后最好不要去执行迁移 +func init() { + StartCmd.PersistentFlags().StringVarP(&configYml, "config", "c", "config/settings.yml", "Start server with provided configuration file") + StartCmd.PersistentFlags().BoolVarP(&generate, "generate", "g", false, "generate migration file") + StartCmd.PersistentFlags().BoolVarP(&goAdmin, "goAdmin", "a", false, "generate go-admin migration file") + StartCmd.PersistentFlags().StringVarP(&host, "domain", "d", "*", "select tenant host") +} + +func run() { + + if !generate { + fmt.Println(`start init`) + //1. 读取配置 + config.Setup( + file.NewSource(file.WithPath(configYml)), + initDB, + ) + } else { + fmt.Println(`generate migration file`) + _ = genFile() + } +} + +func migrateModel() error { + if host == "" { + host = "*" + } + db := sdk.Runtime.GetDbByKey(host) + if db == nil { + if len(sdk.Runtime.GetDb()) == 1 && host == "*" { + for k, v := range sdk.Runtime.GetDb() { + db = v + host = k + break + } + } + } + if db == nil { + return fmt.Errorf("未找到数据库配置") + } + if config.DatabasesConfig[host].Driver == "mysql" { + //初始化数据库时候用 + db.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4") + } + err := db.Debug().AutoMigrate(&models.Migration{}) + if err != nil { + return err + } + migration.Migrate.SetDb(db.Debug()) + migration.Migrate.Migrate() + return err +} +func initDB() { + //3. 初始化数据库链接 + database.Setup() + //4. 数据库迁移 + fmt.Println("数据库迁移开始") + _ = migrateModel() + fmt.Println(`数据库基础数据初始化成功`) +} + +func genFile() error { + t1, err := template.ParseFiles("template/migrate.template") + if err != nil { + return err + } + m := map[string]string{} + m["GenerateTime"] = strconv.FormatInt(time.Now().UnixNano()/1e6, 10) + m["Package"] = "version_local" + if goAdmin { + m["Package"] = "version" + } + var b1 bytes.Buffer + err = t1.Execute(&b1, m) + if goAdmin { + pkg.FileCreate(b1, "./cmd/migrate/migration/version/"+m["GenerateTime"]+"_migrate.go") + } else { + pkg.FileCreate(b1, "./cmd/migrate/migration/version-local/"+m["GenerateTime"]+"_migrate.go") + } + return nil +} diff --git a/cmd/usersubscribe/usersubscribe.go b/cmd/usersubscribe/usersubscribe.go new file mode 100644 index 0000000..efb1da7 --- /dev/null +++ b/cmd/usersubscribe/usersubscribe.go @@ -0,0 +1,135 @@ +package usersubscribe + +import ( + "context" + "fmt" + "go-admin/app/admin/models" + "go-admin/common/database" + "go-admin/common/global" + "go-admin/common/helper" + "go-admin/common/storage" + "go-admin/config/serverinit" + "go-admin/pkg/utility" + "go-admin/services/fileservice" + "log" + "os" + "os/signal" + "strconv" + "time" + + ext "go-admin/config" + + "github.com/go-admin-team/go-admin-core/config/source/file" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/sdk/config" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/spf13/cobra" + "gorm.io/gorm" +) + +var ( + configYml string + StartCmd = &cobra.Command{ + Use: "usersubscribe", + Short: "Start usersubscribe server", + Example: "go-admin usersubscribe -c config/settings.yml", + SilenceUsage: true, + PreRun: func(cmd *cobra.Command, args []string) { + setup() + }, + RunE: func(cmd *cobra.Command, args []string) error { + return run() + }, + } +) + +func init() { + StartCmd.PersistentFlags().StringVarP(&configYml, "config", "c", "config/settings.yml", "Start server with provided configuration file") +} + +func setup() { + // 注入配置扩展项 + config.ExtendConfig = &ext.ExtConfig + //1. 读取配置 + config.Setup( + file.NewSource(file.WithPath(configYml)), + database.Setup, + storage.Setup, + ) + //注册监听函数 + queue := sdk.Runtime.GetMemoryQueue("") + queue.Register(global.LoginLog, models.SaveLoginLog) + queue.Register(global.OperateLog, models.SaveOperaLog) + // queue.Register(global.ApiCheck, models.SaveSysApi) + go queue.Run() + + usageStr := `starting user subscribe server...` + log.Println(usageStr) +} + +func run() error { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var db *gorm.DB + dbs := sdk.Runtime.GetDb() + + for _, item := range dbs { + db = item + break + } + + defaultInit(db, ctx) + + utility.SafeGo(func() { + // 启动定时任务 + clearLogJob(db, ctx) + }) + + // 等待中断信号以优雅地关闭服务器(设置 5 秒的超时时间) + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + + fmt.Printf("%s Shutdown Server ... \r\n", pkg.GetCurrentTimeStr()) + + // if err := srv.Shutdown(ctx); err != nil { + // log.Fatal("Server Shutdown:", err) + // } + log.Println("Server exiting") + + return nil +} + +func defaultInit(db *gorm.DB, ctx context.Context) { + + //初始化 默认redis + helper.InitDefaultRedis(ext.ExtConfig.Redis.Addr, ext.ExtConfig.Redis.Password, ext.ExtConfig.Redis.Db) + helper.InitLockRedisConn(ext.ExtConfig.Redis.Addr, ext.ExtConfig.Redis.Password, strconv.Itoa(ext.ExtConfig.Redis.Db)) + + err := helper.DefaultRedis.Ping() + + if err != nil { + log.Printf("初始化redis失败!请检查配置") + _, cancel := context.WithTimeout(context.Background(), 5*time.Second) + cancel() + } else { + log.Printf("redis初始化成功") + } + + //初始化连接 + serverinit.UserSubscribeInit(db, ctx) +} + +// 定时清理日志 +func clearLogJob(db *gorm.DB, ctx context.Context) { + ticker := time.NewTicker(time.Hour * 1) + defer ticker.Stop() + + select { + case <-ctx.Done(): + return + case <-ticker.C: + fileservice.ClearLogs(db) + } +} diff --git a/cmd/version/server.go b/cmd/version/server.go new file mode 100644 index 0000000..329e1ea --- /dev/null +++ b/cmd/version/server.go @@ -0,0 +1,26 @@ +package version + +import ( + "fmt" + "github.com/spf13/cobra" + "go-admin/common/global" +) + +var ( + StartCmd = &cobra.Command{ + Use: "version", + Short: "Get version info", + Example: "go-admin version", + PreRun: func(cmd *cobra.Command, args []string) { + + }, + RunE: func(cmd *cobra.Command, args []string) error { + return run() + }, + } +) + +func run() error { + fmt.Println(global.Version) + return nil +} diff --git a/common/actions/create.go b/common/actions/create.go new file mode 100644 index 0000000..81989e5 --- /dev/null +++ b/common/actions/create.go @@ -0,0 +1,49 @@ +package actions + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/common/dto" + "go-admin/common/models" +) + +// CreateAction 通用新增动作 +func CreateAction(control dto.Control) gin.HandlerFunc { + return func(c *gin.Context) { + log := api.GetRequestLogger(c) + db, err := pkg.GetOrm(c) + if err != nil { + log.Error(err) + return + } + + //新增操作 + req := control.Generate() + err = req.Bind(c) + if err != nil { + response.Error(c, http.StatusUnprocessableEntity, err, err.Error()) + return + } + var object models.ActiveRecord + object, err = req.GenerateM() + if err != nil { + response.Error(c, 500, err, "模型生成失败") + return + } + object.SetCreateBy(user.GetUserId(c)) + err = db.WithContext(c).Create(object).Error + if err != nil { + log.Errorf("Create error: %s", err) + response.Error(c, 500, err, "创建失败") + return + } + response.OK(c, object.GetId(), "创建成功") + c.Next() + } +} diff --git a/common/actions/delete.go b/common/actions/delete.go new file mode 100644 index 0000000..811b9bf --- /dev/null +++ b/common/actions/delete.go @@ -0,0 +1,61 @@ +package actions + +import ( + "net/http" + + "github.com/gin-gonic/gin" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/common/dto" + "go-admin/common/models" +) + +// DeleteAction 通用删除动作 +func DeleteAction(control dto.Control) gin.HandlerFunc { + return func(c *gin.Context) { + db, err := pkg.GetOrm(c) + if err != nil { + log.Error(err) + return + } + + msgID := pkg.GenerateMsgIDFromContext(c) + //删除操作 + req := control.Generate() + err = req.Bind(c) + if err != nil { + log.Errorf("MsgID[%s] Bind error: %s", msgID, err) + response.Error(c, http.StatusUnprocessableEntity, err, "参数验证失败") + return + } + var object models.ActiveRecord + object, err = req.GenerateM() + if err != nil { + response.Error(c, 500, err, "模型生成失败") + return + } + + object.SetUpdateBy(user.GetUserId(c)) + + //数据权限检查 + p := GetPermissionFromContext(c) + + db = db.WithContext(c).Scopes( + Permission(object.TableName(), p), + ).Where(req.GetId()).Delete(object) + if err = db.Error; err != nil { + log.Errorf("MsgID[%s] Delete error: %s", msgID, err) + response.Error(c, 500, err, "删除失败") + return + } + if db.RowsAffected == 0 { + response.Error(c, http.StatusForbidden, nil, "无权删除该数据") + return + } + response.OK(c, object.GetId(), "删除成功") + c.Next() + } +} diff --git a/common/actions/index.go b/common/actions/index.go new file mode 100644 index 0000000..4453094 --- /dev/null +++ b/common/actions/index.go @@ -0,0 +1,58 @@ +package actions + +import ( + "errors" + "net/http" + + "github.com/gin-gonic/gin" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "gorm.io/gorm" + + "go-admin/common/dto" + "go-admin/common/models" +) + +// IndexAction 通用查询动作 +func IndexAction(m models.ActiveRecord, d dto.Index, f func() interface{}) gin.HandlerFunc { + return func(c *gin.Context) { + db, err := pkg.GetOrm(c) + if err != nil { + log.Error(err) + return + } + + msgID := pkg.GenerateMsgIDFromContext(c) + list := f() + object := m.Generate() + req := d.Generate() + var count int64 + + //查询列表 + err = req.Bind(c) + if err != nil { + response.Error(c, http.StatusUnprocessableEntity, err, "参数验证失败") + return + } + + //数据权限检查 + p := GetPermissionFromContext(c) + + err = db.WithContext(c).Model(object). + Scopes( + dto.MakeCondition(req.GetNeedSearch()), + dto.Paginate(req.GetPageSize(), req.GetPageIndex()), + Permission(object.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(&count).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + log.Errorf("MsgID[%s] Index error: %s", msgID, err) + response.Error(c, 500, err, "查询失败") + return + } + response.PageOK(c, list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") + c.Next() + } +} diff --git a/common/actions/permission.go b/common/actions/permission.go new file mode 100644 index 0000000..82532c4 --- /dev/null +++ b/common/actions/permission.go @@ -0,0 +1,96 @@ +package actions + +import ( + "errors" + + "github.com/gin-gonic/gin" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/config" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "gorm.io/gorm" +) + +type DataPermission struct { + DataScope string + UserId int + DeptId int + RoleId int +} + +func PermissionAction() gin.HandlerFunc { + return func(c *gin.Context) { + db, err := pkg.GetOrm(c) + if err != nil { + log.Error(err) + return + } + + msgID := pkg.GenerateMsgIDFromContext(c) + var p = new(DataPermission) + if userId := user.GetUserIdStr(c); userId != "" { + p, err = newDataPermission(db, userId) + if err != nil { + log.Errorf("MsgID[%s] PermissionAction error: %s", msgID, err) + response.Error(c, 500, err, "权限范围鉴定错误") + c.Abort() + return + } + } + c.Set(PermissionKey, p) + c.Next() + } +} + +func newDataPermission(tx *gorm.DB, userId interface{}) (*DataPermission, error) { + var err error + p := &DataPermission{} + + err = tx.Table("sys_user"). + Select("sys_user.user_id", "sys_role.role_id", "sys_user.dept_id", "sys_role.data_scope"). + Joins("left join sys_role on sys_role.role_id = sys_user.role_id"). + Where("sys_user.user_id = ?", userId). + Scan(p).Error + if err != nil { + err = errors.New("获取用户数据出错 msg:" + err.Error()) + return nil, err + } + return p, nil +} + +func Permission(tableName string, p *DataPermission) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + if !config.ApplicationConfig.EnableDP { + return db + } + switch p.DataScope { + case "2": + return db.Where(tableName+".create_by in (select sys_user.user_id from sys_role_dept left join sys_user on sys_user.dept_id=sys_role_dept.dept_id where sys_role_dept.role_id = ?)", p.RoleId) + case "3": + return db.Where(tableName+".create_by in (SELECT user_id from sys_user where dept_id = ? )", p.DeptId) + case "4": + return db.Where(tableName+".create_by in (SELECT user_id from sys_user where sys_user.dept_id in(select dept_id from sys_dept where dept_path like ? ))", "%/"+pkg.IntToString(p.DeptId)+"/%") + case "5": + return db.Where(tableName+".create_by = ?", p.UserId) + default: + return db + } + } +} + +func getPermissionFromContext(c *gin.Context) *DataPermission { + p := new(DataPermission) + if pm, ok := c.Get(PermissionKey); ok { + switch pm.(type) { + case *DataPermission: + p = pm.(*DataPermission) + } + } + return p +} + +// GetPermissionFromContext 提供非action写法数据范围约束 +func GetPermissionFromContext(c *gin.Context) *DataPermission { + return getPermissionFromContext(c) +} diff --git a/common/actions/type.go b/common/actions/type.go new file mode 100644 index 0000000..69beb8c --- /dev/null +++ b/common/actions/type.go @@ -0,0 +1,5 @@ +package actions + +const ( + PermissionKey = "dataPermission" +) diff --git a/common/actions/update.go b/common/actions/update.go new file mode 100644 index 0000000..7052334 --- /dev/null +++ b/common/actions/update.go @@ -0,0 +1,59 @@ +package actions + +import ( + "net/http" + + "github.com/gin-gonic/gin" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/common/dto" + "go-admin/common/models" +) + +// UpdateAction 通用更新动作 +func UpdateAction(control dto.Control) gin.HandlerFunc { + return func(c *gin.Context) { + db, err := pkg.GetOrm(c) + if err != nil { + log.Error(err) + return + } + + msgID := pkg.GenerateMsgIDFromContext(c) + req := control.Generate() + //更新操作 + err = req.Bind(c) + if err != nil { + response.Error(c, http.StatusUnprocessableEntity, err, "参数验证失败") + return + } + var object models.ActiveRecord + object, err = req.GenerateM() + if err != nil { + response.Error(c, 500, err, "模型生成失败") + return + } + object.SetUpdateBy(user.GetUserId(c)) + + //数据权限检查 + p := GetPermissionFromContext(c) + + db = db.WithContext(c).Scopes( + Permission(object.TableName(), p), + ).Where(req.GetId()).Updates(object) + if err = db.Error; err != nil { + log.Errorf("MsgID[%s] Update error: %s", msgID, err) + response.Error(c, 500, err, "更新失败") + return + } + if db.RowsAffected == 0 { + response.Error(c, http.StatusForbidden, nil, "无权更新该数据") + return + } + response.OK(c, object.GetId(), "更新成功") + c.Next() + } +} diff --git a/common/actions/view.go b/common/actions/view.go new file mode 100644 index 0000000..b134f53 --- /dev/null +++ b/common/actions/view.go @@ -0,0 +1,67 @@ +package actions + +import ( + "errors" + "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "net/http" + + "github.com/gin-gonic/gin" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "gorm.io/gorm" + + "go-admin/common/dto" + "go-admin/common/models" +) + +// ViewAction 通用详情动作 +func ViewAction(control dto.Control, f func() interface{}) gin.HandlerFunc { + return func(c *gin.Context) { + db, err := pkg.GetOrm(c) + if err != nil { + log.Error(err) + return + } + + msgID := pkg.GenerateMsgIDFromContext(c) + //查看详情 + req := control.Generate() + err = req.Bind(c) + if err != nil { + response.Error(c, http.StatusUnprocessableEntity, err, "参数验证失败") + return + } + var object models.ActiveRecord + object, err = req.GenerateM() + if err != nil { + response.Error(c, 500, err, "模型生成失败") + return + } + + var rsp interface{} + if f != nil { + rsp = f() + } else { + rsp, _ = req.GenerateM() + } + + //数据权限检查 + p := GetPermissionFromContext(c) + + err = db.Model(object).WithContext(c).Scopes( + Permission(object.TableName(), p), + ).Where(req.GetId()).First(rsp).Error + + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + response.Error(c, http.StatusNotFound, nil, "查看对象不存在或无权查看") + return + } + if err != nil { + log.Errorf("MsgID[%s] View error: %s", msgID, err) + response.Error(c, 500, err, "查看失败") + return + } + response.OK(c, rsp, "查询成功") + c.Next() + } +} diff --git a/common/apis/api.go b/common/apis/api.go new file mode 100644 index 0000000..4b2c58b --- /dev/null +++ b/common/apis/api.go @@ -0,0 +1,135 @@ +package apis + +import ( + "errors" + "fmt" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "gorm.io/gorm" + + "go-admin/common/service" +) + +type Api struct { + Context *gin.Context + Logger *logger.Helper + Orm *gorm.DB + Errors error +} + +func (e *Api) AddError(err error) { + if e.Errors == nil { + e.Errors = err + } else if err != nil { + e.Logger.Error(err) + e.Errors = fmt.Errorf("%v; %w", e.Errors, err) + } +} + +// MakeContext 设置http上下文 +func (e *Api) MakeContext(c *gin.Context) *Api { + e.Context = c + e.Logger = api.GetRequestLogger(c) + return e +} + +// GetLogger 获取上下文提供的日志 +func (e *Api) GetLogger() *logger.Helper { + return api.GetRequestLogger(e.Context) +} + +func (e *Api) Bind(d interface{}, bindings ...binding.Binding) *Api { + var err error + if len(bindings) == 0 { + bindings = append(bindings, binding.JSON, nil) + } + for i := range bindings { + switch bindings[i] { + case binding.JSON: + err = e.Context.ShouldBindWith(d, binding.JSON) + case binding.XML: + err = e.Context.ShouldBindWith(d, binding.XML) + case binding.Form: + err = e.Context.ShouldBindWith(d, binding.Form) + case binding.Query: + err = e.Context.ShouldBindWith(d, binding.Query) + case binding.FormPost: + err = e.Context.ShouldBindWith(d, binding.FormPost) + case binding.FormMultipart: + err = e.Context.ShouldBindWith(d, binding.FormMultipart) + case binding.ProtoBuf: + err = e.Context.ShouldBindWith(d, binding.ProtoBuf) + case binding.MsgPack: + err = e.Context.ShouldBindWith(d, binding.MsgPack) + case binding.YAML: + err = e.Context.ShouldBindWith(d, binding.YAML) + case binding.Header: + err = e.Context.ShouldBindWith(d, binding.Header) + default: + err = e.Context.ShouldBindUri(d) + } + if err != nil { + e.AddError(err) + } + } + return e +} + +// GetOrm 获取Orm DB +func (e *Api) GetOrm() (*gorm.DB, error) { + db, err := pkg.GetOrm(e.Context) + if err != nil { + e.Error(500, err, "数据库连接获取失败") + return nil, err + } + return db, nil +} + +// MakeOrm 设置Orm DB +func (e *Api) MakeOrm() *Api { + var err error + if e.Logger == nil { + err = errors.New("at MakeOrm logger is nil") + //e.Logger.Error(500, err, "at MakeOrm logger is nil") + e.AddError(err) + return e + } + db, err := pkg.GetOrm(e.Context) + if err != nil { + e.Logger.Error(500, err, "数据库连接获取失败") + e.AddError(err) + } + e.Orm = db + return e +} + +func (e *Api) MakeService(c *service.Service) *Api { + c.Log = e.Logger + c.Orm = e.Orm + return e +} + +// Error 通常错误数据处理 +func (e *Api) Error(code int, err error, msg string) { + response.Error(e.Context, code, err, msg) +} + +// OK 通常成功数据处理 +func (e *Api) OK(data interface{}, msg string) { + response.OK(e.Context, data, msg) +} + +// PageOK 分页数据处理 +func (e *Api) PageOK(result interface{}, count int, pageIndex int, pageSize int, msg string) { + response.PageOK(e.Context, result, count, pageIndex, pageSize, msg) +} + +// Custom 兼容函数 +func (e *Api) Custom(data gin.H) { + response.Custum(e.Context, data) +} diff --git a/common/const/enum/ad_userinfo_transferopen/ad_userinfo_transferopen.go b/common/const/enum/ad_userinfo_transferopen/ad_userinfo_transferopen.go new file mode 100644 index 0000000..1da468e --- /dev/null +++ b/common/const/enum/ad_userinfo_transferopen/ad_userinfo_transferopen.go @@ -0,0 +1,7 @@ +package ad_userinfo_transferopen + +// 用户是否开启内部转账功能 +const ( + Close = 1 // 关闭 + Open = 3 // 开启 +) diff --git a/common/const/enum/agent_account_source/agent_account_source.go b/common/const/enum/agent_account_source/agent_account_source.go new file mode 100644 index 0000000..a06811c --- /dev/null +++ b/common/const/enum/agent_account_source/agent_account_source.go @@ -0,0 +1,6 @@ +package agent_account_source + +const ( + UserRegister = 1 // 用户注册 + SystemRegister = 3 // 系统添加 +) diff --git a/common/const/enum/agent_account_status/agent_account_status.go b/common/const/enum/agent_account_status/agent_account_status.go new file mode 100644 index 0000000..38db931 --- /dev/null +++ b/common/const/enum/agent_account_status/agent_account_status.go @@ -0,0 +1,7 @@ +package agent_account_status + +const ( + Wait = 1 // 待审核 + Audited = 3 // 已通过 + Reject = 5 // 已拒绝 +) diff --git a/common/const/enum/agent_reward_category/agent_reward_category.go b/common/const/enum/agent_reward_category/agent_reward_category.go new file mode 100644 index 0000000..cc07fa6 --- /dev/null +++ b/common/const/enum/agent_reward_category/agent_reward_category.go @@ -0,0 +1,6 @@ +package agent_reward_category + +const ( + Straight = 1 // 直客佣金 + Agent = 3 // 代理佣金 +) diff --git a/common/const/enum/businesstype/businesstype.go b/common/const/enum/businesstype/businesstype.go new file mode 100644 index 0000000..c2f6813 --- /dev/null +++ b/common/const/enum/businesstype/businesstype.go @@ -0,0 +1,96 @@ +package businesstype + +// BusinessType 操作业务 +type BusinessType int + +const ( + // 注册登录业务 + Other BusinessType = 0 // 其他 + Register BusinessType = 1 // 注册 + Login BusinessType = 2 // 登入 + ResetPass BusinessType = 3 // 找回密码 + ChangePassword BusinessType = 4 // 更改密码 + ScanLogin BusinessType = 14 // 扫码登入 + + // 身份验证专用业务类型(请勿占用) + OpenPhoneAuth BusinessType = 11 + ChangePhoneAuth BusinessType = 21 + ClosePhoneAuth BusinessType = 31 + OpenEmailAuth BusinessType = 12 + ChangeEmailAuth BusinessType = 22 + CloseEmailAuth BusinessType = 32 + OpenGoogleAuth BusinessType = 13 + ChangeGoogleAuth BusinessType = 23 + CloseGoogleAuth BusinessType = 33 + + //提现验证使用 + WithdrawAuth BusinessType = 201 + //userkey验证 + UserKeyAuth BusinessType = 202 + //地址浦 + AddAddress BusinessType = 203 + + // 以下请从 100 开始定义 + OtcCaptcha BusinessType = 100 // OTC 验证码 + AddPayAccount BusinessType = 101 // 新增收款账号 + EditPayAccount BusinessType = 102 // 编辑收款账号 + OTCSellerConfirmPay BusinessType = 103 // OTC 卖家已确认收款 + OTCSellerAppeal BusinessType = 104 // OTC 买家申诉 + OTCBuyerPaid BusinessType = 105 // OTC 买家已付款 + OTCBuyerAppeal BusinessType = 106 // OTC 买家申诉 + AgentEmailCode BusinessType = 107 // 代理商找回密码验证码 +) + +// 业务类型中文映射 +var BusinessTypeMapCN = map[BusinessType]string{ + Other: "其它", + Register: "注册", + Login: "登录", + ResetPass: "重置密码", + ChangePassword: "修改登录密码", + + OpenPhoneAuth: "开启手机验证", + ChangePhoneAuth: "更改手机验证", + ClosePhoneAuth: "关闭手机验证", + OpenEmailAuth: "开启邮箱验证", + ChangeEmailAuth: "更改邮箱验证", + CloseEmailAuth: "关闭邮箱验证", + OpenGoogleAuth: "开启谷歌验证", + ChangeGoogleAuth: "更改谷歌验证", + CloseGoogleAuth: "关闭谷歌验证", + + WithdrawAuth: "提现验证", + UserKeyAuth: "api验证", + AddAddress: "新增地址浦验证", + + OtcCaptcha: "OTC验证码", + AddPayAccount: "新增收款账号", + EditPayAccount: "编辑收款账号", +} + +// 业务类型英文映射 +var BusinessTypeMapEN = map[BusinessType]string{ + Other: "Other", + Register: "Register", + Login: "Login", + ResetPass: "Reset Password", + ChangePassword: "Change Login Password", + + OpenPhoneAuth: "Enable mobile phone verification", + ChangePhoneAuth: "Change phone verification", + ClosePhoneAuth: "Turn off phone verification", + OpenEmailAuth: "Enable mailbox verification", + ChangeEmailAuth: "Change mailbox validation", + CloseEmailAuth: "Turn off mailbox verification", + OpenGoogleAuth: "Turn on Google Authentication", + ChangeGoogleAuth: "Change Google Authentication", + CloseGoogleAuth: "Turn off Google Authentication", + + WithdrawAuth: "Withdraw Auth", + UserKeyAuth: "UserKey Auth", + AddAddress: "AddAddress Auth", + + OtcCaptcha: "otc phone verification", + AddPayAccount: "otc add payaccount", + EditPayAccount: "otc edit payaccount", +} diff --git a/common/const/enum/cointype/cointype.go b/common/const/enum/cointype/cointype.go new file mode 100644 index 0000000..a4cf9f8 --- /dev/null +++ b/common/const/enum/cointype/cointype.go @@ -0,0 +1,8 @@ +package cointype + +// cointype +const ( + BTC int = 1 // BTC + USDT int = 2 // USDT + USD int = 3 // USD +) diff --git a/common/const/enum/culatortype/calculatortype.go b/common/const/enum/culatortype/calculatortype.go new file mode 100644 index 0000000..9e45528 --- /dev/null +++ b/common/const/enum/culatortype/calculatortype.go @@ -0,0 +1,10 @@ +package culatortype + +// CulatorType 计算器 +const ( + Income int = 1 // 收益 + TargetPrice int = 2 // 目标价格 + FlatPrice int = 3 // 强平价格 + OpenEable int = 4 // 可开 + OpenPrice int = 5 // 开仓价格 +) diff --git a/common/const/enum/dealtype/dealtype.go b/common/const/enum/dealtype/dealtype.go new file mode 100644 index 0000000..d9e33f1 --- /dev/null +++ b/common/const/enum/dealtype/dealtype.go @@ -0,0 +1,39 @@ +package dealtype + +// DealType 资金小类型:1买单,2卖单,3手续费,4划转(废弃),5已实现盈亏,6爆仓精算,7资金费用 13内部转入 14 内部转出 101现货划转合约 102 合约划转现货 103 现货转法币 104 法币转现货 105 币本位划转现货 106 现货划转到币本位 +// 说明: 13-106之间不能插入其他数值 +const ( + Buy int = 1 // 1买单 + Sell int = 2 // 2卖单 + BrokerAge int = 3 // 手续费 + Transfer int = 4 // 划转(废弃) + Profited int = 5 // 已实现盈亏 + LossFee int = 6 // 爆仓精算 + FundFee int = 7 // 资金费用 + InTransfer int = 13 // 内部转入 + OutTransfer int = 14 // 内部转出 + VtsToFut int = 101 // 现货划转合约 + FutToVts int = 102 // 合约转现货 + VtsToOtc int = 103 // 现货转法币 + OtcToVts int = 104 // 法币转现货 + FutCoinToVts int = 105 // 币本位划转现货 + VtsToFutCoin int = 106 // 现货划转到币本位 +) + +var Types = map[int]string{ + Buy: "买单", + Sell: "卖单", + BrokerAge: "手续费", + Transfer: "划转", + Profited: "已实现盈亏", + LossFee: "爆仓精算", + FundFee: "资金费用", + InTransfer: "内部转入", + OutTransfer: "内部转出", + VtsToFut: "现货划转合约", + FutToVts: "合约转现货", + VtsToOtc: "现货转法币", + OtcToVts: "法币转现货", + FutCoinToVts: "币本位划转现货", + VtsToFutCoin: "现货划转到币本位", +} diff --git a/common/const/enum/enable/enable.go b/common/const/enum/enable/enable.go new file mode 100644 index 0000000..af6168f --- /dev/null +++ b/common/const/enum/enable/enable.go @@ -0,0 +1,7 @@ +package enable + +// 是否启用(通用) +const ( + Disable = 1 // 禁用 + Enable = 3 // 启用 +) diff --git a/common/const/enum/fut_coin_type/fut_coin_type.go b/common/const/enum/fut_coin_type/fut_coin_type.go new file mode 100644 index 0000000..5dacba8 --- /dev/null +++ b/common/const/enum/fut_coin_type/fut_coin_type.go @@ -0,0 +1,6 @@ +package fut_coin_type + +const ( + USDT = 1 // U本位 + COIN = 2 // 币本位 +) diff --git a/common/const/enum/grid_buytype/grid_buytype.go b/common/const/enum/grid_buytype/grid_buytype.go new file mode 100644 index 0000000..7e321d1 --- /dev/null +++ b/common/const/enum/grid_buytype/grid_buytype.go @@ -0,0 +1,7 @@ +package grid_buytype + +// 交易网格买卖类型 +const ( + Buy = 1 // 买 + Sell = 2 // 卖 +) diff --git a/common/const/enum/grid_createtype/grid_createtype.go b/common/const/enum/grid_createtype/grid_createtype.go new file mode 100644 index 0000000..9a339c1 --- /dev/null +++ b/common/const/enum/grid_createtype/grid_createtype.go @@ -0,0 +1,7 @@ +package grid_createtype + +// 交易网格创建方式 +const ( + OneCreation = 1 // 一键创建 + ManualCreation = 2 // 手动创建 +) diff --git a/common/const/enum/grid_dealtype/grid_dealtype.go b/common/const/enum/grid_dealtype/grid_dealtype.go new file mode 100644 index 0000000..d21ae00 --- /dev/null +++ b/common/const/enum/grid_dealtype/grid_dealtype.go @@ -0,0 +1,8 @@ +package grid_dealtype + +// 交易网格资金流水类型 +const ( + Occupy = 1 // 策略占用 + Release = 2 // 策略释放 + Profit = 3 // 策略利润 +) diff --git a/common/const/enum/grid_netmode/grid_netmode.go b/common/const/enum/grid_netmode/grid_netmode.go new file mode 100644 index 0000000..9208d36 --- /dev/null +++ b/common/const/enum/grid_netmode/grid_netmode.go @@ -0,0 +1,7 @@ +package grid_netmode + +// 现货交易网格模式 +const ( + EqualRatio = 1 // 等比 + EqualDifference = 2 // 等差 +) diff --git a/common/const/enum/grid_spotorder_status/grid_spotorder_status.go b/common/const/enum/grid_spotorder_status/grid_spotorder_status.go new file mode 100644 index 0000000..b93a446 --- /dev/null +++ b/common/const/enum/grid_spotorder_status/grid_spotorder_status.go @@ -0,0 +1,9 @@ +package grid_spotorder_status + +// 现货交易网格委托单状态 +const ( + UnDeal = 1 // 未成交 + Partial = 2 // 部分成交 + Complete = 3 // 完成成交 + Cancel = 4 // 取消 +) diff --git a/common/const/enum/grid_spotpolicy_state/grid_spotpolicy_state.go b/common/const/enum/grid_spotpolicy_state/grid_spotpolicy_state.go new file mode 100644 index 0000000..2e10983 --- /dev/null +++ b/common/const/enum/grid_spotpolicy_state/grid_spotpolicy_state.go @@ -0,0 +1,13 @@ +package grid_spotpolicy_state + +// 现货交易网格状态 +const ( + Wait = 1 // 等待触发 + Starting = 2 // 正在启动 + Start = 3 // 启动完成 + Stopping = 4 // 正在停止 + ManualStop = 5 // 手动停止 + ProfitStop = 6 // 止盈停止 + LossStop = 7 // 止损停止 + SignalStop = 8 // 信号触发停止 +) diff --git a/common/const/enum/grid_triggertype/grid_triggertype.go b/common/const/enum/grid_triggertype/grid_triggertype.go new file mode 100644 index 0000000..5dd7536 --- /dev/null +++ b/common/const/enum/grid_triggertype/grid_triggertype.go @@ -0,0 +1,7 @@ +package grid_triggertype + +// 交易网格触发条件 +const ( + Immediately = 1 // 立即触发 + Price = 2 // 价格触发 +) diff --git a/common/const/enum/holddaylog_chart_daytype/holddaylog_chart_daytype.go b/common/const/enum/holddaylog_chart_daytype/holddaylog_chart_daytype.go new file mode 100644 index 0000000..58fd860 --- /dev/null +++ b/common/const/enum/holddaylog_chart_daytype/holddaylog_chart_daytype.go @@ -0,0 +1,9 @@ +package holddaylog_chart_daytype + +// 1==24小时 3==7天 5==30天 7=3个月 +const ( + Hour_24 = 1 // 24小时 + Day_7 = 3 // 7天 + Day_30 = 5 // 30天 + Month_3 = 7 // 3个月 +) diff --git a/common/const/enum/kyctype/kyctype.go b/common/const/enum/kyctype/kyctype.go new file mode 100644 index 0000000..f728dd1 --- /dev/null +++ b/common/const/enum/kyctype/kyctype.go @@ -0,0 +1,54 @@ +package kyctype + +// IdCardLevel 身份等级 +type IdCardLevel int + +const ( + // Junior 初级认证 + Junior IdCardLevel = 1 + + // Middle 中级认证 + Middle IdCardLevel = 2 + + // Senior 高级认证 + Senior IdCardLevel = 3 +) + +// IdCardState 审核状态 +type IdCardState int + +const ( + // UnAuth 待审核 + UnAuth IdCardState = 1 + + // AuthFailed 审核失败 + AuthFailed IdCardState = 2 + + // AuthSuccess 审核完成 + AuthSuccess IdCardState = 3 +) + +// IdCardType 审核类型 +type IdCardType int + +const ( + // IdCard 身份证 + IdCard IdCardType = 1 + + // Passport 护照 + Passport IdCardType = 2 + + // Drive 驾照 + Drive IdCardType = 3 +) + +// VerifyType 审核类型 +type VerifyType int + +const ( + // Third 第三方审核 + Third VerifyType = 1 + + // System 系统审核 + System VerifyType = 2 +) diff --git a/common/const/enum/language_map.go b/common/const/enum/language_map.go new file mode 100644 index 0000000..7729775 --- /dev/null +++ b/common/const/enum/language_map.go @@ -0,0 +1,13 @@ +package enum + +// 语言 +var LanguageMap = map[string]string{ + "en": "英语", + "jp": "日本语", + "kr": "韩语", + "my": "马来西亚语", + "th": "泰国语", + "vn": "越南语", + "zh-CN": "简体中文", + "zh-HK": "繁体中文", +} diff --git a/common/const/enum/logtype/logtype.go b/common/const/enum/logtype/logtype.go new file mode 100644 index 0000000..4f17886 --- /dev/null +++ b/common/const/enum/logtype/logtype.go @@ -0,0 +1,24 @@ +package logtype + +// LogType 日志类型 +type LogType int + +const ( + Min LogType = iota // 最小值 + Login // 登入 + Logout // 登出 + Create // 新增 + Update // 编辑 + Delete // 删除 + ChangePwd // 修改密码 + Max // 最大值 +) + +var LogTypeMap = map[LogType]string{ + Login: "登入", + Logout: "登出", + Create: "新增", + Update: "编辑", + Delete: "删除", + ChangePwd: "修改密码", +} diff --git a/common/const/enum/longshorttype/longshorttype.go b/common/const/enum/longshorttype/longshorttype.go new file mode 100644 index 0000000..38a950e --- /dev/null +++ b/common/const/enum/longshorttype/longshorttype.go @@ -0,0 +1,7 @@ +package longshorttype + +// longshorttype +const ( + Long = 1 // 做多 + Short = 2 // 做空 +) diff --git a/common/const/enum/ordertype/ordertype.go b/common/const/enum/ordertype/ordertype.go new file mode 100644 index 0000000..7b93c83 --- /dev/null +++ b/common/const/enum/ordertype/ordertype.go @@ -0,0 +1,10 @@ +package ordertype + +//订单类型:1限价,2限价止盈止损,3市价,4止盈止损市价单,5系统强平委托 +const ( + Limit = 1 //限价单,下单类型 + StopLimit = 2 //止盈止损限价单,下单类型 + Market = 3 //市价单,下单类型 + StopMarket = 4 //止盈止损市价单,下单类型 + Force = 5 //系统强平委托 +) diff --git a/common/const/enum/otc_advaduitstatus/otc_advaduitstatus.go b/common/const/enum/otc_advaduitstatus/otc_advaduitstatus.go new file mode 100644 index 0000000..c69d78e --- /dev/null +++ b/common/const/enum/otc_advaduitstatus/otc_advaduitstatus.go @@ -0,0 +1,8 @@ +package otc_advaduitstatus + +// otc广告审核状态 +const ( + Pending = 1 // 待审核 + Pass = 3 // 审核通过 + Reject = 5 // 审核拒绝 +) diff --git a/common/const/enum/otc_advertisetype/otc_advertisetype.go b/common/const/enum/otc_advertisetype/otc_advertisetype.go new file mode 100644 index 0000000..dd2bef3 --- /dev/null +++ b/common/const/enum/otc_advertisetype/otc_advertisetype.go @@ -0,0 +1,7 @@ +package otc_advertisetype + +// 广告类型 +const ( + Buy = 1 // 购买 + Sell = 3 // 出售 +) diff --git a/common/const/enum/otc_advstatus/otc_advstatus.go b/common/const/enum/otc_advstatus/otc_advstatus.go new file mode 100644 index 0000000..eeb3a4d --- /dev/null +++ b/common/const/enum/otc_advstatus/otc_advstatus.go @@ -0,0 +1,7 @@ +package otc_advstatus + +// otc广告状态 +const ( + Offline = 1 // 下线 + Online = 3 // 上线 +) diff --git a/common/const/enum/otc_appealprogress/otc_appealprogress.go b/common/const/enum/otc_appealprogress/otc_appealprogress.go new file mode 100644 index 0000000..041eb4b --- /dev/null +++ b/common/const/enum/otc_appealprogress/otc_appealprogress.go @@ -0,0 +1,10 @@ +package otc_appealprogress + +//申诉进程 +const ( + WaitDefendantProcess = 1 // 等待被诉方处理 + WaitAppealProcess = 3 // 等待申诉方处理 + Revoked = 5 // 已撤销 + WaitCustomer = 7 // 等待客服处理 + Arbitrated = 9 // 已仲裁 +) diff --git a/common/const/enum/otc_appealreason/otc_appealreason.go b/common/const/enum/otc_appealreason/otc_appealreason.go new file mode 100644 index 0000000..201517d --- /dev/null +++ b/common/const/enum/otc_appealreason/otc_appealreason.go @@ -0,0 +1,19 @@ +package otc_appealreason + +// 申诉原因 +const ( + Paid = 1 // 我已付款,卖家未放行 + PayMore = 3 // 我向卖家多转了钱 + NotPay = 5 // 我没有收到买家付款 + MoneyErr = 7 // 买家付款金额不对 + Other = 9 // 其他 + +) + +var Types = map[int]string{ + Paid: "我已付款,卖家未放行", + PayMore: "我向卖家多转了钱", + NotPay: "我没有收到买家付款", + MoneyErr: "买家付款金额不对", + Other: "其他", +} diff --git a/common/const/enum/otc_appealstatus/otc_appealstatus.go b/common/const/enum/otc_appealstatus/otc_appealstatus.go new file mode 100644 index 0000000..fbc7c23 --- /dev/null +++ b/common/const/enum/otc_appealstatus/otc_appealstatus.go @@ -0,0 +1,8 @@ +package otc_appealstatus + +// otc仲裁状态 +const ( + Not = 1 // 未仲裁 + BuyerWin = 3 // 买家胜,放行 + SellerWin = 5 // 卖家胜,取消订单 +) diff --git a/common/const/enum/otc_auditlevel/otc_auditlevel.go b/common/const/enum/otc_auditlevel/otc_auditlevel.go new file mode 100644 index 0000000..db14462 --- /dev/null +++ b/common/const/enum/otc_auditlevel/otc_auditlevel.go @@ -0,0 +1,6 @@ +package otc_auditlevel + +// 商家认证等级 +const ( + Certified = 3 // 认证商家 +) diff --git a/common/const/enum/otc_chatlogsourcetype/otc_sourcetype.go b/common/const/enum/otc_chatlogsourcetype/otc_sourcetype.go new file mode 100644 index 0000000..fa64c84 --- /dev/null +++ b/common/const/enum/otc_chatlogsourcetype/otc_sourcetype.go @@ -0,0 +1,8 @@ +package otc_chatlogsourcetype + +// 消息来源 +const ( + Android = 1 + Ios = 3 + Pc = 5 +) diff --git a/common/const/enum/otc_chatlogstatus/otc_chatlogstatus.go b/common/const/enum/otc_chatlogstatus/otc_chatlogstatus.go new file mode 100644 index 0000000..fc44e22 --- /dev/null +++ b/common/const/enum/otc_chatlogstatus/otc_chatlogstatus.go @@ -0,0 +1,7 @@ +package otc_chatlogstatus + +// 聊天发送状态 +const ( + Fail = 1 // 发送失败 + Success = 3 // 发送成功 +) diff --git a/common/const/enum/otc_chattype/otc_chattype.go b/common/const/enum/otc_chattype/otc_chattype.go new file mode 100644 index 0000000..4bcd157 --- /dev/null +++ b/common/const/enum/otc_chattype/otc_chattype.go @@ -0,0 +1,8 @@ +package otc_chattype + +// 通讯软件类型 +const ( + Telegram = 1 + Wechat = 3 + QQ = 5 +) diff --git a/common/const/enum/otc_complaint/otc_complaint.go b/common/const/enum/otc_complaint/otc_complaint.go new file mode 100644 index 0000000..0469b11 --- /dev/null +++ b/common/const/enum/otc_complaint/otc_complaint.go @@ -0,0 +1,7 @@ +package otc_complaint + +// otc申诉方 +const ( + Buyer = 1 // 买方 + Seller = 3 // 卖方 +) diff --git a/common/const/enum/otc_consult/otc_consult.go b/common/const/enum/otc_consult/otc_consult.go new file mode 100644 index 0000000..c90b7d0 --- /dev/null +++ b/common/const/enum/otc_consult/otc_consult.go @@ -0,0 +1,8 @@ +package otc_consult + +// otc协商结果 +const ( + Not = 1 // 未协商 + Unable = 3 // 无法协商 + Negotiated = 5 // 协商解决 +) diff --git a/common/const/enum/otc_holdlogdealtype/otc_holdlogdealtype.go b/common/const/enum/otc_holdlogdealtype/otc_holdlogdealtype.go new file mode 100644 index 0000000..891f2a2 --- /dev/null +++ b/common/const/enum/otc_holdlogdealtype/otc_holdlogdealtype.go @@ -0,0 +1,20 @@ +package otc_holdlogdealtype + +// otc 资金类型 +const ( + Buy = 1 // 买入 + Sell = 3 // 卖出 + Frozen = 7 // 冻结 + AppendBond = 9 // 追加保证金 + UnFreeze = 11 // 解冻 + Deduct = 13 // 扣除保证金 + VtsToOtc = 103 // 现货转法币 + OtcToVts = 104 // 法币转现货 + Buckle = 105 // 划扣 + SaleAdviserAdd = 201 // 出售广告添加 + SaleAdviserEdit = 203 // 出售广告编辑 + MerchantUnVerify = 205 // 商家解除认证 保证金将解除冻结 + MerchantAdd = 207 // 法币商家新增 + MerchantEdit = 209 // 法币商家编辑 + MerchantFrozenBond = 210 // 法币商家冻结保证金 +) diff --git a/common/const/enum/otc_holdstatus/otc_holdstatus.go b/common/const/enum/otc_holdstatus/otc_holdstatus.go new file mode 100644 index 0000000..c2f2ddc --- /dev/null +++ b/common/const/enum/otc_holdstatus/otc_holdstatus.go @@ -0,0 +1,7 @@ +package otc_holdstatus + +// otc 账户状态 +const ( + Frozen = 1 // 冻结 + Normal = 3 // 正常 +) diff --git a/common/const/enum/otc_linktype/otc_linktype.go b/common/const/enum/otc_linktype/otc_linktype.go new file mode 100644 index 0000000..ef94149 --- /dev/null +++ b/common/const/enum/otc_linktype/otc_linktype.go @@ -0,0 +1,8 @@ +package otc_linktype + +// 紧急联系人类型 +const ( + Family = 1 // 家人 + Friend = 3 // 朋友 + Colleague = 5 // 同事 +) diff --git a/common/const/enum/otc_merchantout_status/otc_merchantout_status.go b/common/const/enum/otc_merchantout_status/otc_merchantout_status.go new file mode 100644 index 0000000..89ebdc2 --- /dev/null +++ b/common/const/enum/otc_merchantout_status/otc_merchantout_status.go @@ -0,0 +1,7 @@ +package otc_merchantout_status + +const ( + Wait = 1 // 待处理 + Passed = 3 // 已通过 + Rejected = 5 // 不通过 +) diff --git a/common/const/enum/otc_merchantstatus/otc_merchantstatus.go b/common/const/enum/otc_merchantstatus/otc_merchantstatus.go new file mode 100644 index 0000000..2cf6f82 --- /dev/null +++ b/common/const/enum/otc_merchantstatus/otc_merchantstatus.go @@ -0,0 +1,11 @@ +package otc_merchantstatus + +// otc商家状态:1 申请中,3 不通过,5 正常,7 已禁用,9 已解除认证 +const ( + Applying = 1 // 申请中 + Fail = 3 // 不通过 + Normal = 5 // 正常 + Disabled = 7 // 已禁用 + Revoked = 9 // 已解除认证 + BondNoEnough = 11 // 保证金不足 +) diff --git a/common/const/enum/otc_msgtype/otc_msgtype.go b/common/const/enum/otc_msgtype/otc_msgtype.go new file mode 100644 index 0000000..ab84273 --- /dev/null +++ b/common/const/enum/otc_msgtype/otc_msgtype.go @@ -0,0 +1,7 @@ +package otc_msgtype + +// otc 聊天消息类型 +const ( + Text = 1 // 文本 + Image = 3 // 图片 +) diff --git a/common/const/enum/otc_orderstatus/otc_orderstatus.go b/common/const/enum/otc_orderstatus/otc_orderstatus.go new file mode 100644 index 0000000..509345c --- /dev/null +++ b/common/const/enum/otc_orderstatus/otc_orderstatus.go @@ -0,0 +1,9 @@ +package otc_orderstatus + +const ( + UnPay = 1 // 未付款 + WaitMoney = 3 // 待放币 + Complete = 5 // 已完成 + Complain = 7 // 申诉中 + Cancel = 9 // 已取消 +) diff --git a/common/const/enum/otc_ordertype/otc_ordertype.go b/common/const/enum/otc_ordertype/otc_ordertype.go new file mode 100644 index 0000000..f641860 --- /dev/null +++ b/common/const/enum/otc_ordertype/otc_ordertype.go @@ -0,0 +1,7 @@ +package otc_ordertype + +// 订单方向 +const ( + Buy = 1 // 买 + Sell = 3 // 卖 +) diff --git a/common/const/enum/otc_pricetype/otc_pricetype.go b/common/const/enum/otc_pricetype/otc_pricetype.go new file mode 100644 index 0000000..7eb0381 --- /dev/null +++ b/common/const/enum/otc_pricetype/otc_pricetype.go @@ -0,0 +1,7 @@ +package otc_pricetype + +//定价方式 +const ( + Fix = 1 // 固定价格 + Float = 3 // 浮动价格 +) diff --git a/common/const/enum/otc_progress/otc_progress.go b/common/const/enum/otc_progress/otc_progress.go new file mode 100644 index 0000000..3e18701 --- /dev/null +++ b/common/const/enum/otc_progress/otc_progress.go @@ -0,0 +1,10 @@ +package otc_progress + +// otc申诉进程 +const ( + WaitSellerHandle = 1 // 等待卖家处理 + WaitBuyerHandle = 3 // 等待买家处理 + Revoked = 5 // 已撤销申诉 + WaitCustomer = 7 // 等待客服处理 + Arbitrated = 9 // 已仲裁 +) diff --git a/common/const/enum/otc_rateway/otc_rateway.go b/common/const/enum/otc_rateway/otc_rateway.go new file mode 100644 index 0000000..8bed66a --- /dev/null +++ b/common/const/enum/otc_rateway/otc_rateway.go @@ -0,0 +1,7 @@ +package otc_rateway + +//浮动方式 +const ( + Up = 1 // 上浮 + Down = 3 // 下浮 +) diff --git a/common/const/enum/otc_tradetype/otc_tradetype.go b/common/const/enum/otc_tradetype/otc_tradetype.go new file mode 100644 index 0000000..8a36099 --- /dev/null +++ b/common/const/enum/otc_tradetype/otc_tradetype.go @@ -0,0 +1,7 @@ +package otc_tradetype + +// 交易区 +const ( + Fast = 1 // 快捷区 + Self = 3 // 自选区 +) diff --git a/common/const/enum/showhide/showhide.go b/common/const/enum/showhide/showhide.go new file mode 100644 index 0000000..b78e9c0 --- /dev/null +++ b/common/const/enum/showhide/showhide.go @@ -0,0 +1,7 @@ +package showhide + +// 是否隐藏账号(通用) +const ( + Hide = 1 // 隐藏 + Show = 3 // 显示 +) diff --git a/common/const/enum/smstemplate/sms_template.go b/common/const/enum/smstemplate/sms_template.go new file mode 100644 index 0000000..6343e6d --- /dev/null +++ b/common/const/enum/smstemplate/sms_template.go @@ -0,0 +1,25 @@ +package smstemplate + +// 短信模板 Key 命名规则:SmsTemplate_BusinessType_Language +const ( + // 通用业务模板 - 中文 + SmsTemplate_1_CN = "您正在进行【${business}】,验证码:${code},5分钟内有效,请勿泄露给任何人。如非本人操作请立即联系官方客服。" + // 通用业务模板 - 英文 + SmsTemplate_1_EN = "Are you doing [${business}] Verification code: ${code}, valid within 5 minutes, please do not disclose it to anyone." + // 卖家已确认收款_CN + SmsTemplate_2_CN = "订单号 ${ordersn},卖家已确认收款,订单已完成,请前往账户查收资产。" + // 卖家已确认收款_EN + SmsTemplate_2_EN = "Order number ${ordersn}, the seller has confirmed the payment, the order has been completed, please go to the account to check the assets." + // 卖家申诉_CN + SmsTemplate_3_CN = "订单号 ${ordersn},卖家已发起申诉,请尽快前往处理该订单申诉。" + // 卖家申诉_EN + SmsTemplate_3_EN = "Order number ${ordersn}, the seller has initiated an appeal, please go to the appeal as soon as possible." + // 买家已付款_CN + SmsTemplate_4_CN = "订单号 ${ordersn},买家已付款,请查收款项进行放行。" + // 买家已付款_EN + SmsTemplate_4_EN = "Order number ${ordersn}, the buyer has paid, please check the payment for release." + // 买家申诉_CN + SmsTemplate_5_CN = "订单号 ${ordersn},买家已发起申诉,请尽快处理该订单申诉。" + // 买家申诉_EN + SmsTemplate_5_EN = "Order number ${ordersn}, the buyer has initiated an appeal, please handle the order appeal as soon as possible." +) diff --git a/common/const/enum/transfertype/transfertype.go b/common/const/enum/transfertype/transfertype.go new file mode 100644 index 0000000..9fd7520 --- /dev/null +++ b/common/const/enum/transfertype/transfertype.go @@ -0,0 +1,10 @@ +package transfertype + +const ( + Vts2Fut = 1 // 现货划转合约 + Fut2Vts = 2 // 合约划转现货 + Vts2Otc = 3 // 现货划转Otc + Otc2Vts = 4 // Otc划转现货 + FutCoin2Vts = 5 // 币本位划转现货 + Vts2FutCoin = 6 // 现货划转到币本位 +) diff --git a/common/const/enum/usefree/usefreee.go b/common/const/enum/usefree/usefreee.go new file mode 100644 index 0000000..e3998c6 --- /dev/null +++ b/common/const/enum/usefree/usefreee.go @@ -0,0 +1,7 @@ +package usefree + +// usefree +const ( + Available = 1 // 可用 + Frozen = 2 // 冻结 +) diff --git a/common/const/enum/vts_alertset_frequency/vts_alertset_frequency.go b/common/const/enum/vts_alertset_frequency/vts_alertset_frequency.go new file mode 100644 index 0000000..4808903 --- /dev/null +++ b/common/const/enum/vts_alertset_frequency/vts_alertset_frequency.go @@ -0,0 +1,8 @@ +package vts_alertset_frequency + +// frequency 提醒频率 +const ( + OnlyOnce = 1 // 仅提醒一次 + OnceDay = 2 // 每日提醒一次 + Always = 3 // 持续提醒(每当价格达到该值,则提醒一次) +) diff --git a/common/const/enum/vts_alertset_state/vts_alertset_state.go b/common/const/enum/vts_alertset_state/vts_alertset_state.go new file mode 100644 index 0000000..261967f --- /dev/null +++ b/common/const/enum/vts_alertset_state/vts_alertset_state.go @@ -0,0 +1,7 @@ +package vts_alertset_state + +// state 设置状态 +const ( + Normal = 1 // 正常 + Stop = 2 // 停止(比如频率是只提醒一次,提醒一次完就修改为2) +) diff --git a/common/const/enum/vts_alertset_type/vts_alertset_type.go b/common/const/enum/vts_alertset_type/vts_alertset_type.go new file mode 100644 index 0000000..0be5009 --- /dev/null +++ b/common/const/enum/vts_alertset_type/vts_alertset_type.go @@ -0,0 +1,11 @@ +package vts_alertset_type + +// alerttype 预警类型 +const ( + RisesAbove = 1 //1 价格涨到 + DropTo = 2 //2 价格跌到 + ChangeOver = 3 //3 涨幅达到 + ChangeUnder = 4 //4 跌幅达到 + H24ChangeOver = 5 //5 h24小时涨幅 + H24ChangeUnder = 6 //6 h24小时跌幅 +) diff --git a/common/const/enum/warehousetype/warehousetype.go b/common/const/enum/warehousetype/warehousetype.go new file mode 100644 index 0000000..cba0599 --- /dev/null +++ b/common/const/enum/warehousetype/warehousetype.go @@ -0,0 +1,43 @@ +package warehousetype + +// WareHouse +const ( + Isolated = 1 // 逐仓 + Cross = 2 // 全仓 +) + +// DirectHouse 持仓模式 +const ( + OneWayMode = 1 // 单向持仓 + HedgeMode = 2 // 双向持仓 +) + +// 下单持仓类型 1开多 2平多 3开空 4平空 +const ( + BuyLong = iota + 1 // 开多 + SellLog // 平多 + SellShort // 开空 + BuyShort // 平空 +) + +const ( + BOTH = "both" + LONG = "long" + SHORT = "short" +) + +var ( + PosSide = []string{BOTH, LONG, SHORT} // 持仓方向集合 +) + +const ( + Buy = 1 // 下单方向买 + Sell = 2 // 下单方向卖 +) +const ( + PositionTypeOpen = 1 // 开仓 + PositionTypeCloseOpen = 2 // 反向开仓 + PositionTypeClose = 3 // 平仓 + PositionTypeForce = 4 // 系统强平 + PositionTypeSysClose = 5 // 系统平仓 +) diff --git a/common/const/enum/yesno/yesno.go b/common/const/enum/yesno/yesno.go new file mode 100644 index 0000000..d0f7dee --- /dev/null +++ b/common/const/enum/yesno/yesno.go @@ -0,0 +1,7 @@ +package yesno + +// 是否(通用) +const ( + No = 1 // 否 + Yes = 3 // 是 +) diff --git a/common/const/rediskey/redis_key.go b/common/const/rediskey/redis_key.go new file mode 100644 index 0000000..b0f9251 --- /dev/null +++ b/common/const/rediskey/redis_key.go @@ -0,0 +1,62 @@ +package rediskey + +const ( + IPPositionCache = "_IPPositionCache" // IP 归属地缓存 + AppLoginUserToken = "_AppLoginUserToken_%d" // App登录用户的Token {uid} + AgentLoginUserToken = "_AgentLoginUserToken_%d" // PC端代理商登录用户的Token + AgentEmailCode = "_AgentEmailCode_%d" + AdminLoginUserToken = "_AdminLoginUserToken_%d" // 后台登录用户的Token {uid} + PCLoginUserToken = "_PCLoginUserToken_%d" // PC端登录token + UserLoginPwdErrFre = "_UserLoginPwdErrFre_%d" // 用户登录密码错误次数 {uid} + UserCaptchaSendFre = "_UserCaptchaSendFre_%v_%d" // 用户验证码发送频次 {uid|ip}_{business} + UserLoginWsClient = "_UserLoginWsClient" // websocket连接的客户端 + ScanLoginSecret = "_ScanLoginSecret_%v" // 扫码登录秘钥 + StatusCodeLanguage = "_StatusCodeLanguage_%v" // 状态码语言包_en + PCRegisterEmail = "_PCRegister_%v" // 用户注册时邮箱key + PCRegisterMobile = "_PCRegisterMobile_%v" // 用户注册时手机key + SpotSymbolTicker = "_SpotSymbolTicker_" // 现货交易对行情 + FutSymbolTicker = "_FutSymbolTicker_" // 合约交易对行情 + PreOrderScriptList = "_ProOrderScriptList_" // 脚本执行list + PreSpotOrderList = "_PreSpotOrderList_:%s" // 待触发的现货订单集合{交易所类型 exchange_type} + PreFutOrderList = "_PreFutOrderList_:%s" // 待触发的订单集合 {交易所类型 exchange_type} + + API_USER = "api_user:%v" // api用户 + SystemSetting = "system_setting" //系统设置 + ApiGroup = "api_group:%v" //api用户组 {id} + ApiGroupAll = "api_group:" + + ApiUserActiveList = "api_user_active_list" //已启用待连接websocket的api + ApiUserDeleteList = "api_user_delete_list" //已删除待删除的api + + FutStopTrigger = "fut_trigger_stop_lock:%v_%s" //合约止损触发锁 + SpotStopTrigger = "spot_trigger_stop_lock:%v_%s" //现货止损触发锁 + + SpotAddPositionTrigger = "spot_addposition_trigger:%v_%s" //现货加仓触发 {apiuserid|symbol} + FutAddPositionTrigger = "fut_addposition_trigger:%v_%s" //合约加仓触发 {apiuserid|symbol} + SpotTrigger = "spot_trigger_lock:%v_%s" //现货触发 {apiuserid|symbol} + FutTrigger = "fut_trigger_lock:%v_%s" //合约触发 {apiuserid|symbol} + + SpotCallBack = "spot_callback:%s" //现货回调 {ordersn} + FutCallBack = "fut_callback:%s" //合约回调 {ordersn} + + //需要清理键值---------BEGIN--------------- + + UserHolding = "api_user_hold:%v" //用户持仓币种 {主单apiid} 不是交易对 + + SpotHedgeClosePosition = "spot_hedge_close_position:%v_%s" //现货对冲平仓 {mainorderid|symbol} + FuturesHedgeClosePosition = "futures_hedge_close_position:%v_%s" //合约对冲平仓 {mainorderid|symbol} + + SpotStopLossList = "spot_stoploss_list" //现货止损待触发列表 + FuturesStopLossList = "futures_stoploss_list" //合约止损待触发列表 + + SpotAddPositionList = "spot_add_position_list" //现货加仓待触发 + FuturesAddPositionList = "futures_add_position_list" //合约加仓待触发 + StoplossMarkt = "stop_loss_markt" //对冲保险单(市价) 对冲单成交之后、清除 + + HoldeA = "holde_a:%v" //持仓A {主单id} + HoldeB = "holde_b:%v" //持仓B {主单id} + + HedgeClosePosition = "hedge_close_position:%v" //对冲平仓记录 {主单id} + //需要清理键值---------END----------------- + +) diff --git a/common/const/userlockkey/userlockkey.go b/common/const/userlockkey/userlockkey.go new file mode 100644 index 0000000..59de16a --- /dev/null +++ b/common/const/userlockkey/userlockkey.go @@ -0,0 +1,28 @@ +package userlockkey + +import ( + "strconv" +) + +var ( + preOrder = "Order-" //分布式锁现货委托单+划转前缀 + + preFutOrder = "futOrder-" //分布式锁u本位--合约委托单+划转前缀 + + preFutCoinOrder = "futCoinOrder-" //分布式锁币本位--合约委托单+划转前缀 +) + +// GetUserLockKey 分布式锁现货 用户 key +func GetUserLockKey(userId int) string { + return preOrder + strconv.Itoa(userId) +} + +// GetUserFutLockKey u本位--分布式锁合约 用户 key +func GetUserFutLockKey(userId int) string { + return preFutOrder + strconv.Itoa(userId) +} + +// GetUserFutCoinLockKey 币本位--分布式锁合约 用户 key +func GetUserFutCoinLockKey(userId int) string { + return preFutCoinOrder + strconv.Itoa(userId) +} diff --git a/common/database/initialize.go b/common/database/initialize.go new file mode 100644 index 0000000..22e6d61 --- /dev/null +++ b/common/database/initialize.go @@ -0,0 +1,65 @@ +package database + +import ( + "time" + + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk" + toolsConfig "github.com/go-admin-team/go-admin-core/sdk/config" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + mycasbin "github.com/go-admin-team/go-admin-core/sdk/pkg/casbin" + toolsDB "github.com/go-admin-team/go-admin-core/tools/database" + . "github.com/go-admin-team/go-admin-core/tools/gorm/logger" + "gorm.io/gorm" + "gorm.io/gorm/logger" + "gorm.io/gorm/schema" + + "go-admin/common/global" +) + +// Setup 配置数据库 +func Setup() { + for k := range toolsConfig.DatabasesConfig { + setupSimpleDatabase(k, toolsConfig.DatabasesConfig[k]) + } +} + +func setupSimpleDatabase(host string, c *toolsConfig.Database) { + if global.Driver == "" { + global.Driver = c.Driver + } + log.Infof("%s => %s", host, pkg.Green(c.Source)) + registers := make([]toolsDB.ResolverConfigure, len(c.Registers)) + for i := range c.Registers { + registers[i] = toolsDB.NewResolverConfigure( + c.Registers[i].Sources, + c.Registers[i].Replicas, + c.Registers[i].Policy, + c.Registers[i].Tables) + } + resolverConfig := toolsDB.NewConfigure(c.Source, c.MaxIdleConns, c.MaxOpenConns, c.ConnMaxIdleTime, c.ConnMaxLifeTime, registers) + db, err := resolverConfig.Init(&gorm.Config{ + NamingStrategy: schema.NamingStrategy{ + SingularTable: true, + }, + Logger: New( + logger.Config{ + SlowThreshold: time.Second, + Colorful: true, + LogLevel: logger.LogLevel( + log.DefaultLogger.Options().Level.LevelForGorm()), + }, + ), + }, opens[c.Driver]) + + if err != nil { + log.Fatal(pkg.Red(c.Driver+" connect error :"), err) + } else { + log.Info(pkg.Green(c.Driver + " connect success !")) + } + + e := mycasbin.Setup(db, "") + + sdk.Runtime.SetDb(host, db) + sdk.Runtime.SetCasbin(host, e) +} diff --git a/common/database/open.go b/common/database/open.go new file mode 100644 index 0000000..de02868 --- /dev/null +++ b/common/database/open.go @@ -0,0 +1,16 @@ +//go:build !sqlite3 + +package database + +import ( + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlserver" + "gorm.io/gorm" +) + +var opens = map[string]func(string) gorm.Dialector{ + "mysql": mysql.Open, + "postgres": postgres.Open, + "sqlserver": sqlserver.Open, +} diff --git a/common/database/open_sqlite3.go b/common/database/open_sqlite3.go new file mode 100644 index 0000000..aaebe94 --- /dev/null +++ b/common/database/open_sqlite3.go @@ -0,0 +1,19 @@ +//go:build sqlite3 +// +build sqlite3 + +package database + +import ( + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/driver/sqlserver" + "gorm.io/gorm" +) + +var opens = map[string]func(string) gorm.Dialector{ + "mysql": mysql.Open, + "postgres": postgres.Open, + "sqlite3": sqlite.Open, + "sqlserver": sqlserver.Open, +} diff --git a/common/dto/api_group.go b/common/dto/api_group.go new file mode 100644 index 0000000..a4332f5 --- /dev/null +++ b/common/dto/api_group.go @@ -0,0 +1,8 @@ +package dto + +type ApiGroupDto struct { + Id int `json:"id"` + Name string `json:"name"` + ApiUserId int `json:"apiUserId"` + ChildApiUserId int `json:"childApiUserId"` +} diff --git a/common/dto/auto_form.go b/common/dto/auto_form.go new file mode 100644 index 0000000..c99976a --- /dev/null +++ b/common/dto/auto_form.go @@ -0,0 +1,74 @@ +package dto + +type AutoForm struct { + Fields []Field `json:"fields"` + FormRef string `json:"formRef"` + FormModel string `json:"formModel"` + Size string `json:"size"` + LabelPosition string `json:"labelPosition"` + LabelWidth int `json:"labelWidth"` + FormRules string `json:"formRules"` + Gutter int `json:"gutter"` + Disabled bool `json:"disabled"` + Span int `json:"span"` + FormBtns bool `json:"formBtns"` +} + +type Config struct { + Label string `json:"label"` + LabelWidth interface{} `json:"labelWidth"` + ShowLabel bool `json:"showLabel"` + ChangeTag bool `json:"changeTag"` + Tag string `json:"tag"` + TagIcon string `json:"tagIcon"` + Required bool `json:"required"` + Layout string `json:"layout"` + Span int `json:"span"` + Document string `json:"document"` + RegList []interface{} `json:"regList"` + FormId int `json:"formId"` + RenderKey int64 `json:"renderKey"` + DefaultValue interface{} `json:"defaultValue"` + ShowTip bool `json:"showTip,omitempty"` + ButtonText string `json:"buttonText,omitempty"` + FileSize int `json:"fileSize,omitempty"` + SizeUnit string `json:"sizeUnit,omitempty"` +} + +type Option struct { + Label string `json:"label"` + Value string `json:"value"` +} + +type Slot struct { + Prepend string `json:"prepend,omitempty"` + Append string `json:"append,omitempty"` + ListType bool `json:"list-type,omitempty"` + Options []Option `json:"options,omitempty"` +} + +type Field struct { + Config Config `json:"__config__"` + Slot Slot `json:"__slot__"` + Placeholder string `json:"placeholder,omitempty"` + Style Style `json:"style,omitempty"` + Clearable bool `json:"clearable,omitempty"` + PrefixIcon string `json:"prefix-icon,omitempty"` + SuffixIcon string `json:"suffix-icon,omitempty"` + Maxlength interface{} `json:"maxlength"` + ShowWordLimit bool `json:"show-word-limit,omitempty"` + Readonly bool `json:"readonly,omitempty"` + Disabled bool `json:"disabled"` + VModel string `json:"__vModel__"` + Action string `json:"action,omitempty"` + Accept string `json:"accept,omitempty"` + Name string `json:"name,omitempty"` + AutoUpload bool `json:"auto-upload,omitempty"` + ListType string `json:"list-type,omitempty"` + Multiple bool `json:"multiple,omitempty"` + Filterable bool `json:"filterable,omitempty"` +} + +type Style struct { + Width string `json:"width"` +} diff --git a/common/dto/generate.go b/common/dto/generate.go new file mode 100644 index 0000000..313ae6a --- /dev/null +++ b/common/dto/generate.go @@ -0,0 +1,106 @@ +package dto + +import ( + vd "github.com/bytedance/go-tagexpr/v2/validator" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" +) + +type ObjectById struct { + Id int `uri:"id"` + Ids []int `json:"ids"` +} + +func (s *ObjectById) Bind(ctx *gin.Context) error { + var err error + log := api.GetRequestLogger(ctx) + err = ctx.ShouldBindUri(s) + if err != nil { + log.Warnf("ShouldBindUri error: %s", err.Error()) + return err + } + if ctx.Request.Method == http.MethodDelete { + err = ctx.ShouldBind(&s) + if err != nil { + log.Warnf("ShouldBind error: %s", err.Error()) + return err + } + if len(s.Ids) > 0 { + return nil + } + if s.Ids == nil { + s.Ids = make([]int, 0) + } + if s.Id != 0 { + s.Ids = append(s.Ids, s.Id) + } + } + if err = vd.Validate(s); err != nil { + log.Errorf("Validate error: %s", err.Error()) + return err + } + return err +} + +func (s *ObjectById) GetId() interface{} { + if len(s.Ids) > 0 { + s.Ids = append(s.Ids, s.Id) + return s.Ids + } + return s.Id +} + +type ObjectGetReq struct { + Id int `uri:"id"` +} + +func (s *ObjectGetReq) Bind(ctx *gin.Context) error { + var err error + log := api.GetRequestLogger(ctx) + err = ctx.ShouldBindUri(s) + if err != nil { + log.Warnf("ShouldBindUri error: %s", err.Error()) + return err + } + if err = vd.Validate(s); err != nil { + log.Errorf("Validate error: %s", err.Error()) + return err + } + return err +} + +func (s *ObjectGetReq) GetId() interface{} { + return s.Id +} + +type ObjectDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *ObjectDeleteReq) Bind(ctx *gin.Context) error { + var err error + log := api.GetRequestLogger(ctx) + err = ctx.ShouldBind(&s) + if err != nil { + log.Warnf("ShouldBind error: %s", err.Error()) + return err + } + if len(s.Ids) > 0 { + return nil + } + if s.Ids == nil { + s.Ids = make([]int, 0) + } + + if err = vd.Validate(s); err != nil { + log.Errorf("Validate error: %s", err.Error()) + return err + } + return err +} + +func (s *ObjectDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/common/dto/order.go b/common/dto/order.go new file mode 100644 index 0000000..25455fb --- /dev/null +++ b/common/dto/order.go @@ -0,0 +1,12 @@ +package dto + +import ( + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +func OrderDest(sort string, bl bool) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + return db.Order(clause.OrderByColumn{Column: clause.Column{Name: sort}, Desc: bl}) + } +} diff --git a/common/dto/pagination.go b/common/dto/pagination.go new file mode 100644 index 0000000..ee438e5 --- /dev/null +++ b/common/dto/pagination.go @@ -0,0 +1,20 @@ +package dto + +type Pagination struct { + PageIndex int `form:"pageIndex"` + PageSize int `form:"pageSize"` +} + +func (m *Pagination) GetPageIndex() int { + if m.PageIndex <= 0 { + m.PageIndex = 1 + } + return m.PageIndex +} + +func (m *Pagination) GetPageSize() int { + if m.PageSize <= 0 { + m.PageSize = 10 + } + return m.PageSize +} diff --git a/common/dto/search.go b/common/dto/search.go new file mode 100644 index 0000000..72cc60e --- /dev/null +++ b/common/dto/search.go @@ -0,0 +1,84 @@ +package dto + +import ( + "github.com/go-admin-team/go-admin-core/tools/search" + "go-admin/common/global" + "gorm.io/gorm" +) + +type GeneralDelDto struct { + Id int `uri:"id" json:"id" validate:"required"` + Ids []int `json:"ids"` +} + +func (g GeneralDelDto) GetIds() []int { + ids := make([]int, 0) + if g.Id != 0 { + ids = append(ids, g.Id) + } + if len(g.Ids) > 0 { + for _, id := range g.Ids { + if id > 0 { + ids = append(ids, id) + } + } + } else { + if g.Id > 0 { + ids = append(ids, g.Id) + } + } + if len(ids) <= 0 { + //方式全部删除 + ids = append(ids, 0) + } + return ids +} + +type GeneralGetDto struct { + Id int `uri:"id" json:"id" validate:"required"` +} + +func MakeCondition(q interface{}) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + condition := &search.GormCondition{ + GormPublic: search.GormPublic{}, + Join: make([]*search.GormJoin, 0), + } + search.ResolveSearchQuery(global.Driver, q, condition) + for _, join := range condition.Join { + if join == nil { + continue + } + db = db.Joins(join.JoinOn) + for k, v := range join.Where { + db = db.Where(k, v...) + } + for k, v := range join.Or { + db = db.Or(k, v...) + } + for _, o := range join.Order { + db = db.Order(o) + } + } + for k, v := range condition.Where { + db = db.Where(k, v...) + } + for k, v := range condition.Or { + db = db.Or(k, v...) + } + for _, o := range condition.Order { + db = db.Order(o) + } + return db + } +} + +func Paginate(pageSize, pageIndex int) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + offset := (pageIndex - 1) * pageSize + if offset < 0 { + offset = 0 + } + return db.Offset(offset).Limit(pageSize) + } +} diff --git a/common/dto/type.go b/common/dto/type.go new file mode 100644 index 0000000..e029943 --- /dev/null +++ b/common/dto/type.go @@ -0,0 +1,21 @@ +package dto + +import ( + "github.com/gin-gonic/gin" + "go-admin/common/models" +) + +type Index interface { + Generate() Index + Bind(ctx *gin.Context) error + GetPageIndex() int + GetPageSize() int + GetNeedSearch() interface{} +} + +type Control interface { + Generate() Control + Bind(ctx *gin.Context) error + GenerateM() (models.ActiveRecord, error) + GetId() interface{} +} diff --git a/common/file_store/initialize.go b/common/file_store/initialize.go new file mode 100644 index 0000000..fabce9f --- /dev/null +++ b/common/file_store/initialize.go @@ -0,0 +1,45 @@ +package file_store + +import "fmt" + +type OXS struct { + // Endpoint 访问域名 + Endpoint string + // AccessKeyID AK + AccessKeyID string + // AccessKeySecret AKS + AccessKeySecret string + // BucketName 桶名称 + BucketName string +} + +// Setup 配置文件存储driver +func (e *OXS) Setup(driver DriverType, options ...ClientOption) FileStoreType { + fileStoreType := driver + var fileStore FileStoreType + switch fileStoreType { + case AliYunOSS: + fileStore = new(ALiYunOSS) + err := fileStore.Setup(e.Endpoint, e.AccessKeyID, e.AccessKeySecret, e.BucketName) + if err != nil { + fmt.Println(err) + } + return fileStore + case HuaweiOBS: + fileStore = new(HuaWeiOBS) + err := fileStore.Setup(e.Endpoint, e.AccessKeyID, e.AccessKeySecret, e.BucketName) + if err != nil { + fmt.Println(err) + } + return fileStore + case QiNiuKodo: + fileStore = new(QiNiuKODO) + err := fileStore.Setup(e.Endpoint, e.AccessKeyID, e.AccessKeySecret, e.BucketName) + if err != nil { + fmt.Println(err) + } + return fileStore + } + + return nil +} diff --git a/common/file_store/interface.go b/common/file_store/interface.go new file mode 100644 index 0000000..acecbe9 --- /dev/null +++ b/common/file_store/interface.go @@ -0,0 +1,27 @@ +package file_store + +// DriverType 驱动类型 +type DriverType string + +const ( + // HuaweiOBS 华为云OBS + HuaweiOBS DriverType = "HuaweiOBS" + // AliYunOSS 阿里云OSS + AliYunOSS DriverType = "AliYunOSS" + // QiNiuKodo 七牛云kodo + QiNiuKodo DriverType = "QiNiuKodo" +) + +type ClientOption map[string]interface{} + +// TODO: FileStoreType名称待定 + +// FileStoreType OXS +type FileStoreType interface { + // Setup 装载 endpoint sss + Setup(endpoint, accessKeyID, accessKeySecret, BucketName string, options ...ClientOption) error + // UpLoad 上传 + UpLoad(yourObjectName string, localFile interface{}) error + // GetTempToken 获取临时Token + GetTempToken() (string, error) +} diff --git a/common/file_store/kodo.go b/common/file_store/kodo.go new file mode 100644 index 0000000..db896e2 --- /dev/null +++ b/common/file_store/kodo.go @@ -0,0 +1,111 @@ +package file_store + +import ( + "context" + "fmt" + "github.com/qiniu/go-sdk/v7/auth/qbox" + "github.com/qiniu/go-sdk/v7/storage" +) + +type Zone string + +const ( + // HuaDong 华东 + HuaDong Zone = "HuaDong" + // HuaBei 华北 + HuaBei Zone = "HuaBei" + // HuaNan 华南 + HuaNan Zone = "HuaNan" + // BeiMei 北美 + BeiMei Zone = "BeiMei" + // XinJiaPo 新加坡 + XinJiaPo Zone = "XinJiaPo" +) + +type QiNiuKODO struct { + Client interface{} + BucketName string + cfg storage.Config + options []ClientOption +} + +func (e *QiNiuKODO) getToken() string { + putPolicy := storage.PutPolicy{ + Scope: e.BucketName, + } + if len(e.options) > 0 && e.options[0]["Expires"] != nil { + putPolicy.Expires = e.options[0]["Expires"].(uint64) + } + upToken := putPolicy.UploadToken(e.Client.(*qbox.Mac)) + return upToken +} + +//Setup 装载 +//endpoint sss +func (e *QiNiuKODO) Setup(endpoint, accessKeyID, accessKeySecret, BucketName string, options ...ClientOption) error { + + mac := qbox.NewMac(accessKeyID, accessKeySecret) + // 获取存储空间。 + cfg := storage.Config{} + // 空间对应的机房 + e.setZoneORDefault(cfg, options...) + // 是否使用https域名 + cfg.UseHTTPS = true + // 上传是否使用CDN上传加速 + cfg.UseCdnDomains = false + + e.Client = mac + e.BucketName = BucketName + e.cfg = cfg + e.options = options + return nil +} + +// setZoneORDefault 设置Zone或者默认华东 +func (e *QiNiuKODO) setZoneORDefault(cfg storage.Config, options ...ClientOption) { + if len(options) > 0 && options[0]["Zone"] != nil { + if _, ok := options[0]["Zone"].(Zone); !ok { + cfg.Zone = &storage.ZoneHuadong + } + switch options[0]["Zone"].(Zone) { + case HuaDong: + cfg.Zone = &storage.ZoneHuadong + case HuaBei: + cfg.Zone = &storage.ZoneHuabei + case HuaNan: + cfg.Zone = &storage.ZoneHuanan + case BeiMei: + cfg.Zone = &storage.ZoneBeimei + case XinJiaPo: + cfg.Zone = &storage.ZoneXinjiapo + default: + cfg.Zone = &storage.ZoneHuadong + } + } +} + +// UpLoad 文件上传 +func (e *QiNiuKODO) UpLoad(yourObjectName string, localFile interface{}) error { + + // 构建表单上传的对象 + formUploader := storage.NewFormUploader(&e.cfg) + ret := storage.PutRet{} + // 可选配置 + putExtra := storage.PutExtra{ + Params: map[string]string{ + "x:name": "github logo", + }, + } + err := formUploader.PutFile(context.Background(), &ret, e.getToken(), yourObjectName, localFile.(string), &putExtra) + if err != nil { + fmt.Println(err) + return err + } + fmt.Println(ret.Key, ret.Hash) + return nil +} + +func (e *QiNiuKODO) GetTempToken() (string, error) { + token := e.getToken() + return token, nil +} diff --git a/common/file_store/kodo_test.go b/common/file_store/kodo_test.go new file mode 100644 index 0000000..a8767ae --- /dev/null +++ b/common/file_store/kodo_test.go @@ -0,0 +1,23 @@ +package file_store + +import ( + "testing" +) + +func TestKODOUpload(t *testing.T) { + e := OXS{"", "", "", ""} + var oxs = e.Setup(QiNiuKodo, map[string]interface{}{"Zone": "华东"}) + err := oxs.UpLoad("test.png", "./test.png") + if err != nil { + t.Error(err) + } + t.Log("ok") +} + +func TestKODOGetTempToken(t *testing.T) { + e := OXS{"", "", "", ""} + var oxs = e.Setup(QiNiuKodo, map[string]interface{}{"Zone": "华东"}) + token, _ := oxs.GetTempToken() + t.Log(token) + t.Log("ok") +} diff --git a/common/file_store/obs.go b/common/file_store/obs.go new file mode 100644 index 0000000..e937729 --- /dev/null +++ b/common/file_store/obs.go @@ -0,0 +1,52 @@ +package file_store + +import ( + "fmt" + "github.com/huaweicloud/huaweicloud-sdk-go-obs/obs" + "log" +) + +type HuaWeiOBS struct { + Client interface{} + BucketName string +} + +func (e *HuaWeiOBS) Setup(endpoint, accessKeyID, accessKeySecret, BucketName string, options ...ClientOption) error { + // 创建ObsClient结构体 + client, err := obs.New(accessKeyID, accessKeySecret, endpoint) + if err != nil { + log.Println("Error:", err) + return err + } + e.Client = client + e.BucketName = BucketName + return nil +} + +// UpLoad 文件上传 +// yourObjectName 文件路径名称,与objectKey是同一概念,表示断点续传上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg +func (e *HuaWeiOBS) UpLoad(yourObjectName string, localFile interface{}) error { + // 获取存储空间。 + input := &obs.PutFileInput{} + input.Bucket = e.BucketName + input.Key = yourObjectName + input.SourceFile = localFile.(string) + output, err := e.Client.(*obs.ObsClient).PutFile(input) + + if err == nil { + fmt.Printf("RequestId:%s\n", output.RequestId) + fmt.Printf("ETag:%s, StorageClass:%s\n", output.ETag, output.StorageClass) + } else { + if obsError, ok := err.(obs.ObsError); ok { + fmt.Println(obsError.Code) + fmt.Println(obsError.Message) + } else { + fmt.Println(err) + } + } + return nil +} + +func (e *HuaWeiOBS) GetTempToken() (string, error) { + return "", nil +} diff --git a/common/file_store/obs_test.go b/common/file_store/obs_test.go new file mode 100644 index 0000000..0960750 --- /dev/null +++ b/common/file_store/obs_test.go @@ -0,0 +1,15 @@ +package file_store + +import ( + "testing" +) + +func TestOBSUpload(t *testing.T) { + e := OXS{"", "", "", ""} + var oxs = e.Setup(HuaweiOBS) + err := oxs.UpLoad("test.png", "./test.png") + if err != nil { + t.Error(err) + } + t.Log("ok") +} diff --git a/common/file_store/oss.go b/common/file_store/oss.go new file mode 100644 index 0000000..c35627b --- /dev/null +++ b/common/file_store/oss.go @@ -0,0 +1,48 @@ +package file_store + +import ( + "github.com/aliyun/aliyun-oss-go-sdk/oss" + "log" +) + +type ALiYunOSS struct { + Client interface{} + BucketName string +} + +//Setup 装载 +//endpoint sss +func (e *ALiYunOSS) Setup(endpoint, accessKeyID, accessKeySecret, BucketName string, options ...ClientOption) error { + client, err := oss.New(endpoint, accessKeyID, accessKeySecret) + if err != nil { + log.Println("Error:", err) + return err + } + e.Client = client + e.BucketName = BucketName + + return nil +} + +// UpLoad 文件上传 +func (e *ALiYunOSS) UpLoad(yourObjectName string, localFile interface{}) error { + // 获取存储空间。 + bucket, err := e.Client.(*oss.Client).Bucket(e.BucketName) + if err != nil { + log.Println("Error:", err) + return err + } + // 设置分片大小为100 KB,指定分片上传并发数为3,并开启断点续传上传。 + // 其中与objectKey是同一概念,表示断点续传上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。 + // "LocalFile"为filePath,100*1024为partSize。 + err = bucket.UploadFile(yourObjectName, localFile.(string), 100*1024, oss.Routines(3), oss.Checkpoint(true, "")) + if err != nil { + log.Println("Error:", err) + return err + } + return nil +} + +func (e *ALiYunOSS) GetTempToken() (string, error) { + return "", nil +} diff --git a/common/file_store/oss_test.go b/common/file_store/oss_test.go new file mode 100644 index 0000000..f04dd22 --- /dev/null +++ b/common/file_store/oss_test.go @@ -0,0 +1,16 @@ +package file_store + +import ( + "testing" +) + +func TestOSSUpload(t *testing.T) { + // 打括号内填写自己的测试信息即可 + e := OXS{} + var oxs = e.Setup(AliYunOSS) + err := oxs.UpLoad("test.png", "./test.png") + if err != nil { + t.Error(err) + } + t.Log("ok") +} diff --git a/common/global/adm.go b/common/global/adm.go new file mode 100644 index 0000000..ff26a0a --- /dev/null +++ b/common/global/adm.go @@ -0,0 +1,20 @@ +package global + +const ( + // Version go-admin version info + Version = "2.1.2" +) + +var ( + // Driver 数据库驱动 + Driver string +) + +const ( + //钱包 回调配置 + SYS_CONFIG_CALLBACK = "CoinGateCallBack" + //钱包 取消配置 + SYS_CONFIG_CANCECL = "CoinGateCancel" + ////钱包 成功配置 + SYS_CONFIG_SUCCESS = "CoinGateSuccess" +) diff --git a/common/global/casbin.go b/common/global/casbin.go new file mode 100644 index 0000000..72b7e6c --- /dev/null +++ b/common/global/casbin.go @@ -0,0 +1,18 @@ +package global + +import ( + "github.com/casbin/casbin/v2" + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/sdk/api" +) + +func LoadPolicy(c *gin.Context) (*casbin.SyncedEnforcer, error) { + log := api.GetRequestLogger(c) + if err := sdk.Runtime.GetCasbinKey(c.Request.Host).LoadPolicy(); err == nil { + return sdk.Runtime.GetCasbinKey(c.Request.Host), err + } else { + log.Errorf("casbin rbac_model or policy init error, %s ", err.Error()) + return nil, err + } +} diff --git a/common/global/coingate.go b/common/global/coingate.go new file mode 100644 index 0000000..1c86417 --- /dev/null +++ b/common/global/coingate.go @@ -0,0 +1,25 @@ +package global + +const ( + //内部生成默认状态 + COINGATE_STATUS_DEFAULT = "default" + + //新创建的发票。购物者尚未选择付款货币。 + COINGATE_STATUS_NEW = "new" + // 购物者已选择支付货币。正在等待付款 + COINGATE_STATUS_PENDING = "pending" + //购物者已转账支付发票款项。正在等待区块链网络确认。 + COINGATE_STATUS_CONFIRMING = "confirming" + //付款已由网络确认,并记入商家账户。购买的商品/服务可以安全地交付给购物者。 + COINGATE_STATUS_PAID = "paid" + //由于 AML/CTF 合规原因,付款被网络拒绝或被标记为无效 + COINGATE_STATUS_INVALID = "invalid" + //购物者未在规定时间内付款(默认值:20 分钟),因此发票已过期。 + COINGATE_STATUS_EXPIRED = "expired" + //购物者取消了发票。 + COINGATE_STATUS_CANCELED = "canceled" + //付款已退还给购物者 + COINGATE_STATUS_REFUNDED = "refunded" + //部分付款已退还给购物者。 + COINGATE_STATUS_PARTIALLY_REFUNDED = "partially_refunded" +) diff --git a/common/global/exchange.go b/common/global/exchange.go new file mode 100644 index 0000000..4436758 --- /dev/null +++ b/common/global/exchange.go @@ -0,0 +1,11 @@ +package global + +//交易所类型字典 +const ( + EXCHANGE_BINANCE = "binance" + EXCHANGE_OKEX = "okex" + EXCHANGE_GATE = "gate" + EXCHANGE_COINBASE = "coinbase" + EXCHANGE_BITFINEX = "bitfinex" + EXCHANGE_BITMEX = "bitmex" +) diff --git a/common/global/logo.go b/common/global/logo.go new file mode 100644 index 0000000..e4ed471 --- /dev/null +++ b/common/global/logo.go @@ -0,0 +1,4 @@ +package global + +// LogoContent go-admin ascii显示,减少静态文件依赖 +var LogoContent = []byte{10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 95, 95, 95, 95, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 44, 45, 45, 45, 44, 32, 32, 32, 32, 32, 32, 32, 32, 44, 39, 32, 32, 44, 32, 96, 46, 32, 32, 44, 45, 45, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 44, 45, 45, 45, 46, 32, 32, 32, 32, 32, 32, 44, 45, 45, 45, 44, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 44, 45, 45, 45, 46, 39, 124, 32, 32, 32, 32, 32, 44, 45, 43, 45, 44, 46, 39, 32, 95, 32, 124, 44, 45, 45, 46, 39, 124, 32, 32, 32, 32, 32, 32, 32, 32, 32, 44, 45, 45, 45, 44, 10, 32, 32, 44, 45, 45, 45, 45, 46, 95, 44, 46, 32, 32, 39, 32, 32, 32, 44, 39, 92, 32, 32, 32, 44, 39, 32, 32, 46, 39, 32, 124, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 124, 32, 32, 32, 124, 32, 58, 32, 32, 44, 45, 43, 45, 46, 32, 59, 32, 32, 32, 44, 32, 124, 124, 124, 32, 32, 124, 44, 32, 32, 32, 32, 32, 32, 44, 45, 43, 45, 46, 32, 47, 32, 32, 124, 10, 32, 47, 32, 32, 32, 47, 32, 32, 39, 32, 47, 32, 47, 32, 32, 32, 47, 32, 32, 32, 124, 44, 45, 45, 45, 46, 39, 32, 32, 32, 44, 32, 44, 45, 45, 46, 45, 45, 46, 32, 32, 32, 32, 32, 32, 124, 32, 32, 32, 124, 32, 124, 32, 44, 45, 45, 46, 39, 124, 39, 32, 32, 32, 124, 32, 32, 124, 124, 96, 45, 45, 39, 95, 32, 32, 32, 32, 32, 44, 45, 45, 46, 39, 124, 39, 32, 32, 32, 124, 10, 124, 32, 32, 32, 58, 32, 32, 32, 32, 32, 124, 46, 32, 32, 32, 59, 32, 44, 46, 32, 58, 124, 32, 32, 32, 124, 32, 32, 32, 32, 124, 47, 32, 32, 32, 32, 32, 32, 32, 92, 32, 32, 32, 44, 45, 45, 46, 95, 95, 124, 32, 124, 124, 32, 32, 32, 124, 32, 32, 44, 39, 44, 32, 124, 32, 32, 124, 44, 44, 39, 32, 44, 39, 124, 32, 32, 32, 124, 32, 32, 32, 124, 32, 32, 44, 34, 39, 32, 124, 10, 124, 32, 32, 32, 124, 32, 46, 92, 32, 32, 46, 39, 32, 32, 32, 124, 32, 124, 58, 32, 58, 58, 32, 32, 32, 58, 32, 32, 46, 39, 46, 45, 45, 46, 32, 32, 46, 45, 46, 32, 124, 32, 47, 32, 32, 32, 44, 39, 32, 32, 32, 124, 124, 32, 32, 32, 124, 32, 47, 32, 32, 124, 32, 124, 45, 45, 39, 32, 39, 32, 32, 124, 32, 124, 32, 32, 32, 124, 32, 32, 32, 124, 32, 47, 32, 32, 124, 32, 124, 10, 46, 32, 32, 32, 59, 32, 39, 59, 32, 32, 124, 39, 32, 32, 32, 124, 32, 46, 59, 32, 58, 58, 32, 32, 32, 124, 46, 39, 32, 32, 32, 92, 95, 95, 92, 47, 58, 32, 46, 32, 46, 46, 32, 32, 32, 39, 32, 32, 47, 32, 32, 124, 124, 32, 32, 32, 58, 32, 124, 32, 32, 124, 32, 44, 32, 32, 32, 32, 124, 32, 32, 124, 32, 58, 32, 32, 32, 124, 32, 32, 32, 124, 32, 124, 32, 32, 124, 32, 124, 10, 39, 32, 32, 32, 46, 32, 32, 32, 46, 32, 124, 124, 32, 32, 32, 58, 32, 32, 32, 32, 124, 96, 45, 45, 45, 39, 32, 32, 32, 32, 32, 44, 34, 32, 46, 45, 45, 46, 59, 32, 124, 39, 32, 32, 32, 59, 32, 124, 58, 32, 32, 124, 124, 32, 32, 32, 58, 32, 124, 32, 32, 124, 47, 32, 32, 32, 32, 32, 39, 32, 32, 58, 32, 124, 95, 95, 32, 124, 32, 32, 32, 124, 32, 124, 32, 32, 124, 47, 10, 32, 96, 45, 45, 45, 96, 45, 39, 124, 32, 124, 32, 92, 32, 32, 32, 92, 32, 32, 47, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 32, 32, 47, 32, 32, 44, 46, 32, 32, 124, 124, 32, 32, 32, 124, 32, 39, 47, 32, 32, 39, 124, 32, 32, 32, 124, 32, 124, 96, 45, 39, 32, 32, 32, 32, 32, 32, 124, 32, 32, 124, 32, 39, 46, 39, 124, 124, 32, 32, 32, 124, 32, 124, 45, 45, 39, 10, 32, 46, 39, 95, 95, 47, 92, 95, 58, 32, 124, 32, 32, 96, 45, 45, 45, 45, 39, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 59, 32, 32, 58, 32, 32, 32, 46, 39, 32, 32, 32, 92, 32, 32, 32, 58, 32, 32, 32, 32, 58, 124, 124, 32, 32, 32, 59, 47, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 59, 32, 32, 58, 32, 32, 32, 32, 59, 124, 32, 32, 32, 124, 47, 10, 32, 124, 32, 32, 32, 58, 32, 32, 32, 32, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 124, 32, 32, 44, 32, 32, 32, 32, 32, 46, 45, 46, 47, 92, 32, 32, 32, 92, 32, 32, 47, 32, 32, 39, 45, 45, 45, 39, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 124, 32, 32, 44, 32, 32, 32, 47, 32, 39, 45, 45, 45, 39, 10, 32, 32, 92, 32, 32, 32, 92, 32, 32, 47, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 96, 45, 45, 96, 45, 45, 45, 39, 32, 32, 32, 32, 32, 96, 45, 45, 45, 45, 39, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 96, 45, 39, 10, 32, 32, 32, 96, 45, 45, 96, 45, 39, 10} diff --git a/common/global/redis_prefix.go b/common/global/redis_prefix.go new file mode 100644 index 0000000..9549576 --- /dev/null +++ b/common/global/redis_prefix.go @@ -0,0 +1,69 @@ +package global + +const ( + //现货 + SPOT = "1" + //合约 + FUT = "2" + //现货-24h行情 {交易所类型code,交易对名称} + TICKER_SPOT = "tc_spot:%s:%s" + //合约-24h行情 {交易所类型code,交易对名称} + TICKER_FUTURES = "tc_fut:%s:%s" + //合约-资金费率 + FUNDING_INFO_FUTURES = "fi_fut" + //合约-k线 + K_FUT = "k_fut" + //现货-k线 + K_SPOT = "k_spot" + //代币-配置 + COIN_DETAIL = "c_spot_detail" + //代币-现货24h涨跌幅 + COIN_PRICE_CHANGE = "c_spot_priceChange" + //代币-现货热门排序 + COIN_HOT_SORT = "c_spot_hot_sort" + //代币-现货新币排序 + COIN_NEW_SORT = "c_spot_new_sort" + //代币-现货主流排序 + COIN_MAIN_SORT = "c_spot_main_sort" + + //代币-合约配置 + COIN_FUTURES_DETAIL = "c_fut_detail" + //代币-合约24h涨跌幅 + COIN_FUTURES_PRICE_CHANGE = "c_fut_priceChange" + //代币-合约热门排序 + COIN_FUTURES_HOT_SORT = "c_fut_hot_sort" + //代币-合约新币排序 + COIN_FUTURES_NEW_SORT = "c_fut_new_sort" + //代币-合约主流排序 + COIN_FUTURES_MAIN_SORT = "c_fut_main_sort" + + //U本位合约-24h涨跌幅 + UFUTURES_PRICE_CHANGE = "ufut_priceChange" + //币本位合约-24h涨跌幅 + CFUTURES_PRICE_CHANGE = "cfut_priceChange" + + //用户订阅信息 + USER_SUBSCRIBE = "user_sub:%s" //{apikey} + + //币种配置 + RECHARGE_COIN = "recharge_coin" +) + +const ( + //参数管理 + SYS_CONFIG = "sys_config" + //字典数据 + DICT_DATA_PREFIX = "dic_list" +) + +const ( + //api websocket 错误信息 + API_WEBSOCKET_ERR = "api_ws_err:%s" +) + +const ( + //交易对-现货 + SYMBOL_SPOT = 0 + //交易对-合约 + SYMBOL_FUTURES = 1 +) diff --git a/common/global/state.go b/common/global/state.go new file mode 100644 index 0000000..4cc40ac --- /dev/null +++ b/common/global/state.go @@ -0,0 +1,60 @@ +package global + +const ( + //币种配置-开启交易 + VTS_COIN_TRADE_Y = 1 + //币种配置-关闭交易 + VTS_COIN_TRADE_N = 2 + + //币种配置-已上架 + VTS_STATUS_Y = 3 + //币种配置-未上架 + VTS_STATUS_N = 1 + + //币种配置-显示在首页-是 + VTS_SHOW_HOME_Y = 1 + //币种配置-显示在首页-否 + VTS_SHOW_HOME_N = 2 + + // 币种配置-现货 + VTS_COIN_CATEGORY_SPOT = 0 + // 币种配置-合约 + VTS_COIN_CATEGORY_FUTURES = 1 +) + +const ( + //api 启用 + AD_API_STATUS_Y = 1 + //api 禁用 + AD_API_STATUS_N = 2 +) + +const ( + //代理-http + PROXY_TYPE_HTTP = "http" + ////代理-https + PROXY_TYPE_HTTPS = "https" + ////代理-socks5 + PROXY_TYPE_SOCKS5 = "socks5" +) + +const ( + //充值内容 线上钱包 + TRAN_TYPE_WALLET = 1 + //充值类型 内部 + TRAN_TYPE_ADMIN = 2 +) + +const ( + //资金账户状态-正常 + OTC_HOLD_STATUS_NOMAL = 3 + //资金账户状态-冻结 + OTC_HOLD_STATUS_FREEZEN = 1 +) + +const ( + //充值确认状态-未确认 + RECHARGE_CONFIRM_STATUS_UNCONFIRMED = "un_confirm" + //充值确认状态-已确认 + RECHARGE_CONFIRM_STATUS_CONFIRMED = "confirmed" +) diff --git a/common/global/topic.go b/common/global/topic.go new file mode 100644 index 0000000..60e1d34 --- /dev/null +++ b/common/global/topic.go @@ -0,0 +1,7 @@ +package global + +const ( + LoginLog = "login_log_queue" + OperateLog = "operate_log_queue" + ApiCheck = "api_check_queue" +) diff --git a/common/helper/binancehttphelper.go b/common/helper/binancehttphelper.go new file mode 100644 index 0000000..c108a07 --- /dev/null +++ b/common/helper/binancehttphelper.go @@ -0,0 +1,282 @@ +package helper + +import ( + "crypto/hmac" + "crypto/sha256" + "crypto/tls" + "encoding/hex" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "reflect" + "strconv" + "strings" + "time" + + "github.com/shopspring/decimal" +) + +type BinanceClient struct { + APIKey string + APISecret string + HTTPClient *http.Client + SpotBaseURL string //现货api地址 + FutBaseURL string //合约api地址 +} + +// NewBinanceClient creates a new Binance client +func NewBinanceClient(apiKey, apiSecret string, proxyType, proxyAddr string) (*BinanceClient, error) { + // Create HTTP client with transport settings + client := &http.Client{ + Transport: &http.Transport{ + MaxIdleConns: 1000, + IdleConnTimeout: 10 * time.Second, // 设置超时 10 秒 + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } + err := CreateHtppProxy(proxyType, proxyAddr, client) + + if err != nil { + return nil, err + } + + return &BinanceClient{ + APIKey: apiKey, + APISecret: apiSecret, + HTTPClient: client, + SpotBaseURL: "https://api.binance.com", + FutBaseURL: "https://fapi.binance.com", + }, nil +} + +// Helper to get proxy password from URL +func getProxyPassword(proxyURL *url.URL) string { + if password, ok := proxyURL.User.Password(); ok { + return password + } + return "" +} + +// signRequest generates HMAC SHA256 signature +func (bc *BinanceClient) signRequest(query string) string { + mac := hmac.New(sha256.New, []byte(bc.APISecret)) + mac.Write([]byte(query)) + return hex.EncodeToString(mac.Sum(nil)) +} + +/* +binance 现货请求 + + - @endpoint 路由 + - @method get、post + - @params 参数map +*/ +func (bc *BinanceClient) SendSpotRequest(endpoint string, method string, params map[string]string) ([]byte, int, error) { + // Prepare URL + reqURL := bc.SpotBaseURL + endpoint + + return bc.SendRequest(reqURL, method, params, 0) +} + +/* +binance 现货请求 + + - @endpoint 路由 + - @method get、post + - @params 参数map +*/ +func (bc *BinanceClient) SendSpotRequestAuth(endpoint string, method string, params map[string]string) ([]byte, int, error) { + // Prepare URL + reqURL := bc.SpotBaseURL + endpoint + + return bc.SendRequest(reqURL, method, params, 2) +} + +/* +binance 现货请求 + + - @endpoint 路由 + - @method get、post + - @params 参数map +*/ +func (bc *BinanceClient) SendSpotAuth(endpoint string, method string, params interface{}) ([]byte, int, error) { + // Prepare URL + reqURL := bc.SpotBaseURL + endpoint + + return bc.SendRequest(reqURL, method, params, 2) +} + +/* +binance 现货请求 只需要api key + + - @endpoint 路由 + - @method get、post + - @params 参数map +*/ +func (bc *BinanceClient) SendSpotRequestByKey(endpoint string, method string, params interface{}) ([]byte, int, error) { + // Prepare URL + reqURL := bc.SpotBaseURL + endpoint + + return bc.SendRequest(reqURL, method, params, 1) +} + +/* +binance 合约请求 + + - @endpoint 路由 + - @method get、post + - @params 参数map +*/ +func (bc *BinanceClient) SendFuturesRequest(endpoint string, method string, params map[string]string) ([]byte, int, error) { + // Prepare URL + reqURL := bc.FutBaseURL + endpoint + + return bc.SendRequest(reqURL, method, params, 0) +} + +/* +binance 合约请求 + + - @endpoint 路由 + - @method get、post + - @params 参数map +*/ +func (bc *BinanceClient) SendFuturesRequestAuth(endpoint string, method string, params map[string]string) ([]byte, int, error) { + // Prepare URL + reqURL := bc.FutBaseURL + endpoint + + return bc.SendRequest(reqURL, method, params, 2) +} + +/* +binance 合约请求 + + - @endpoint 路由 + - @method get、post + - @params 参数map +*/ +func (bc *BinanceClient) SendFuturesRequestByKey(endpoint string, method string, params map[string]string) ([]byte, int, error) { + // Prepare URL + reqURL := bc.FutBaseURL + endpoint + + return bc.SendRequest(reqURL, method, params, 1) +} + +/* +binance 合约请求 + + - @endpoint 路由 + - @method get、post + - @params 参数map +*/ +func (bc *BinanceClient) SendFuturesAuth(endpoint string, method string, params interface{}) ([]byte, int, error) { + // Prepare URL + reqURL := bc.FutBaseURL + endpoint + + return bc.SendRequest(reqURL, method, params, 2) +} + +// SendRequest sends a request to Binance API +/* +发送请求 + - auth 0-不需要 1-只需要key 2-需要key和签名 +*/ +func (bc *BinanceClient) SendRequest(reqURL string, method string, params interface{}, auth int) ([]byte, int, error) { + method = strings.ToUpper(method) + reqParams := url.Values{} + + // 处理 `params`,如果是 map[string]string 则添加为 URL 参数 + if paramMap, ok := params.(map[string]string); ok { + for k, v := range paramMap { + reqParams.Add(k, v) + } + } else if v := reflect.ValueOf(params); v.Kind() == reflect.Struct { + for i := 0; i < v.NumField(); i++ { + field := v.Type().Field(i) + value := v.Field(i) + + // 获取字段名或 JSON 标签名 + key := field.Tag.Get("json") + if key == "" { + key = field.Name // 如果没有 json 标签,使用字段名 + } + + // 检查字段类型并转换为对应的字符串 + var strValue string + switch value.Kind() { + case reflect.String: + strValue = value.String() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + strValue = strconv.FormatInt(value.Int(), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + strValue = strconv.FormatUint(value.Uint(), 10) + case reflect.Float32, reflect.Float64: + strValue = strconv.FormatFloat(value.Float(), 'f', -1, 64) + case reflect.Bool: + strValue = strconv.FormatBool(value.Bool()) + case reflect.Struct: + // 处理 decimal.Decimal 类型 + if value.Type() == reflect.TypeOf(decimal.Decimal{}) { + strValue = value.Interface().(decimal.Decimal).String() + } else { + continue // 跳过其他 struct 类型 + } + default: + continue // 跳过不支持的类型 + } + // 添加到 reqParams + reqParams.Add(key, strValue) + } + } + + // Add timestamp if signature is needed + if auth == 2 && bc.APIKey != "" && bc.APISecret != "" { + reqParams.Add("timestamp", fmt.Sprintf("%d", time.Now().UnixMilli())) + signature := bc.signRequest(reqParams.Encode()) + reqParams.Add("signature", signature) + } + // Create HTTP request + var req *http.Request + var err error + if method == http.MethodGet || method == http.MethodDelete { + if len(reqParams) > 0 { + reqURL = fmt.Sprintf("%s?%s", reqURL, reqParams.Encode()) + } + req, err = http.NewRequest(method, reqURL, nil) + } else { + // req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + if len(reqParams) > 0 { + reqURL = fmt.Sprintf("%s?%s", reqURL, reqParams.Encode()) + } + req, err = http.NewRequest(method, reqURL, nil) + } + if err != nil { + return nil, -1, fmt.Errorf("failed to create request: %w", err) + } + // Set headers + if auth > 0 && bc.APIKey != "" { + req.Header.Set("X-MBX-APIKEY", bc.APIKey) + } + // req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + // Send request + resp, err := bc.HTTPClient.Do(req) + if err != nil { + return nil, -1, fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + // Read response + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, -1, fmt.Errorf("failed to read response: %w", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, resp.StatusCode, fmt.Errorf("%s", body) + } + + return body, http.StatusOK, nil +} diff --git a/common/helper/cache_helper.go b/common/helper/cache_helper.go new file mode 100644 index 0000000..f65bb55 --- /dev/null +++ b/common/helper/cache_helper.go @@ -0,0 +1,139 @@ +package helper + +import ( + "fmt" + "time" + + "github.com/bytedance/sonic" + "github.com/go-redis/redis/v8" +) + +// CacheFunction 通用缓存装饰器,支持任意函数类型 +func CacheFunctionExpire[T any](fn func(args ...any) (T, error), keyPrefix string, ttl time.Duration) func(args ...any) (T, error) { + return func(args ...any) (T, error) { + // 创建缓存键,基于函数名和参数 + cacheKey := fmt.Sprintf("%s:%v", keyPrefix, args) + + // 尝试从 Redis 获取缓存 + cachedData, err := DefaultRedis.GetString(cacheKey) + var zeroT T // 用于返回零值 + if err == redis.Nil { + // 缓存不存在,调用实际函数 + result, err := fn(args...) + if err != nil { + return zeroT, err + } + + // 将结果序列化并存入 Redis + jsonData, err := sonic.Marshal(result) + if err != nil { + return zeroT, err + } + + err = DefaultRedis.SetStringExpire(cacheKey, string(jsonData), ttl) + if err != nil { + return zeroT, err + } + + return result, nil + } else if err != nil { + return zeroT, err + } + + // 缓存命中,反序列化结果 + var result T + err = sonic.Unmarshal([]byte(cachedData), &result) + if err != nil { + return zeroT, err + } + + return result, nil + } +} + +// CacheFunction 通用缓存装饰器,支持任意函数类型 +func CacheFunctionNoArgsExpire[T any](fn func() (T, error), keyPrefix string, ttl time.Duration) func() (T, error) { + return func() (T, error) { + // 创建缓存键,基于函数名和参数 + cacheKey := keyPrefix + + // 尝试从 Redis 获取缓存 + cachedData, err := DefaultRedis.GetString(cacheKey) + var zeroT T // 用于返回零值 + if err == redis.Nil { + // 缓存不存在,调用实际函数 + result, err := fn() + if err != nil { + return zeroT, err + } + + // 将结果序列化并存入 Redis + jsonData, err := sonic.Marshal(result) + if err != nil { + return zeroT, err + } + + err = DefaultRedis.SetStringExpire(cacheKey, string(jsonData), ttl) + if err != nil { + return zeroT, err + } + + return result, nil + } else if err != nil { + return zeroT, err + } + + // 缓存命中,反序列化结果 + var result T + err = sonic.Unmarshal([]byte(cachedData), &result) + if err != nil { + return zeroT, err + } + + return result, nil + } +} + +// DeleteCacheFunction 通用删除缓存装饰器 +func DeleteCacheFunction[T any](fn func(args ...any) (T, error), keyPrefix string) func(args ...any) (T, error) { + return func(args ...any) (T, error) { + // 调用实际函数 + result, err := fn(args...) + if err != nil { + return result, err + } + + // 创建缓存键,基于函数名和参数 + cacheKey := fmt.Sprintf("%s:%v", keyPrefix, args) + + // 从 Redis 删除缓存 + err = DefaultRedis.DeleteString(cacheKey) + if err != nil { + return result, fmt.Errorf("failed to delete cache: %w", err) + } + + return result, nil + } +} + +// DeleteCacheFunction 通用删除缓存装饰器 +func DeleteCacheNoArgsFunction[T any](fn func() (T, error), keyPrefix string) func() (T, error) { + return func() (T, error) { + // 调用实际函数 + result, err := fn() + if err != nil { + return result, err + } + + // 创建缓存键,基于函数名和参数 + cacheKey := keyPrefix + + // 从 Redis 删除缓存 + err = DefaultRedis.DeleteString(cacheKey) + if err != nil { + return result, fmt.Errorf("failed to delete cache: %w", err) + } + + return result, nil + } +} diff --git a/common/helper/proxy.go b/common/helper/proxy.go new file mode 100644 index 0000000..c76a95f --- /dev/null +++ b/common/helper/proxy.go @@ -0,0 +1,79 @@ +package helper + +import ( + "context" + "errors" + "fmt" + "net" + "net/http" + "net/url" + "strings" + + "golang.org/x/net/proxy" +) + +/* +创建代理 + + - @proxyType 代理类别 http\https\socks5 + - @proxyAddr 代理地址 userName:password@endpoint:port +*/ +func CreateHtppProxy(proxyType, proxyAddr string, client *http.Client) error { + // Set up proxy based on type (HTTP, HTTPS, SOCKS5) + transport := &http.Transport{} + if proxyAddr != "" { + if !strings.HasPrefix(proxyAddr, "http://") && !strings.HasPrefix(proxyAddr, "https://") && !strings.HasPrefix(proxyAddr, "socks5://") { + proxyAddr = proxyType + "://" + proxyAddr + } + + if proxyType == "" { + proxyType = strings.Split(proxyAddr, "://")[0] + } + + switch proxyType { + case "http", "https": + proxyURL, err := url.Parse(proxyAddr) + if err != nil { + return errors.New(fmt.Sprintf("invalid proxy URL: %w", err)) + } + // Check if proxy URL contains credentials + if proxyURL.User != nil { + username := proxyURL.User.Username() + password, _ := proxyURL.User.Password() + transport.Proxy = func(req *http.Request) (*url.URL, error) { + req.SetBasicAuth(username, password) // Set basic auth headers + return proxyURL, nil + } + } else { + transport.Proxy = http.ProxyURL(proxyURL) + } + case "socks5": + proxyURL, err := url.Parse(proxyAddr) + if err != nil { + return errors.New(fmt.Sprintf("invalid proxy URL: %w", err)) + } + + var auth *proxy.Auth + if proxyURL.User != nil { + auth = &proxy.Auth{ + User: proxyURL.User.Username(), + Password: getProxyPassword(proxyURL), + } + } + + dialer, err := proxy.SOCKS5("tcp", proxyURL.Host, auth, proxy.Direct) + if err != nil { + return errors.New(fmt.Sprintf("failed to set up SOCKS5 proxy: %w", err)) + } + transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + return dialer.Dial(network, addr) + } + default: + return errors.New(fmt.Sprintf("unsupported proxy type: %s", proxyType)) + } + + client.Transport = transport + } + + return nil +} diff --git a/common/helper/redis_helper.go b/common/helper/redis_helper.go new file mode 100644 index 0000000..903976a --- /dev/null +++ b/common/helper/redis_helper.go @@ -0,0 +1,695 @@ +package helper + +import ( + "context" + "errors" + "fmt" + "log" + "reflect" + "strconv" + "time" + + "github.com/bytedance/sonic" + "github.com/go-redis/redis/v8" +) + +// RedisHelper 结构体封装了 Redis 客户端及上下文 +type RedisHelper struct { + client *redis.Client // Redis 客户端 + ctx context.Context // 上下文 + emptyCacheValue string // 缓存空值的标志 +} + +var DefaultRedis *RedisHelper + +// 初始化默认链接 +func InitDefaultRedis(addr, password string, db int) { + if DefaultRedis == nil { + DefaultRedis = NewRedisHelper(addr, password, db) + } + + log.Printf("初始化redis链接") +} + +// NewRedisHelper 创建一个新的 RedisHelper 实例 +func NewRedisHelper(addr, password string, db int) *RedisHelper { + rdb := redis.NewClient(&redis.Options{ + Addr: addr, // Redis 服务器地址 + Password: password, // Redis 密码 + DB: db, // 使用的数据库编号 + PoolSize: 50, + MinIdleConns: 10, + DialTimeout: 10 * time.Second, // 调整连接超时时间 + ReadTimeout: 10 * time.Second, // 调整读超时时间 + WriteTimeout: 10 * time.Second, // 调整写超时时间 + }) + + return &RedisHelper{ + client: rdb, + ctx: context.Background(), // 创建背景上下文 + } +} + +// 测试连接 +func (r *RedisHelper) Ping() error { + return r.client.Ping(r.ctx).Err() +} + +// SetString 设置字符串值 +func (r *RedisHelper) SetString(key, value string) error { + return r.client.Set(r.ctx, key, value, 0).Err() // 将值存储到指定的键 +} + +// 批量设置 +func (r *RedisHelper) BatchSet(maps *map[string]string) error { + pipe := r.client.Pipeline() + + for key, val := range *maps { + pipe.Set(r.ctx, key, val, 0) + } + + _, err := pipe.Exec(r.ctx) + + return err +} + +// SetString 设置字符串值 +func (r *RedisHelper) SetStringExpire(key, value string, expireTime time.Duration) error { + return r.client.Set(r.ctx, key, value, expireTime).Err() // 将值存储到指定的键 +} + +// SetString 设置字符串值 +func (r *RedisHelper) SetAdd(key, value string, expireTime time.Duration) error { + // 存储到 SET 中 + result, err := r.client.SAdd(r.ctx, key, value).Result() + if err != nil { + return err + } + + if result == 1 { + // 设置 SET 的过期时间 + err = r.client.Expire(r.ctx, key, expireTime).Err() + if err != nil { + return errors.New("设置过期时间失败:" + err.Error()) + } + + return nil + } else { + return errors.New("key已存在") + } +} + +// 设置对象 +func SetObjString[T any](r *RedisHelper, key string, value T) error { + keyValue, err := sonic.Marshal(value) + + if err != nil { + return err + } + + return r.SetString(key, string(keyValue)) +} + +// 获取对象 +func GetObjString[T any](r *RedisHelper, key string) (T, error) { + var result T + value, err := r.GetString(key) + + if err != nil { + return result, err + } + + err = sonic.Unmarshal([]byte(value), &result) + + if err != nil { + return result, err + } + + return result, nil +} + +func (r *RedisHelper) Get(key string) *redis.StringCmd { + return r.client.Get(r.ctx, key) +} + +/* +获取剩余时间 + - @key redis key +*/ +func (r *RedisHelper) TTL(key string) *redis.DurationCmd { + return r.client.TTL(r.ctx, key) +} + +// GetString 获取字符串值 +func (r *RedisHelper) GetString(key string) (string, error) { + return r.client.Get(r.ctx, key).Result() // 从指定的键获取值 +} + +// DeleteString 删除字符串键 +func (r *RedisHelper) DeleteString(key string) error { + return r.client.Del(r.ctx, key).Err() // 删除指定的键 +} + +// DeleteString 删除目录下所有key +func (r *RedisHelper) DeleteAll(key string) error { + keys, err := r.ScanKeys(key) + + if err != nil { + return err + } + + _, err = r.BatchDeleteKeys(keys) + return err +} + +/* +递增 + - @key rediskey +*/ +func (r *RedisHelper) Incr(key string) *redis.IntCmd { + return r.client.Incr(r.ctx, key) +} + +/* +设置过期时间 + - @key redis key + - @expiration 过期时间 +*/ +func (r *RedisHelper) Expire(key string, expiration time.Duration) *redis.BoolCmd { + return r.client.Expire(r.ctx, key, expiration) +} + +/* +批量删除 + + - @keys 键数组 +*/ +func (r *RedisHelper) BatchDeleteKeys(keys []string) (int, error) { + if r.client == nil { + return 0, errors.New("Redis client is nil") + } + if len(keys) == 0 { + return 0, nil + } + + deletedCount := 0 + batchSize := 1000 // 每批次删除的键数量 + for i := 0; i < len(keys); i += batchSize { + end := i + batchSize + if end > len(keys) { + end = len(keys) + } + batch := keys[i:end] + + _, err := r.client.Pipelined(r.ctx, func(pipe redis.Pipeliner) error { + for _, key := range batch { + pipe.Del(r.ctx, key) + deletedCount++ + } + return nil + }) + if err != nil { + return deletedCount, fmt.Errorf("failed to delete keys in batch: %v", err) + } + } + + return deletedCount, nil +} + +// DeleteKeysByPrefix 删除指定前缀的键 +func (r *RedisHelper) DeleteKeysByPrefix(prefixes ...string) error { + ctx := context.Background() + // 遍历每个前缀 + for _, prefix := range prefixes { + var cursor uint64 + var keys []string + + // 使用 SCAN 命令查找匹配的键 + for { + var err error + keys, cursor, err = r.client.Scan(ctx, cursor, prefix+"*", 1000).Result() + if err != nil { + return err + } + + // 删除匹配的键 + if len(keys) > 0 { + _, err := r.client.Del(ctx, keys...).Result() + if err != nil { + return err + } + fmt.Printf("Deleted keys with prefix '%s': %v\n", prefix, keys) + } + + // 如果游标为 0,表示迭代结束 + if cursor == 0 { + break + } + } + } + return nil +} + +// 查找所有value +func (r *RedisHelper) GetAllKeysAndValues(pattern string) ([]string, error) { + var cursor uint64 + var result = []string{} + + for { + // 使用 SCAN 命令获取匹配的键 + keys, nextCursor, err := r.client.Scan(r.ctx, cursor, pattern+"*", 1000).Result() + if err != nil { + log.Printf("Error scanning keys: %v", err) + return nil, err + } + + // 处理匹配到的键 + for _, key := range keys { + value, err := r.client.Get(r.ctx, key).Result() + if err != nil { + if err == redis.Nil { + fmt.Printf("Key %s does not exist\n", key) + } else { + fmt.Printf("Error getting value for key %s: %v", key, err) + } + } else { + result = append(result, value) + } + } + + // 如果 cursor 为 0,表示扫描完成 + if nextCursor == 0 { + break + } + cursor = nextCursor + } + + return result, nil +} + +// LPushList 将一个或多个值插入到列表的头部 +func (r *RedisHelper) LPushList(key string, values ...string) error { + return r.client.LPush(r.ctx, key, values).Err() // 将值插入到列表的头部 +} + +// RPushList 将一个或多个值插入到列表的尾部 +func (r *RedisHelper) RPushList(key string, values ...string) error { + return r.client.RPush(r.ctx, key, values).Err() // 将值插入到列表的尾部 +} + +// LPopList 从列表的头部弹出一个元素 +func (r *RedisHelper) LPopList(key string) (string, error) { + return r.client.LPop(r.ctx, key).Result() // 从列表的头部移除并返回第一个元素 +} + +// RPopList 从列表的尾部弹出一个元素 +func (r *RedisHelper) RPopList(key string) (string, error) { + return r.client.RPop(r.ctx, key).Result() // 从列表的尾部移除并返回最后一个元素 +} + +// LRangeList 获取列表中指定范围的元素 +func (r *RedisHelper) LRangeList(key string, start, stop int64) ([]string, error) { + return r.client.LRange(r.ctx, key, start, stop).Result() // 获取列表中指定范围的元素 +} + +// GetAllList 获取列表中的所有元素 +func (r *RedisHelper) GetAllList(key string) ([]string, error) { + values, err := r.client.LRange(r.ctx, key, 0, -1).Result() + if err == redis.Nil { + return nil, nil + } else if err != nil { + return nil, err + } + + // 检查是否包含空值标志 + if len(values) == 1 && values[0] == r.emptyCacheValue { + return nil, nil + } + + return values, nil +} + +func (r *RedisHelper) LRem(key, val string) (int64, error) { + count := 0 // 删除所有与 valueToRemove 相等的元素 + result, err := r.client.LRem(r.ctx, key, int64(count), val).Result() + if err != nil { + fmt.Printf("删除元素失败: %v\n", err) + } + + return result, nil +} + +func (r *RedisHelper) IsElementInList(key string, element string) (bool, error) { + var cursor int64 = 0 + const batchSize int64 = 1000 // 每批次获取的元素数量 + + for { + // 分批次获取列表元素 + elements, err := r.client.LRange(r.ctx, key, cursor, cursor+batchSize-1).Result() + if err != nil { + return false, err + } + if len(elements) == 0 { + break // 没有更多数据 + } + + // 遍历当前批次的元素 + for _, e := range elements { + if e == element { + return true, nil + } + } + + cursor += batchSize // 移动到下一批次 + } + + return false, nil +} + +/* +SetListCache 重新设置列表缓存 + - @expiration 0-过期 1-过期时间 +*/ +func (r *RedisHelper) SetListCache(key string, expiration time.Duration, values ...string) error { + tempKey := key + ":temp" + + // 使用事务来确保操作的原子性 + pipe := r.client.TxPipeline() + + // 将新数据插入到临时列表中 + pipe.RPush(r.ctx, tempKey, values) + + // 重命名临时列表为目标列表 + pipe.Rename(r.ctx, tempKey, key) + + if expiration > 0 { + // 设置目标列表的过期时间 + pipe.Expire(r.ctx, key, expiration) + } + + // 执行事务 + _, err := pipe.Exec(r.ctx) + return err +} + +// SetEmptyListCache 设置空值缓存 +func (r *RedisHelper) SetEmptyListCache(key string, expiration time.Duration) error { + // 使用一个特殊标志值表示列表为空 + _, err := r.client.RPush(r.ctx, key, r.emptyCacheValue).Result() + if err != nil { + return err + } + + // 设置列表的过期时间 + return r.client.Expire(r.ctx, key, expiration).Err() +} + +// scanKeys 使用 SCAN 命令获取所有匹配的键 +func (r *RedisHelper) ScanKeys(pattern string) ([]string, error) { + + var cursor uint64 + var keys []string + for { + var newKeys []string + var err error + + // SCAN 命令每次返回部分匹配的键 + newKeys, cursor, err = r.client.Scan(r.ctx, cursor, pattern, 1000).Result() + if err != nil { + return nil, err + } + keys = append(keys, newKeys...) + if cursor == 0 { + break + } + } + return keys, nil +} + +// 泛型函数,用于获取所有键的列表数据并合并为一个数组 +func GetAndMergeLists[T any](r *RedisHelper, keys []string) ([]T, error) { + var combinedList []T + for _, key := range keys { + // 获取每个键的列表数据 + listData, err := r.client.LRange(r.ctx, key, 0, -1).Result() + if err != nil { + return nil, err + } + + // 解码每个数据项为类型 T,并添加到结果列表中 + for _, data := range listData { + var item T + if err := sonic.Unmarshal([]byte(data), &item); err != nil { + return nil, err + } + combinedList = append(combinedList, item) + } + } + return combinedList, nil +} + +// SetNX 实现类似于 Redis 的 SETNX 命令 +func (r *RedisHelper) SetNX(key string, value interface{}, expiration time.Duration) (bool, error) { + result, err := r.client.Set(r.ctx, key, value, expiration).Result() + if err != nil { + return false, err + } + + // 如果键不存在则 result 会等于 "OK" + return result == "OK", nil +} + +func getFieldsFromStruct(obj interface{}) map[string]interface{} { + fields := make(map[string]interface{}) + val := reflect.ValueOf(obj) + typ := reflect.TypeOf(obj) + + for i := 0; i < val.NumField(); i++ { + field := typ.Field(i) + tag := field.Tag.Get("redis") + if tag != "" { + fieldVal := val.Field(i) + if fieldVal.Kind() == reflect.Slice || fieldVal.Kind() == reflect.Map { + // 处理切片或映射类型 + // 对于切片,使用索引作为字段名 + if fieldVal.Kind() == reflect.Slice { + for j := 0; j < fieldVal.Len(); j++ { + elem := fieldVal.Index(j).Interface() + fields[fmt.Sprintf("%s_%d", tag, j)] = elem + } + } else if fieldVal.Kind() == reflect.Map { + // 对于映射,使用键作为字段名 + for _, key := range fieldVal.MapKeys() { + elem := fieldVal.MapIndex(key).Interface() + fields[fmt.Sprintf("%s_%v", tag, key.Interface())] = elem + } + } + } else { + fields[tag] = fieldVal.Interface() + } + } + } + + return fields +} + +func (r *RedisHelper) SetHashWithTags(key string, obj interface{}) error { + fields := getFieldsFromStruct(obj) + _, err := r.client.HSet(r.ctx, key, fields).Result() + return err +} + +// HSetField 设置哈希中的一个字段 +func (r *RedisHelper) HSetField(key, field string, value interface{}) error { + _, err := r.client.HSet(r.ctx, key, field, value).Result() + if err != nil { + log.Printf("Error setting field %s in hash %s: %v", field, key, err) + return err + } + return nil +} + +// HSetMultipleFields 设置哈希中的多个字段 +func (r *RedisHelper) HSetMultipleFields(key string, fields map[string]interface{}) error { + _, err := r.client.HSet(r.ctx, key, fields).Result() + if err != nil { + log.Printf("Error setting multiple fields in hash %s: %v", key, err) + return err + } + return nil +} + +// HGetField 获取哈希中某个字段的值 +func (r *RedisHelper) HGetField(key, field string) (string, error) { + val, err := r.client.HGet(r.ctx, key, field).Result() + if err != nil { + if err == redis.Nil { + return "", nil // Field does not exist + } + log.Printf("Error getting field %s from hash %s: %v", field, key, err) + return "", err + } + return val, nil +} + +// HGetAllFields 获取哈希中所有字段的值 +func (r *RedisHelper) HGetAllFields(key string) (map[string]string, error) { + fields, err := r.client.HGetAll(r.ctx, key).Result() + if err != nil { + log.Printf("Error getting all fields from hash %s: %v", key, err) + return nil, err + } + return fields, nil +} + +// HDelField 删除哈希中的某个字段 +func (r *RedisHelper) HDelField(key, field string) error { + _, err := r.client.HDel(r.ctx, key, field).Result() + if err != nil { + log.Printf("Error deleting field %s from hash %s: %v", field, key, err) + return err + } + return nil +} + +// 删除哈希 +func (r *RedisHelper) HDelAll(key string) error { + _, err := r.client.Del(r.ctx, key).Result() + if err != nil { + log.Printf("Error deleting from hash %s: %v", key, err) + return err + } + return nil +} + +// HKeys 获取哈希中所有字段的名字 +func (r *RedisHelper) HKeys(key string) ([]string, error) { + fields, err := r.client.HKeys(r.ctx, key).Result() + if err != nil { + log.Printf("Error getting keys from hash %s: %v", key, err) + return nil, err + } + return fields, nil +} + +// DelSet 从集合中删除元素 +func (r *RedisHelper) DelSet(key string, value string) error { + _, err := r.client.SRem(r.ctx, key, value).Result() + if err != nil { + log.Printf("Error del value from set %s: %v", key, err) + return err + } + return nil +} + +func (r *RedisHelper) Sismember(key string, value string) (bool, error) { + result, err := r.client.SIsMember(r.ctx, key, value).Result() + if err != nil { + log.Printf("Error Sismember value from set %s: %v", key, err) + } + return result, err +} + +// sort set start +// 批量添加 +func (r *RedisHelper) BatchSortSet(key string, array []*redis.Z) error { + pipe := r.client.Pipeline() + for _, val := range array { + pipe.ZAdd(r.ctx, key, val) + } + + _, err := pipe.Exec(r.ctx) + return err +} + +// 单一写入 sort set +func (e *RedisHelper) SignelAdd(key string, score float64, member string) error { + // 先删除具有相同 score 的所有成员 + scoreStr := strconv.FormatFloat(score, 'g', -1, 64) + _, err := e.client.ZRemRangeByScore(e.ctx, key, scoreStr, scoreStr).Result() + + if err != nil { + fmt.Printf("删除score失败", err.Error()) + } + _, err = e.client.ZAdd(e.ctx, key, &redis.Z{ + Score: score, + Member: member, + }).Result() + + if err != nil { + return err + } + + return nil +} + +// 写入数据 +func (e *RedisHelper) AddSortSet(key string, score float64, member string) error { + _, err := e.client.ZAdd(e.ctx, key, &redis.Z{ + Score: score, + Member: member, + }).Result() + + if err != nil { + return err + } + + return nil +} + +// 删除指定元素 +func (e *RedisHelper) DelSortSet(key, member string) error { + return e.client.ZRem(e.ctx, key, member).Err() +} + +/* +获取sort set 所有数据 +*/ +func (e *RedisHelper) GetAllSortSet(key string) ([]string, error) { + return e.client.ZRange(e.ctx, key, 0, -1).Result() +} + +/* +获取sort set 所有数据和score +*/ +func (e *RedisHelper) GetRevRangeScoresSortSet(key string) ([]redis.Z, error) { + return e.client.ZRevRangeWithScores(e.ctx, key, 0, -1).Result() +} + +// 获取指定区间数据 +func (e *RedisHelper) GetSortSetMembers(key string, start, stop int64) ([]string, error) { + return e.client.ZRange(e.ctx, key, start, stop).Result() +} + +// 获取最后N条数据 +func (e *RedisHelper) GetLastSortSetMembers(key string, num int64) ([]string, error) { + return e.client.ZRevRange(e.ctx, key, 0, num).Result() +} + +// func (e *RedisHelper) DelSortSet(key,) + +// 根据索引范围删除 +func (e *RedisHelper) DelByRank(key string, start, stop int64) error { + return e.client.ZRemRangeByRank(e.ctx, key, start, stop).Err() +} + +// sort set end + +// GetUserLoginPwdErrFre 获取用户登录密码错误频次 +func (e *RedisHelper) GetUserLoginPwdErrFre(key string) (total int, wait time.Duration, err error) { + total, _ = e.client.Get(e.ctx, key).Int() + wait = e.client.TTL(e.ctx, key).Val() + return +} + +// SetUserLoginPwdErrFre 设置用户登录密码错误频次 +func (e *RedisHelper) SetUserLoginPwdErrFre(key string, expire time.Duration) (val int64, err error) { + val, err = e.client.Incr(e.ctx, key).Result() + if err != nil { + return + } + if err = e.client.Expire(e.ctx, key, expire).Err(); err != nil { + return + } + return +} diff --git a/common/helper/redislock.go b/common/helper/redislock.go new file mode 100644 index 0000000..1716794 --- /dev/null +++ b/common/helper/redislock.go @@ -0,0 +1,183 @@ +package helper + +import ( + "context" + "errors" + "go-admin/pkg/utility" + "math/rand" + "strconv" + "sync" + "sync/atomic" + "time" + + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-redis/redis/v8" + "go.uber.org/zap" +) + +const ( + tolerance = 500 // milliseconds + millisPerSecond = 1000 + lockCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then + redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2]) + return "OK" +else + return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2]) +end` + delCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then + return redis.call("DEL", KEYS[1]) +else + return 0 +end` + touchCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then + return redis.call("PEXPIRE", KEYS[1], ARGV[2]) +else + return 0 +end` +) + +var ( + clientRedisLock *redis.Client + onece sync.Once + ErrFailed = errors.New("redsync: failed to acquire lock") +) + +// InitLockRedisConn 初始化 Redis 连接 +func InitLockRedisConn(host, password, dbIndex string) { + onece.Do(func() { + dbIndexInt, err := strconv.Atoi(dbIndex) + if err != nil { + dbIndexInt = 0 + } + clientRedisLock = redis.NewClient(&redis.Options{ + Addr: host, + Password: password, + DB: dbIndexInt, + }) + + // 测试连接 + if _, err := clientRedisLock.Ping(context.Background()).Result(); err != nil { + log.Error("Failed to connect to Redis", zap.Error(err)) + panic(err) + } + }) +} + +// RedisLock 分布式锁结构 +type RedisLock struct { + redisClient *redis.Client + key string + id string + expiry time.Duration + retryCount int // 重试次数 + retryInterval time.Duration // 重试间隔时间 + seconds uint32 +} + +// NewRedisLock 创建一个新的 Redis 锁 +func NewRedisLock(key string, timeout uint32, retryCount int, retryInterval time.Duration) *RedisLock { + return &RedisLock{ + redisClient: clientRedisLock, + key: key, + id: utility.GetXid(), + expiry: time.Duration(timeout) * time.Second, + retryCount: retryCount, + retryInterval: retryInterval, + seconds: timeout, + } +} + +// Acquire 获取锁 +func (rl *RedisLock) Acquire() (bool, error) { + return rl.acquireCtx(context.Background()) +} + +// AcquireWait 获取锁,支持重试和超时 +func (rl *RedisLock) AcquireWait(ctx context.Context) (bool, error) { + if ctx == nil { + ctx = context.Background() + } + + for i := 0; i < rl.retryCount; i++ { + if i > 0 { + // 指数退避 + baseInterval := time.Duration(int64(rl.retryInterval) * (1 << i)) // 指数增长 + if baseInterval > time.Second { + baseInterval = time.Second + } + + // 随机退避 + retryInterval := time.Duration(rand.Int63n(int64(baseInterval))) // 随机退避 + if retryInterval < time.Millisecond*100 { + retryInterval = time.Millisecond * 100 // 至少 100ms + } + select { + case <-ctx.Done(): + return false, ctx.Err() + case <-time.After(retryInterval): + } + } + + ok, err := rl.acquireCtx(ctx) + if ok { + return true, nil + } + if err != nil { + log.Error("Failed to acquire lock", zap.String("key", rl.key), zap.Error(err)) + } + } + return false, ErrFailed +} + +// Release 释放锁 +func (rl *RedisLock) Release() (bool, error) { + return rl.releaseCtx(context.Background()) +} + +// Touch 续期锁 +func (rl *RedisLock) Touch() (bool, error) { + return rl.touchCtx(context.Background()) +} + +// acquireCtx 获取锁的核心逻辑 +func (rl *RedisLock) acquireCtx(ctx context.Context) (bool, error) { + resp, err := rl.redisClient.Eval(ctx, lockCommand, []string{rl.key}, []string{ + rl.id, strconv.Itoa(int(rl.seconds)*millisPerSecond + tolerance), + }).Result() + if err == redis.Nil { + return false, nil + } else if err != nil { + log.Error("Error acquiring lock", zap.String("key", rl.key), zap.Error(err)) + return false, err + } + + reply, ok := resp.(string) + return ok && reply == "OK", nil +} + +// releaseCtx 释放锁的核心逻辑 +func (rl *RedisLock) releaseCtx(ctx context.Context) (bool, error) { + resp, err := rl.redisClient.Eval(ctx, delCommand, []string{rl.key}, []string{rl.id}).Result() + if err != nil { + log.Error("Error releasing lock", zap.String("key", rl.key), zap.Error(err)) + return false, err + } + + reply, ok := resp.(int64) + return ok && reply == 1, nil +} + +// touchCtx 续期锁的核心逻辑 +func (rl *RedisLock) touchCtx(ctx context.Context) (bool, error) { + seconds := atomic.LoadUint32(&rl.seconds) + resp, err := rl.redisClient.Eval(ctx, touchCommand, []string{rl.key}, []string{ + rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance), + }).Result() + if err != nil { + log.Error("Error touching lock", zap.String("key", rl.key), zap.Error(err)) + return false, err + } + + reply, ok := resp.(int64) + return ok && reply == 1, nil +} diff --git a/common/helper/snowflake.go b/common/helper/snowflake.go new file mode 100644 index 0000000..2409663 --- /dev/null +++ b/common/helper/snowflake.go @@ -0,0 +1,50 @@ +package helper + +import ( + "strconv" + "sync" + + extendConfig "go-admin/config" + + "github.com/bwmarrin/snowflake" + log "github.com/go-admin-team/go-admin-core/logger" +) + +// 全局雪花节点实例 +var ( + node *snowflake.Node + once sync.Once +) + +func InitSnowflakeNode() error { + var err error + + node, err = snowflake.NewNode(extendConfig.ExtConfig.ServiceId) + if err != nil { + return err + } + + return nil +} + +// 获取订单雪花id +func GetOrderNo() string { + if node == nil { + once.Do(func() { + if node == nil { + if err := InitSnowflakeNode(); err != nil { + log.Fatalf("初始化雪花算法节点失败: %v", err) + } + } + }) + } + + if node == nil { + log.Fatal("雪花算法节点未初始化") + } + + orderID := node.Generate() + idStr := strconv.FormatInt(orderID.Int64(), 10) + + return idStr +} diff --git a/common/ip.go b/common/ip.go new file mode 100644 index 0000000..bc4d9ef --- /dev/null +++ b/common/ip.go @@ -0,0 +1,27 @@ +package common + +import ( + "github.com/gin-gonic/gin" + "strings" +) + +func GetClientIP(c *gin.Context) string { + ClientIP := c.ClientIP() + //fmt.Println("ClientIP:", ClientIP) + RemoteIP := c.RemoteIP() + //fmt.Println("RemoteIP:", RemoteIP) + ip := c.Request.Header.Get("X-Forwarded-For") + if strings.Contains(ip, "127.0.0.1") || ip == "" { + ip = c.Request.Header.Get("X-real-ip") + } + if ip == "" { + ip = "127.0.0.1" + } + if RemoteIP != "127.0.0.1" { + ip = RemoteIP + } + if ClientIP != "127.0.0.1" { + ip = ClientIP + } + return ip +} diff --git a/common/middleware/auth.go b/common/middleware/auth.go new file mode 100644 index 0000000..3ea6902 --- /dev/null +++ b/common/middleware/auth.go @@ -0,0 +1,104 @@ +package middleware + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "go-admin/common/middleware/dto" + "go-admin/common/service/sysservice/sysstatuscode" + statuscode "go-admin/common/status_code" + "go-admin/pkg/cryptohelper/jwthelper" + "strconv" + "strings" + "time" + + "github.com/go-admin-team/go-admin-core/sdk/config" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "go-admin/common/middleware/handler" +) + +// AuthInit jwt验证new +func AuthInit() (*jwt.GinJWTMiddleware, error) { + timeout := time.Hour + if config.ApplicationConfig.Mode == "dev" { + timeout = time.Duration(876010) * time.Hour + } else { + if config.JwtConfig.Timeout != 0 { + timeout = time.Duration(config.JwtConfig.Timeout) * time.Second + } + } + return jwt.New(&jwt.GinJWTMiddleware{ + Realm: "test zone", + Key: []byte(config.JwtConfig.Secret), + Timeout: timeout, + MaxRefresh: time.Hour, + PayloadFunc: handler.PayloadFunc, + IdentityHandler: handler.IdentityHandler, + Authenticator: handler.Authenticator, + Authorizator: handler.Authorizator, + Unauthorized: handler.Unauthorized, + TokenLookup: "header: Authorization, query: token, cookie: jwt", + TokenHeadName: "Bearer", + TimeFunc: time.Now, + }) + +} + +func FrontedAuth(c *gin.Context) { + // 从请求头中获取 token 和 os + token := c.GetHeader("Authorization") + source, _ := strconv.Atoi(c.GetHeader("os")) + // 如果 token 不存在,返回未登录的状态 + if len(token) == 0 { + err := ResponseWithStatus(c, dto.NotLoginStatus, statuscode.NotLoggedIn) + if err != nil { + return + } + c.Abort() // 停止后续中间件的执行 + return + } + // 验证 token 并获取结果 + flag, rew := jwthelper.MidValidToken(token, source) + if flag < 0 || len(rew) == 0 { + if flag == -1 { + ResponseWithStatus(c, dto.NotLoginStatus, statuscode.NotLoggedIn) + } else if flag == -2 { + ResponseWithStatus(c, dto.ReLoginStatus, statuscode.ReLogin) + } + c.Abort() // 停止后续中间件的执行 + return + } + // 将解析后的 token 设置到请求头中 + c.Request.Header.Set("ParseToken", rew) + // 继续处理请求 + c.Next() +} + +// ResponseWithStatus 带状态的响应 +func ResponseWithStatus(ctx *gin.Context, status int, code int, data ...interface{}) error { + // 获取语言对应的 msg + msg := sysstatuscode.GetStatusCodeDescription(ctx, code) + if msg == `` { + msg = strconv.Itoa(code) + } else { + // 配置了语言包参数 + if strings.Contains(msg, "%") && len(data) > 1 { + msg = fmt.Sprintf(msg, data[1:]...) + } + } + + resp := dto.Response{ + Status: status, + Code: code, + Msg: msg, + } + + resp.RequestID = pkg.GenerateMsgIDFromContext(ctx) + if len(data) > 0 { + resp.Data = data[0] + } + + ctx.JSON(200, resp) + + return nil +} diff --git a/common/middleware/customerror.go b/common/middleware/customerror.go new file mode 100644 index 0000000..1f92628 --- /dev/null +++ b/common/middleware/customerror.go @@ -0,0 +1,61 @@ +package middleware + +import ( + "fmt" + "net/http" + "runtime" + "strconv" + "strings" + "time" + + "github.com/gin-gonic/gin" +) + +func CustomError(c *gin.Context) { + defer func() { + if err := recover(); err != nil { + + if c.IsAborted() { + c.Status(200) + } + switch errStr := err.(type) { + case string: + p := strings.Split(errStr, "#") + if len(p) == 3 && p[0] == "CustomError" { + statusCode, e := strconv.Atoi(p[1]) + if e != nil { + break + } + c.Status(statusCode) + fmt.Println( + time.Now().Format("2006-01-02 15:04:05"), + "[ERROR]", + c.Request.Method, + c.Request.URL, + statusCode, + c.Request.RequestURI, + c.ClientIP(), + p[2], + ) + c.JSON(http.StatusOK, gin.H{ + "code": statusCode, + "msg": p[2], + }) + } else { + c.JSON(http.StatusOK, gin.H{ + "code": 500, + "msg": errStr, + }) + } + case runtime.Error: + c.JSON(http.StatusOK, gin.H{ + "code": 500, + "msg": errStr.Error(), + }) + default: + panic(err) + } + } + }() + c.Next() +} diff --git a/common/middleware/db.go b/common/middleware/db.go new file mode 100644 index 0000000..0e73a2d --- /dev/null +++ b/common/middleware/db.go @@ -0,0 +1,11 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk" +) + +func WithContextDb(c *gin.Context) { + c.Set("db", sdk.Runtime.GetDbByKey(c.Request.Host).WithContext(c)) + c.Next() +} diff --git a/common/middleware/demo.go b/common/middleware/demo.go new file mode 100644 index 0000000..f13f28f --- /dev/null +++ b/common/middleware/demo.go @@ -0,0 +1,29 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/config" + "net/http" +) + +func DemoEvn() gin.HandlerFunc { + return func(c *gin.Context) { + method := c.Request.Method + if config.ApplicationConfig.Mode == "demo" { + if method == "GET" || + method == "OPTIONS" || + c.Request.RequestURI == "/api/v1/login" || + c.Request.RequestURI == "/api/v1/logout" { + c.Next() + } else { + c.JSON(http.StatusOK, gin.H{ + "code": 500, + "msg": "谢谢您的参与,但为了大家更好的体验,所以本次提交就算了吧!\U0001F600\U0001F600\U0001F600", + }) + c.Abort() + return + } + } + c.Next() + } +} diff --git a/common/middleware/dto/response.go b/common/middleware/dto/response.go new file mode 100644 index 0000000..c8a097c --- /dev/null +++ b/common/middleware/dto/response.go @@ -0,0 +1,21 @@ +package dto + +/** + * TODO 请优先使用 Success 和 Fail 方法响应请求 + */ + +const ( + SuccessStatus = 1 + FailStatus = 0 + NotLoginStatus = -1 // 未登录 + ReLoginStatus = -2 // 重新登录 +) + +// Response 响应结构 +type Response struct { + Status int `json:"status"` + Code int `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data"` + RequestID string `json:"RequestId"` +} diff --git a/common/middleware/handler/auth.go b/common/middleware/handler/auth.go new file mode 100644 index 0000000..ddb6791 --- /dev/null +++ b/common/middleware/handler/auth.go @@ -0,0 +1,182 @@ +package handler + +import ( + "go-admin/app/admin/models" + "go-admin/common" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/config" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/captcha" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "github.com/mssola/user_agent" + "go-admin/common/global" +) + +func PayloadFunc(data interface{}) jwt.MapClaims { + if v, ok := data.(map[string]interface{}); ok { + u, _ := v["user"].(SysUser) + r, _ := v["role"].(SysRole) + return jwt.MapClaims{ + jwt.IdentityKey: u.UserId, + jwt.RoleIdKey: r.RoleId, + jwt.RoleKey: r.RoleKey, + jwt.NiceKey: u.Username, + jwt.DataScopeKey: r.DataScope, + jwt.RoleNameKey: r.RoleName, + } + } + return jwt.MapClaims{} +} + +func IdentityHandler(c *gin.Context) interface{} { + claims := jwt.ExtractClaims(c) + return map[string]interface{}{ + "IdentityKey": claims["identity"], + "UserName": claims["nice"], + "RoleKey": claims["rolekey"], + "UserId": claims["identity"], + "RoleIds": claims["roleid"], + "DataScope": claims["datascope"], + } +} + +// Authenticator 获取token +// @Summary 登陆 +// @Description 获取token +// @Description LoginHandler can be used by clients to get a jwt token. +// @Description Payload needs to be json in the form of {"username": "USERNAME", "password": "PASSWORD"}. +// @Description Reply will be of the form {"token": "TOKEN"}. +// @Description dev mode:It should be noted that all fields cannot be empty, and a value of 0 can be passed in addition to the account password +// @Description 注意:开发模式:需要注意全部字段不能为空,账号密码外可以传入0值 +// @Tags 登陆 +// @Accept application/json +// @Product application/json +// @Param account body Login true "account" +// @Success 200 {string} string "{"code": 200, "expire": "2019-08-07T12:45:48+08:00", "token": ".eyJleHAiOjE1NjUxNTMxNDgsImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTU2NTE0OTU0OH0.-zvzHvbg0A" }" +// @Router /api/v1/login [post] +func Authenticator(c *gin.Context) (interface{}, error) { + log := api.GetRequestLogger(c) + db, err := pkg.GetOrm(c) + if err != nil { + log.Errorf("get db error, %s", err.Error()) + response.Error(c, 500, err, "数据库连接获取失败") + return nil, jwt.ErrFailedAuthentication + } + + var loginVals Login + var status = "2" + var msg = "登录成功" + var username = "" + defer func() { + LoginLogToDB(c, status, msg, username) + }() + + if err = c.ShouldBind(&loginVals); err != nil { + username = loginVals.Username + msg = "数据解析失败" + status = "1" + + return nil, jwt.ErrMissingLoginValues + } + if config.ApplicationConfig.Mode != "dev" { + if !captcha.Verify(loginVals.UUID, loginVals.Code, true) { + username = loginVals.Username + msg = "验证码错误" + status = "1" + + return nil, jwt.ErrInvalidVerificationode + } + } + sysUser, role, e := loginVals.GetUser(db) + if e == nil { + username = loginVals.Username + + return map[string]interface{}{"user": sysUser, "role": role}, nil + } else { + msg = "登录失败" + status = "1" + log.Warnf("%s login failed!", loginVals.Username) + } + return nil, jwt.ErrFailedAuthentication +} + +// LoginLogToDB Write log to database +func LoginLogToDB(c *gin.Context, status string, msg string, username string) { + if !config.LoggerConfig.EnabledDB { + return + } + log := api.GetRequestLogger(c) + l := make(map[string]interface{}) + + ua := user_agent.New(c.Request.UserAgent()) + l["ipaddr"] = common.GetClientIP(c) + l["loginLocation"] = "" // pkg.GetLocation(common.GetClientIP(c),gaConfig.ExtConfig.AMap.Key) + l["loginTime"] = pkg.GetCurrentTime() + l["status"] = status + l["remark"] = c.Request.UserAgent() + browserName, browserVersion := ua.Browser() + l["browser"] = browserName + " " + browserVersion + l["os"] = ua.OS() + l["platform"] = ua.Platform() + l["username"] = username + l["msg"] = msg + + q := sdk.Runtime.GetMemoryQueue(c.Request.Host) + message, err := sdk.Runtime.GetStreamMessage("", global.LoginLog, l) + if err != nil { + log.Errorf("GetStreamMessage error, %s", err.Error()) + //日志报错错误,不中断请求 + } else { + err = q.Append(message) + if err != nil { + log.Errorf("Append message error, %s", err.Error()) + } + } +} + +// LogOut +// @Summary 退出登录 +// @Description 获取token +// LoginHandler can be used by clients to get a jwt token. +// Reply will be of the form {"token": "TOKEN"}. +// @Accept application/json +// @Product application/json +// @Success 200 {string} string "{"code": 200, "msg": "成功退出系统" }" +// @Router /logout [post] +// @Security Bearer +func LogOut(c *gin.Context) { + LoginLogToDB(c, "2", "退出成功", user.GetUserName(c)) + c.JSON(http.StatusOK, gin.H{ + "code": 200, + "msg": "退出成功", + }) + +} + +func Authorizator(data interface{}, c *gin.Context) bool { + + if v, ok := data.(map[string]interface{}); ok { + u, _ := v["user"].(models.SysUser) + r, _ := v["role"].(models.SysRole) + c.Set("role", r.RoleName) + c.Set("roleIds", r.RoleId) + c.Set("userId", u.UserId) + c.Set("userName", u.Username) + c.Set("dataScope", r.DataScope) + return true + } + return false +} + +func Unauthorized(c *gin.Context, code int, message string) { + c.JSON(http.StatusOK, gin.H{ + "code": code, + "msg": message, + }) +} diff --git a/common/middleware/handler/httpshandler.go b/common/middleware/handler/httpshandler.go new file mode 100644 index 0000000..e127439 --- /dev/null +++ b/common/middleware/handler/httpshandler.go @@ -0,0 +1,22 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/unrolled/secure" + + "github.com/go-admin-team/go-admin-core/sdk/config" +) + +func TlsHandler() gin.HandlerFunc { + return func(c *gin.Context) { + secureMiddleware := secure.New(secure.Options{ + SSLRedirect: true, + SSLHost: config.SslConfig.Domain, + }) + err := secureMiddleware.Process(c.Writer, c.Request) + if err != nil { + return + } + c.Next() + } +} diff --git a/common/middleware/handler/login.go b/common/middleware/handler/login.go new file mode 100644 index 0000000..d17626c --- /dev/null +++ b/common/middleware/handler/login.go @@ -0,0 +1,33 @@ +package handler + +import ( + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "gorm.io/gorm" +) + +type Login struct { + Username string `form:"UserName" json:"username" binding:"required"` + Password string `form:"Password" json:"password" binding:"required"` + Code string `form:"Code" json:"code" binding:"required"` + UUID string `form:"UUID" json:"uuid" binding:"required"` +} + +func (u *Login) GetUser(tx *gorm.DB) (user SysUser, role SysRole, err error) { + err = tx.Table("sys_user").Where("username = ? and status = '2'", u.Username).First(&user).Error + if err != nil { + log.Errorf("get user error, %s", err.Error()) + return + } + _, err = pkg.CompareHashAndPassword(user.Password, u.Password) + if err != nil { + log.Errorf("user login error, %s", err.Error()) + return + } + err = tx.Table("sys_role").Where("role_id = ? ", user.RoleId).First(&role).Error + if err != nil { + log.Errorf("get role error, %s", err.Error()) + return + } + return +} diff --git a/common/middleware/handler/ping.go b/common/middleware/handler/ping.go new file mode 100644 index 0000000..ab4d645 --- /dev/null +++ b/common/middleware/handler/ping.go @@ -0,0 +1,11 @@ +package handler + +import ( + "github.com/gin-gonic/gin" +) + +func Ping(c *gin.Context) { + c.JSON(200, gin.H{ + "message": "ok", + }) +} diff --git a/common/middleware/handler/role.go b/common/middleware/handler/role.go new file mode 100644 index 0000000..0895ebc --- /dev/null +++ b/common/middleware/handler/role.go @@ -0,0 +1,24 @@ +package handler + +import "go-admin/common/models" + +type SysRole struct { + RoleId int `json:"roleId" gorm:"primaryKey;autoIncrement"` // 角色编码 + RoleName string `json:"roleName" gorm:"size:128;"` // 角色名称 + Status string `json:"status" gorm:"size:4;"` // + RoleKey string `json:"roleKey" gorm:"size:128;"` //角色代码 + RoleSort int `json:"roleSort" gorm:""` //角色排序 + Flag string `json:"flag" gorm:"size:128;"` // + Remark string `json:"remark" gorm:"size:255;"` //备注 + Admin bool `json:"admin" gorm:"size:4;"` + DataScope string `json:"dataScope" gorm:"size:128;"` + Params string `json:"params" gorm:"-"` + MenuIds []int `json:"menuIds" gorm:"-"` + DeptIds []int `json:"deptIds" gorm:"-"` + models.ControlBy + models.ModelTime +} + +func (SysRole) TableName() string { + return "sys_role" +} diff --git a/common/middleware/handler/user.go b/common/middleware/handler/user.go new file mode 100644 index 0000000..b34d4db --- /dev/null +++ b/common/middleware/handler/user.go @@ -0,0 +1,40 @@ +package handler + +import ( + "go-admin/common/models" + "gorm.io/gorm" +) + +type SysUser struct { + UserId int `gorm:"primaryKey;autoIncrement;comment:编码" json:"userId"` + Username string `json:"username" gorm:"size:64;comment:用户名"` + Password string `json:"-" gorm:"size:128;comment:密码"` + NickName string `json:"nickName" gorm:"size:128;comment:昵称"` + Phone string `json:"phone" gorm:"size:11;comment:手机号"` + RoleId int `json:"roleId" gorm:"size:20;comment:角色ID"` + Salt string `json:"-" gorm:"size:255;comment:加盐"` + Avatar string `json:"avatar" gorm:"size:255;comment:头像"` + Sex string `json:"sex" gorm:"size:255;comment:性别"` + Email string `json:"email" gorm:"size:128;comment:邮箱"` + DeptId int `json:"deptId" gorm:"size:20;comment:部门"` + PostId int `json:"postId" gorm:"size:20;comment:岗位"` + Remark string `json:"remark" gorm:"size:255;comment:备注"` + Status string `json:"status" gorm:"size:4;comment:状态"` + DeptIds []int `json:"deptIds" gorm:"-"` + PostIds []int `json:"postIds" gorm:"-"` + RoleIds []int `json:"roleIds" gorm:"-"` + //Dept *SysDept `json:"dept"` + models.ControlBy + models.ModelTime +} + +func (*SysUser) TableName() string { + return "sys_user" +} + +func (e *SysUser) AfterFind(_ *gorm.DB) error { + e.DeptIds = []int{e.DeptId} + e.PostIds = []int{e.PostId} + e.RoleIds = []int{e.RoleId} + return nil +} diff --git a/common/middleware/header.go b/common/middleware/header.go new file mode 100644 index 0000000..b1411e5 --- /dev/null +++ b/common/middleware/header.go @@ -0,0 +1,48 @@ +package middleware + +import ( + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +// NoCache is a middleware function that appends headers +// to prevent the client from caching the HTTP response. +func NoCache(c *gin.Context) { + c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") + c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") + c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) + c.Next() +} + +// Options is a middleware function that appends headers +// for options requests and aborts then exits the middleware +// chain and ends the request. +func Options(c *gin.Context) { + if c.Request.Method != "OPTIONS" { + c.Next() + } else { + c.Header("Access-Control-Allow-Origin", "*") + c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") + c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") + c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") + c.Header("Content-Type", "application/json") + c.AbortWithStatus(200) + } +} + +// Secure is a middleware function that appends security +// and resource access headers. +func Secure(c *gin.Context) { + c.Header("Access-Control-Allow-Origin", "*") + //c.Header("X-Frame-Options", "DENY") + c.Header("X-Content-Type-Options", "nosniff") + c.Header("X-XSS-Protection", "1; mode=block") + if c.Request.TLS != nil { + c.Header("Strict-Transport-Security", "max-age=31536000") + } + + // Also consider adding Content-Security-Policy headers + // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") +} diff --git a/common/middleware/init.go b/common/middleware/init.go new file mode 100644 index 0000000..9902698 --- /dev/null +++ b/common/middleware/init.go @@ -0,0 +1,35 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "go-admin/common/actions" +) + +const ( + JwtTokenCheck string = "JwtToken" + RoleCheck string = "AuthCheckRole" + PermissionCheck string = "PermissionAction" +) + +func InitMiddleware(r *gin.Engine) { + r.Use(DemoEvn()) + // 数据库链接 + r.Use(WithContextDb) + // 日志处理 + r.Use(LoggerToFile()) + // 自定义错误处理 + r.Use(CustomError) + // NoCache is a middleware function that appends headers + r.Use(NoCache) + // 跨域处理 + r.Use(Options) + // Secure is a middleware function that appends security + r.Use(Secure) + // 链路追踪 + //r.Use(middleware.Trace()) + sdk.Runtime.SetMiddleware(JwtTokenCheck, (*jwt.GinJWTMiddleware).MiddlewareFunc) + sdk.Runtime.SetMiddleware(RoleCheck, AuthCheckRole()) + sdk.Runtime.SetMiddleware(PermissionCheck, actions.PermissionAction()) +} diff --git a/common/middleware/logger.go b/common/middleware/logger.go new file mode 100644 index 0000000..c662edb --- /dev/null +++ b/common/middleware/logger.go @@ -0,0 +1,138 @@ +package middleware + +import ( + "bufio" + "bytes" + "encoding/json" + "go-admin/app/admin/service/dto" + "go-admin/common" + "io" + "io/ioutil" + "net/http" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/config" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + + "go-admin/common/global" +) + +// LoggerToFile 日志记录到文件 +func LoggerToFile() gin.HandlerFunc { + return func(c *gin.Context) { + log := api.GetRequestLogger(c) + // 开始时间 + startTime := time.Now() + // 处理请求 + var body string + switch c.Request.Method { + case http.MethodPost, http.MethodPut, http.MethodGet, http.MethodDelete: + bf := bytes.NewBuffer(nil) + wt := bufio.NewWriter(bf) + _, err := io.Copy(wt, c.Request.Body) + if err != nil { + log.Warnf("copy body error, %s", err.Error()) + err = nil + } + rb, _ := ioutil.ReadAll(bf) + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(rb)) + body = string(rb) + } + + c.Next() + url := c.Request.RequestURI + if strings.Index(url, "logout") > -1 || + strings.Index(url, "login") > -1 { + return + } + // 结束时间 + endTime := time.Now() + if c.Request.Method == http.MethodOptions { + return + } + + rt, bl := c.Get("result") + var result = "" + if bl { + rb, err := json.Marshal(rt) + if err != nil { + log.Warnf("json Marshal result error, %s", err.Error()) + } else { + result = string(rb) + } + } + + st, bl := c.Get("status") + var statusBus = 0 + if bl { + statusBus = st.(int) + } + + // 请求方式 + reqMethod := c.Request.Method + // 请求路由 + reqUri := c.Request.RequestURI + // 状态码 + statusCode := c.Writer.Status() + // 请求IP + clientIP := common.GetClientIP(c) + // 执行时间 + latencyTime := endTime.Sub(startTime) + // 日志格式 + logData := map[string]interface{}{ + "statusCode": statusCode, + "latencyTime": latencyTime, + "clientIP": clientIP, + "method": reqMethod, + "uri": reqUri, + } + log.WithFields(logData).Info() + defer func() { + log.Fields(map[string]interface{}{}) + }() + if c.Request.Method != "OPTIONS" && config.LoggerConfig.EnabledDB && statusCode != 404 { + SetDBOperLog(c, clientIP, statusCode, reqUri, reqMethod, latencyTime, body, result, statusBus) + } + } +} + +// SetDBOperLog 写入操作日志表 fixme 该方法后续即将弃用 +func SetDBOperLog(c *gin.Context, clientIP string, statusCode int, reqUri string, reqMethod string, latencyTime time.Duration, body string, result string, status int) { + + log := api.GetRequestLogger(c) + l := make(map[string]interface{}) + l["_fullPath"] = c.FullPath() + l["operUrl"] = reqUri + l["operIp"] = clientIP + l["operLocation"] = "" // pkg.GetLocation(clientIP, gaConfig.ExtConfig.AMap.Key) + l["operName"] = user.GetUserName(c) + l["requestMethod"] = reqMethod + l["operParam"] = body + l["operTime"] = time.Now() + l["jsonResult"] = result + l["latencyTime"] = latencyTime.String() + l["statusCode"] = statusCode + l["userAgent"] = c.Request.UserAgent() + l["createBy"] = user.GetUserId(c) + l["updateBy"] = user.GetUserId(c) + if status == http.StatusOK { + l["status"] = dto.OperaStatusEnabel + } else { + l["status"] = dto.OperaStatusDisable + } + q := sdk.Runtime.GetMemoryQueue(c.Request.Host) + message, err := sdk.Runtime.GetStreamMessage("", global.OperateLog, l) + if err != nil { + log.Errorf("GetStreamMessage error, %s", err.Error()) + // 日志报错错误,不中断请求 + } else { + err = q.Append(message) + if err != nil { + log.Errorf("Append message error, %s", err.Error()) + } + } +} diff --git a/common/middleware/permission.go b/common/middleware/permission.go new file mode 100644 index 0000000..00d1b1f --- /dev/null +++ b/common/middleware/permission.go @@ -0,0 +1,61 @@ +package middleware + +import ( + "github.com/casbin/casbin/v2/util" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "github.com/go-admin-team/go-admin-core/sdk/pkg/response" +) + +// AuthCheckRole 权限检查中间件 +func AuthCheckRole() gin.HandlerFunc { + return func(c *gin.Context) { + log := api.GetRequestLogger(c) + data, _ := c.Get(jwtauth.JwtPayloadKey) + v := data.(jwtauth.MapClaims) + e := sdk.Runtime.GetCasbinKey(c.Request.Host) + var res, casbinExclude bool + var err error + //检查权限 + if v["rolekey"] == "admin" { + res = true + c.Next() + return + } + for _, i := range CasbinExclude { + if util.KeyMatch2(c.Request.URL.Path, i.Url) && c.Request.Method == i.Method { + casbinExclude = true + break + } + } + if casbinExclude { + log.Infof("Casbin exclusion, no validation method:%s path:%s", c.Request.Method, c.Request.URL.Path) + c.Next() + return + } + res, err = e.Enforce(v["rolekey"], c.Request.URL.Path, c.Request.Method) + if err != nil { + log.Errorf("AuthCheckRole error:%s method:%s path:%s", err, c.Request.Method, c.Request.URL.Path) + response.Error(c, 500, err, "") + return + } + + if res { + log.Infof("isTrue: %v role: %s method: %s path: %s", res, v["rolekey"], c.Request.Method, c.Request.URL.Path) + c.Next() + } else { + log.Warnf("isTrue: %v role: %s method: %s path: %s message: %s", res, v["rolekey"], c.Request.Method, c.Request.URL.Path, "当前request无权限,请管理员确认!") + c.JSON(http.StatusOK, gin.H{ + "code": 403, + "msg": "对不起,您没有该接口访问权限,请联系管理员", + }) + c.Abort() + return + } + + } +} diff --git a/common/middleware/request_id.go b/common/middleware/request_id.go new file mode 100644 index 0000000..8dedd83 --- /dev/null +++ b/common/middleware/request_id.go @@ -0,0 +1,36 @@ +package middleware + +import ( + "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "net/http" + "strings" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" +) + +// RequestId 自动增加requestId +func RequestId(trafficKey string) gin.HandlerFunc { + return func(c *gin.Context) { + if c.Request.Method == http.MethodOptions { + c.Next() + return + } + requestId := c.GetHeader(trafficKey) + if requestId == "" { + requestId = c.GetHeader(strings.ToLower(trafficKey)) + } + if requestId == "" { + requestId = uuid.New().String() + } + c.Request.Header.Set(trafficKey, requestId) + c.Set(trafficKey, requestId) + c.Set(pkg.LoggerKey, + logger.NewHelper(logger.DefaultLogger). + WithFields(map[string]interface{}{ + trafficKey: requestId, + })) + c.Next() + } +} diff --git a/common/middleware/sentinel.go b/common/middleware/sentinel.go new file mode 100644 index 0000000..603559a --- /dev/null +++ b/common/middleware/sentinel.go @@ -0,0 +1,30 @@ +package middleware + +import ( + "github.com/alibaba/sentinel-golang/core/system" + sentinel "github.com/alibaba/sentinel-golang/pkg/adapters/gin" + "github.com/gin-gonic/gin" + + log "github.com/go-admin-team/go-admin-core/logger" +) + +// Sentinel 限流 +func Sentinel() gin.HandlerFunc { + if _, err := system.LoadRules([]*system.Rule{ + { + MetricType: system.InboundQPS, + TriggerCount: 200, + Strategy: system.BBR, + }, + }); err != nil { + log.Fatalf("Unexpected error: %+v", err) + } + return sentinel.SentinelMiddleware( + sentinel.WithBlockFallback(func(ctx *gin.Context) { + ctx.AbortWithStatusJSON(200, map[string]interface{}{ + "msg": "too many request; the quota used up!", + "code": 500, + }) + }), + ) +} diff --git a/common/middleware/settings.go b/common/middleware/settings.go new file mode 100644 index 0000000..daf8d30 --- /dev/null +++ b/common/middleware/settings.go @@ -0,0 +1,43 @@ +package middleware + +type UrlInfo struct { + Url string + Method string +} + +// CasbinExclude casbin 排除的路由列表 +var CasbinExclude = []UrlInfo{ + {Url: "/api/v1/dict/type-option-select", Method: "GET"}, + {Url: "/api/v1/dict-data/option-select", Method: "GET"}, + {Url: "/api/v1/deptTree", Method: "GET"}, + {Url: "/api/v1/db/tables/page", Method: "GET"}, + {Url: "/api/v1/db/columns/page", Method: "GET"}, + {Url: "/api/v1/gen/toproject/:tableId", Method: "GET"}, + {Url: "/api/v1/gen/todb/:tableId", Method: "GET"}, + {Url: "/api/v1/gen/tabletree", Method: "GET"}, + {Url: "/api/v1/gen/preview/:tableId", Method: "GET"}, + {Url: "/api/v1/gen/apitofile/:tableId", Method: "GET"}, + {Url: "/api/v1/getCaptcha", Method: "GET"}, + {Url: "/api/v1/getinfo", Method: "GET"}, + {Url: "/api/v1/menuTreeselect", Method: "GET"}, + {Url: "/api/v1/menurole", Method: "GET"}, + {Url: "/api/v1/menuids", Method: "GET"}, + {Url: "/api/v1/roleMenuTreeselect/:roleId", Method: "GET"}, + {Url: "/api/v1/roleDeptTreeselect/:roleId", Method: "GET"}, + {Url: "/api/v1/refresh_token", Method: "GET"}, + {Url: "/api/v1/configKey/:configKey", Method: "GET"}, + {Url: "/api/v1/app-config", Method: "GET"}, + {Url: "/api/v1/user/profile", Method: "GET"}, + {Url: "/info", Method: "GET"}, + {Url: "/api/v1/login", Method: "POST"}, + {Url: "/api/v1/logout", Method: "POST"}, + {Url: "/api/v1/user/avatar", Method: "POST"}, + {Url: "/api/v1/user/pwd", Method: "PUT"}, + {Url: "/api/v1/metrics", Method: "GET"}, + {Url: "/api/v1/health", Method: "GET"}, + {Url: "/", Method: "GET"}, + {Url: "/api/v1/server-monitor", Method: "GET"}, + {Url: "/api/v1/public/uploadFile", Method: "POST"}, + {Url: "/api/v1/user/pwd/set", Method: "PUT"}, + {Url: "/api/v1/sys-user", Method: "PUT"}, +} diff --git a/common/middleware/trace.go b/common/middleware/trace.go new file mode 100644 index 0000000..6cce4a3 --- /dev/null +++ b/common/middleware/trace.go @@ -0,0 +1,27 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "github.com/opentracing/opentracing-go" +) + +// Trace 链路追踪 +func Trace() gin.HandlerFunc { + return func(ctx *gin.Context) { + var sp opentracing.Span + opName := ctx.Request.URL.Path + // Attempt to join a trace by getting trace context from the headers. + wireContext, err := opentracing.GlobalTracer().Extract( + opentracing.TextMap, + opentracing.HTTPHeadersCarrier(ctx.Request.Header)) + if err != nil { + // If for whatever reason we can't join, go ahead and start a new root span. + sp = opentracing.StartSpan(opName) + } else { + sp = opentracing.StartSpan(opName, opentracing.ChildOf(wireContext)) + } + ctx.Set("traceSpan", sp) + ctx.Next() + sp.Finish() + } +} diff --git a/common/models/by.go b/common/models/by.go new file mode 100644 index 0000000..181c7ed --- /dev/null +++ b/common/models/by.go @@ -0,0 +1,32 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +type ControlBy struct { + CreateBy int `json:"createBy" gorm:"index;comment:创建者"` + UpdateBy int `json:"updateBy" gorm:"index;comment:更新者"` +} + +// SetCreateBy 设置创建人id +func (e *ControlBy) SetCreateBy(createBy int) { + e.CreateBy = createBy +} + +// SetUpdateBy 设置修改人id +func (e *ControlBy) SetUpdateBy(updateBy int) { + e.UpdateBy = updateBy +} + +type Model struct { + Id int `json:"id" gorm:"primaryKey;autoIncrement;comment:主键编码"` +} + +type ModelTime struct { + CreatedAt time.Time `json:"createdAt" gorm:"comment:创建时间"` + UpdatedAt time.Time `json:"updatedAt" gorm:"comment:最后更新时间"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index;comment:删除时间"` +} \ No newline at end of file diff --git a/common/models/menu.go b/common/models/menu.go new file mode 100644 index 0000000..e3b0c92 --- /dev/null +++ b/common/models/menu.go @@ -0,0 +1,11 @@ +package models + +// Menu 菜单中的类型枚举值 +const ( + // Directory 目录 + Directory string = "M" + // Menu 菜单 + Menu string = "C" + // Button 按钮 + Button string = "F" +) diff --git a/common/models/migrate.go b/common/models/migrate.go new file mode 100644 index 0000000..5573cb2 --- /dev/null +++ b/common/models/migrate.go @@ -0,0 +1,12 @@ +package models + +import "time" + +type Migration struct { + Version string `gorm:"primaryKey"` + ApplyTime time.Time `gorm:"autoCreateTime"` +} + +func (Migration) TableName() string { + return "sys_migration" +} diff --git a/common/models/response.go b/common/models/response.go new file mode 100644 index 0000000..8ee0625 --- /dev/null +++ b/common/models/response.go @@ -0,0 +1,30 @@ +package models + +type Response struct { + // 代码 + Code int `json:"code" example:"200"` + // 数据集 + Data interface{} `json:"data"` + // 消息 + Msg string `json:"msg"` + RequestId string `json:"requestId"` +} + +type Page struct { + List interface{} `json:"list"` + Count int `json:"count"` + PageIndex int `json:"pageIndex"` + PageSize int `json:"pageSize"` +} + +// ReturnOK 正常返回 +func (res *Response) ReturnOK() *Response { + res.Code = 200 + return res +} + +// ReturnError 错误返回 +func (res *Response) ReturnError(code int) *Response { + res.Code = code + return res +} diff --git a/common/models/type.go b/common/models/type.go new file mode 100644 index 0000000..2087d55 --- /dev/null +++ b/common/models/type.go @@ -0,0 +1,11 @@ +package models + +import "gorm.io/gorm/schema" + +type ActiveRecord interface { + schema.Tabler + SetCreateBy(createBy int) + SetUpdateBy(updateBy int) + Generate() ActiveRecord + GetId() interface{} +} diff --git a/common/models/user.go b/common/models/user.go new file mode 100644 index 0000000..6053bb3 --- /dev/null +++ b/common/models/user.go @@ -0,0 +1,42 @@ +package models + +import ( + "gorm.io/gorm" + + "github.com/go-admin-team/go-admin-core/sdk/pkg" +) + +// BaseUser 密码登录基础用户 +type BaseUser struct { + Username string `json:"username" gorm:"type:varchar(100);comment:用户名"` + Salt string `json:"-" gorm:"type:varchar(255);comment:加盐;<-"` + PasswordHash string `json:"-" gorm:"type:varchar(128);comment:密码hash;<-"` + Password string `json:"password" gorm:"-"` +} + +// SetPassword 设置密码 +func (u *BaseUser) SetPassword(value string) { + u.Password = value + u.generateSalt() + u.PasswordHash = u.GetPasswordHash() +} + +// GetPasswordHash 获取密码hash +func (u *BaseUser) GetPasswordHash() string { + passwordHash, err := pkg.SetPassword(u.Password, u.Salt) + if err != nil { + return "" + } + return passwordHash +} + +// generateSalt 生成加盐值 +func (u *BaseUser) generateSalt() { + u.Salt = pkg.GenerateRandomKey16() +} + +// Verify 验证密码 +func (u *BaseUser) Verify(db *gorm.DB, tableName string) bool { + db.Table(tableName).Where("username = ?", u.Username).First(u) + return u.GetPasswordHash() == u.PasswordHash +} diff --git a/common/response/binding.go b/common/response/binding.go new file mode 100644 index 0000000..6cf279a --- /dev/null +++ b/common/response/binding.go @@ -0,0 +1,105 @@ +package response + +import ( + "fmt" + "github.com/gin-gonic/gin/binding" + "reflect" + "strings" + "sync" +) + +const ( + _ uint8 = iota + json + xml + yaml + form + query +) + +//var constructor = &bindConstructor{} + +type bindConstructor struct { + cache map[string][]uint8 + mux sync.Mutex +} + +func (e *bindConstructor) GetBindingForGin(d interface{}) []binding.Binding { + bs := e.getBinding(reflect.TypeOf(d).String()) + if bs == nil { + //重新构建 + bs = e.resolve(d) + } + gbs := make([]binding.Binding, len(bs)) + for i, b := range bs { + switch b { + case json: + gbs[i] = binding.JSON + case xml: + gbs[i] = binding.XML + case yaml: + gbs[i] = binding.YAML + case form: + gbs[i] = binding.Form + case query: + gbs[i] = binding.Query + default: + gbs[i] = nil + } + } + return gbs +} + +func (e *bindConstructor) resolve(d interface{}) []uint8 { + bs := make([]uint8, 0) + qType := reflect.TypeOf(d).Elem() + var tag reflect.StructTag + var ok bool + fmt.Println(qType.Kind()) + for i := 0; i < qType.NumField(); i++ { + tag = qType.Field(i).Tag + if _, ok = tag.Lookup("json"); ok { + bs = append(bs, json) + } + if _, ok = tag.Lookup("xml"); ok { + bs = append(bs, xml) + } + if _, ok = tag.Lookup("yaml"); ok { + bs = append(bs, yaml) + } + if _, ok = tag.Lookup("form"); ok { + bs = append(bs, form) + } + if _, ok = tag.Lookup("query"); ok { + bs = append(bs, query) + } + if _, ok = tag.Lookup("uri"); ok { + bs = append(bs, 0) + } + if t, ok := tag.Lookup("binding"); ok && strings.Index(t, "dive") > -1 { + qValue := reflect.ValueOf(d) + bs = append(bs, e.resolve(qValue.Field(i))...) + continue + } + if t, ok := tag.Lookup("validate"); ok && strings.Index(t, "dive") > -1 { + qValue := reflect.ValueOf(d) + bs = append(bs, e.resolve(qValue.Field(i))...) + } + } + return bs +} + +func (e *bindConstructor) getBinding(name string) []uint8 { + e.mux.Lock() + defer e.mux.Unlock() + return e.cache[name] +} + +func (e *bindConstructor) setBinding(name string, bs []uint8) { + e.mux.Lock() + defer e.mux.Unlock() + if e.cache == nil { + e.cache = make(map[string][]uint8) + } + e.cache[name] = bs +} diff --git a/common/service/common/base.go b/common/service/common/base.go new file mode 100644 index 0000000..1be6b3a --- /dev/null +++ b/common/service/common/base.go @@ -0,0 +1,77 @@ +package common + +import ( + "go-admin/pkg/cryptohelper/jwthelper" + "strconv" + + "github.com/gin-gonic/gin" +) + +// 通过原生 token 获取登录用户 ID,判断是否登录。 +// 注:该方法未经过登录中间件,从原始 token 中解析出登录信息,而非从已解析的 token 串中获取登录信息。 +func GetUserIdByToken(ctx *gin.Context) int { + token := ctx.GetHeader("token") + if len(token) == 0 { + return 0 + } + // 解析token + flag, rew := jwthelper.MidValidToken(token, 3) + if flag < 0 || len(rew) == 0 { + return 0 + } + + loginUser := jwthelper.GetLoginUserJwt(rew) + + return loginUser.UserID +} + +// 获取操作系统:1-Android,2-IOS,3-PC +func GetOS(ctx *gin.Context) int { + osStr := ctx.GetHeader("os") // 获取请求头中的"os"字段 + osInt, err := strconv.Atoi(osStr) // 转换为整数 + if err != nil { + return 0 // 如果转换失败,返回0 + } + return osInt +} + +// 获取 设备ID +func GetDeviceID(ctx *gin.Context) string { + device := ctx.GetHeader("device_id") // 获取请求头中的"os"字段 + + return device +} + +// 获取 language,默认语言:zh-CN +// 英语 en +// 日本语 jp +// 韩语 kr +// 马来西亚语 my +// 泰国语 th +// 越南语 vn +// 简体中文 zh-CN +// 繁体中文 zh-HK +func GetLanguage(ctx *gin.Context) string { + lang := "" + + val, exits := ctx.Get("language") + + if !exits { + lang = "zh-CN" + } else { + lang = val.(string) + } + + return lang +} + +// 根据token获取当前userid +func GetUserId(ctx *gin.Context) int { + token := ctx.GetHeader("ParseToken") // .Request.Header.Peek("token") + // loghelper.Debug("token: " + token) + if len(token) == 0 { + return 0 + } + tokenItem := jwthelper.GetLoginUserJwt(token) + return tokenItem.UserID +} diff --git a/common/service/service.go b/common/service/service.go new file mode 100644 index 0000000..0deb4a9 --- /dev/null +++ b/common/service/service.go @@ -0,0 +1,25 @@ +package service + +import ( + "fmt" + + "github.com/go-admin-team/go-admin-core/logger" + "gorm.io/gorm" +) + +type Service struct { + Orm *gorm.DB + Msg string + MsgID string + Log *logger.Helper + Error error +} + +func (db *Service) AddError(err error) error { + if db.Error == nil { + db.Error = err + } else if err != nil { + db.Error = fmt.Errorf("%v; %w", db.Error, err) + } + return db.Error +} diff --git a/common/service/sysservice/aliyunossservice/oss.go b/common/service/sysservice/aliyunossservice/oss.go new file mode 100644 index 0000000..e5ee8aa --- /dev/null +++ b/common/service/sysservice/aliyunossservice/oss.go @@ -0,0 +1,360 @@ +package aliyunossservice + +// +//import ( +// "bytes" +// "encoding/base64" +// "fmt" +// "go-admin/config" +// "io" +// "mime/multipart" +// "path" +// "path/filepath" +// "strings" +// "sync" +// "time" +// +// "github.com/aliyun/aliyun-oss-go-sdk/oss" +// "github.com/google/uuid" +//) +// +//var ( +// scheme = "https" +// clientPool = sync.Pool{} +// once sync.Once +//) +// +//// GetUuidFileName 获取随机文件名 +//func GetUuidFileName() string { +// return strings.ReplaceAll(uuid.NewString(), "-", "") +//} +// +//func GetDomain() string { +// domain := strings.Join([]string{"https://", config.ExtConfig.ALYOssConfig.BucketName, ".", config.ExtConfig.ALYOssConfig.Endpoint}, "") +// return domain +// +//} +// +//// PublicUpload oss 公开图片文件上传 +//func PublicUpload(fileName string, fileByte []byte) (url string, err error) { +// // 创建OSSClient实例 +// client, err := oss.New(config.ExtConfig.ALYOssConfig.Endpoint, config.ExtConfig.ALYOssConfig.AccessKeyID, config.ExtConfig.ALYOssConfig.AccessKeySecret) +// if err != nil { +// return url, fmt.Errorf("oss init err: %w", err) +// } +// +// // 获取存储空间 +// bucket, err := client.Bucket(config.ExtConfig.ALYOssConfig.BucketName) +// if err != nil { +// return url, fmt.Errorf("get bucket err: %w", err) +// } +// +// // 上传阿里云路径 +// folderName := time.Now().Format("2006-01-02") +// if fileName == "" { +// fileName = fmt.Sprintf("%v.jpg", GetUuidFileName()) +// } +// yunFileTmpPath := filepath.Join("uploads", folderName, "coin", fileName) +// // windows文件问题 +// yunFileTmpPath = strings.ReplaceAll(yunFileTmpPath, "\\", "/") +// +// // 上传Byte数组 +// option := oss.ContentType("image/jpg") +// err = bucket.PutObject(yunFileTmpPath, bytes.NewReader(fileByte), option) +// if err != nil { +// return url, fmt.Errorf("upload file err: %w", err) +// } +// domain := GetDomain() +// return domain + "/" + yunFileTmpPath, nil +//} +// +//// UploadVideoOSS 此方法可以用来上传各种类型的文件 +//func UploadVideoOSS(file io.Reader, yunFileTmpPath string) (url string, err error) { +// // 创建OSSClient实例 +// client, err := oss.New(config.ExtConfig.ALYOssConfig.Endpoint, config.ExtConfig.ALYOssConfig.AccessKeyID, config.ExtConfig.ALYOssConfig.AccessKeySecret) +// if err != nil { +// return url, fmt.Errorf("oss init err: %w", err) +// } +// // 获取存储空间 +// bucket, err := client.Bucket(config.ExtConfig.ALYOssConfig.BucketName) +// if err != nil { +// return url, fmt.Errorf("get bucket err: %w", err) +// } +// //option := oss.ContentType("image/jpg") // 支持 jpg/png +// err = bucket.PutObject(yunFileTmpPath, file) +// if err != nil { +// return url, fmt.Errorf("upload file err: %w", err) +// } +// domain := GetDomain() +// return domain + "/" + yunFileTmpPath, nil +// +//} +// +//func PublicUpload1(file io.Reader) (url string, err error) { +// // 创建OSSClient实例 +// client, err := oss.New(config.ExtConfig.ALYOssConfig.Endpoint, config.ExtConfig.ALYOssConfig.AccessKeyID, config.ExtConfig.ALYOssConfig.AccessKeySecret) +// if err != nil { +// return url, fmt.Errorf("oss init err: %w", err) +// } +// +// // 获取存储空间 +// bucket, err := client.Bucket(config.ExtConfig.ALYOssConfig.BucketName) +// if err != nil { +// return url, fmt.Errorf("get bucket err: %w", err) +// } +// +// // 上传阿里云路径 +// folderName := time.Now().Format("2006-01-02") +// yunFileTmpPath := filepath.Join("uploads", folderName, fmt.Sprintf("%v.jpg", GetUuidFileName())) +// // windows文件问题 +// yunFileTmpPath = strings.ReplaceAll(yunFileTmpPath, "\\", "/") +// +// // 上传Byte数组 +// option := oss.ContentType("image/jpg") +// err = bucket.PutObject(yunFileTmpPath, file, option) +// if err != nil { +// return url, fmt.Errorf("upload file err: %w", err) +// } +// domain := GetDomain() +// return domain + "/" + yunFileTmpPath, nil +//} +// +////// SecurityUpload 私有图片文件上传 +////// TODO 该方法有问题待修改, 请勿使用 +////func SecurityUpload(fileName string, fileByte []byte) (url string, err error) { +//// // 临时访问凭证 +//// credentials, err := getSecurityToken() +//// if err != nil { +//// return url, fmt.Errorf("get security err: %w", err) +//// } +//// // 创建OSSClient实例 +//// client, err := oss.New(config.ExtConfig.ALYOssConfig.Endpoint, credentials.AccessKeyId, credentials.AccessKeySecret, oss.SecurityToken(credentials.SecurityToken)) +//// if err != nil { +//// return url, fmt.Errorf("create oss virtual client err: %w", err) +//// } +//// // 获取存储空间 +//// bucket, err := client.Bucket(config.ExtConfig.ALYOssConfig.BucketName) +//// if err != nil { +//// return url, fmt.Errorf("get bucket err: %w", err) +//// } +//// // 上传阿里云路径 +//// folderName := time.Now().Format("2006-01-02") +//// yunFileTmpPath := filepath.Join("uploads", folderName, fmt.Sprintf("f%v_%v.jpg", fileName, GetUuidFileName())) +//// // windows文件问题 +//// yunFileTmpPath = strings.ReplaceAll(yunFileTmpPath, "\\", "/") +//// // 带可选参数的签名直传 +//// options := []oss.Option{ +//// oss.ContentType("image/jpg"), +//// } +//// err = bucket.PutObject(yunFileTmpPath, bytes.NewReader(fileByte), options...) +//// if err != nil { +//// return url, fmt.Errorf("upload file err: %w", err) +//// } +//// signedGetURL, err := bucket.SignURL(yunFileTmpPath, oss.HTTPGet, utility.StringAsInt64(config.ExtConfig.ALYOssConfig.ExpiredInSec)) +//// if err != nil { +//// return url, fmt.Errorf("get sign url err: %w", err) +//// } +//// return signedGetURL, nil +////} +////func GetSecurityURL(fileName string) (url string, err error) { +//// // 临时访问凭证 +//// credentials, err := getSecurityToken() +//// if err != nil { +//// return url, fmt.Errorf("get security err: %w", err) +//// } +//// // 创建OSSClient实例 +//// client, err := oss.New(config.ExtConfig.ALYOssConfig.Endpoint, credentials.AccessKeyId, credentials.AccessKeySecret, oss.SecurityToken(credentials.SecurityToken)) +//// // client, err := oss.New(appconfig.ExtConfig.ALYOssConfig.Endpoint, appconfig.ExtConfig.ALYOssConfig.AccessKeyID, appconfig.ExtConfig.ALYOssConfig.AccessKeySecret, oss.SecurityToken(credentials.SecurityToken)) +//// if err != nil { +//// return url, fmt.Errorf("create oss virtual client err: %w", err) +//// } +//// // 获取存储空间 +//// bucket, err := client.Bucket(config.ExtConfig.ALYOssConfig.BucketName) +//// if err != nil { +//// return url, fmt.Errorf("get bucket err: %w", err) +//// } +//// // 上传阿里云路径 +//// folderName := time.Now().Format("2006-01-02") +//// yunFileTmpPath := filepath.Join("uploads", folderName, fileName) +//// // windows文件问题 +//// yunFileTmpPath = strings.ReplaceAll(yunFileTmpPath, "\\", "/") +//// // 带可选参数的签名直传 +//// fmt.Println("fdfasfsdfasfsdf", yunFileTmpPath) +//// signedGetURL, err := bucket.SignURL(yunFileTmpPath, oss.HTTPGet, utility.StringAsInt64(config.ExtConfig.ALYOssConfig.ExpiredInSec)) +//// if err != nil { +//// return url, fmt.Errorf("get sign url err: %w", err) +//// } +//// return signedGetURL, nil +////} +////func getSecurityToken() (credentials sts.Credentials, er error) { +//// // 构建一个阿里云客户端, 用于发起请求。 +//// // 构建阿里云客户端时,需要设置AccessKey ID和AccessKey Secret。 +//// client, err := sts.NewClientWithAccessKey(config.ExtConfig.ALYOssConfig.RegionId, config.ExtConfig.ALYOssConfig.AccessKeyID, config.ExtConfig.ALYOssConfig.AccessKeySecret) +//// if err != nil { +//// return credentials, fmt.Errorf("get credentials err: %w", err) +//// } +//// // 构建请求对象。 +//// request := sts.CreateAssumeRoleRequest() +//// // 设置参数。关于参数含义和设置方法,请参见《API参考》。 +//// request.Scheme = scheme +//// request.RoleArn = config.ExtConfig.ALYOssConfig.RoleArn +//// request.RoleSessionName = config.ExtConfig.ALYOssConfig.RoleSessionName +//// +//// // 发起请求,并得到响应。 +//// response, err := client.AssumeRole(request) +//// if err != nil { +//// loghelper.Error("get assume role err", zap.Error(err)) +//// return credentials, fmt.Errorf("get assume role err: %w", err) +//// } +//// return response.Credentials, nil +////} +// +//type UploadByBase64 struct { +// Images string `json:"images" validate:"required"` +// FileName string `json:"fileName"` // 文件名称包含文件类型 +//} +// +//// UploadByString @name 上传文件-字符串 +//func UploadByString(params UploadByBase64) (url string, err error) { +// // 获取文件名称 +// if params.FileName == "" { +// // 获取上传文件类型 +// fileTypePosition := strings.Index(params.Images, "/") +// fileType := params.Images[fileTypePosition+1 : fileTypePosition+5] +// uid := uuid.NewString() +// params.FileName = uid + "." + fileType // 代码生成图片名称 +// } +// filePath := fmt.Sprintf("%v/%v", time.Now().Format("2006-01-02"), params.FileName) +// uploadPath := filepath.Join("uploads", filePath) // 生成oos图片存储路径 +// +// // 获取图片内容并base64解密 +// fileContentPosition := strings.Index(params.Images, ",") +// uploadBaseString := params.Images[fileContentPosition+1:] +// uploadString, _ := base64.StdEncoding.DecodeString(uploadBaseString) +// +// // 创建OSSClient实例 +// client, err := oss.New(config.ExtConfig.ALYOssConfig.Endpoint, config.ExtConfig.ALYOssConfig.AccessKeyID, config.ExtConfig.ALYOssConfig.AccessKeySecret) +// if err != nil { +// return url, fmt.Errorf("create oss client err: %w", err) +// } +// +// // 获取存储空间 +// bucket, err := client.Bucket(config.ExtConfig.ALYOssConfig.BucketName) +// if err != nil { +// return url, fmt.Errorf("get bucket err: %w", err) +// } +// option := oss.ContentType("image/jpg") +// err = bucket.PutObject(uploadPath, strings.NewReader(string(uploadString)), option) +// if err != nil { +// return url, fmt.Errorf("put object err: %w", err) +// } +// domain := GetDomain() +// return domain + "/" + uploadPath, nil +//} +// +//// 从连接池中获取客户端实例 +//func getClient() (*oss.Client, error) { +// once.Do(func() { +// clientPool.New = func() interface{} { +// if config.ExtConfig.ALYOssConfig.Endpoint == `` || config.ExtConfig.ALYOssConfig.AccessKeyID == `` || config.ExtConfig.ALYOssConfig.AccessKeySecret == `` { +// return fmt.Errorf("阿里云oss配置错误") +// } +// client, err := oss.New(config.ExtConfig.ALYOssConfig.Endpoint, config.ExtConfig.ALYOssConfig.AccessKeyID, config.ExtConfig.ALYOssConfig.AccessKeySecret) +// if err != nil { +// return fmt.Errorf("oss init err: %w", err) +// } +// return client +// } +// }) +// poolObj := clientPool.Get() +// client, ok := poolObj.(*oss.Client) +// if !ok { +// err, ok := poolObj.(error) +// if ok { +// return nil, err +// } +// return nil, fmt.Errorf("getClient err: %v", poolObj) +// } +// return client, nil +//} +// +//// 将实例放回 +//func putClient(x *oss.Client) { +// clientPool.Put(x) +//} +// +//// UploadFromFileHeader 通过multipart.FileHeader上传文件 +//func UploadFromFileHeader(fileType string, file *multipart.FileHeader) (url string, err error) { +// fileReader, err := file.Open() +// if err != nil { +// return url, err +// } +// filePath := getPath("uploads/"+fileType, path.Ext(file.Filename)) +// +// return Upload(fileReader, filePath, oss.ContentType("image/jpg")) +//} +// +//// getPath 通过路径和文件名后缀获取上传阿里云路径 +//func getPath(filePath, ext string) (resPath string) { +// resPath = path.Join(filePath, time.Now().Format("2006-01-02"), fmt.Sprintf("%v.%s", GetUuidFileName(), ext)) +// // windows文件问题 +// resPath = strings.ReplaceAll(resPath, "\\", "/") +// return resPath +//} +// +//// deUrl 解开url到阿里云路径 用于删除对象 +//func deUrl(url string) (resPath string) { +// return strings.Replace(url, "https://"+config.ExtConfig.ALYOssConfig.BucketName+"."+config.ExtConfig.ALYOssConfig.Endpoint+"/", ``, 1) +//} +// +//// enUrl 合成url +//func enUrl(path string) (resPath string) { +// return "https://" + config.ExtConfig.ALYOssConfig.BucketName + "." + config.ExtConfig.ALYOssConfig.Endpoint + "/" + path +//} +// +//// Upload 指定 file文件, path 存储路径 options 配置选项 +//func Upload(file io.Reader, path string, options ...oss.Option) (url string, err error) { +// // 创建OSSClient实例 +// client, err := getClient() +// if err != nil { +// return url, err +// } +// defer putClient(client) +// +// // 获取存储空间 +// bucket, err := client.Bucket(config.ExtConfig.ALYOssConfig.BucketName) +// if err != nil { +// return url, fmt.Errorf("get bucket err: %w", err) +// } +// +// // 上传Byte数组 +// err = bucket.PutObject(path, file, options...) +// if err != nil { +// return url, fmt.Errorf("upload file err: %w", err) +// } +// url = enUrl(path) +// return +//} +// +//// Delete 删除对象 +//func Delete(filePath string) (err error) { +// // 创建OSSClient实例 +// client, err := getClient() +// if err != nil { +// return err +// } +// defer putClient(client) +// +// // 获取存储空间 +// bucket, err := client.Bucket(config.ExtConfig.ALYOssConfig.BucketName) +// if err != nil { +// return fmt.Errorf("get bucket err: %w", err) +// } +// +// // 删除 +// err = bucket.DeleteObject(deUrl(filePath)) +// if err != nil { +// return fmt.Errorf("upload file err: %w", err) +// } +// return +//} diff --git a/common/service/sysservice/authservice/authentication.go b/common/service/sysservice/authservice/authentication.go new file mode 100644 index 0000000..816877f --- /dev/null +++ b/common/service/sysservice/authservice/authentication.go @@ -0,0 +1,681 @@ +package authservice + +import ( + "fmt" + "go-admin/app/admin/models" + "go-admin/app/admin/models/sysmodel" + "go-admin/app/admin/service/aduserdb" + "go-admin/app/admin/service/dto" + "go-admin/common/const/rediskey" + "go-admin/common/helper" + cModels "go-admin/common/models" + statuscode "go-admin/common/status_code" + "go-admin/pkg/cryptohelper/inttostring" + "go-admin/pkg/cryptohelper/jwthelper" + "go-admin/pkg/cryptohelper/md5helper" + "go-admin/pkg/emailhelper" + "time" + + log "github.com/go-admin-team/go-admin-core/logger" + "go.uber.org/zap" + "gorm.io/gorm" +) + +/** + * 身份验证服务 + */ + +var codeVerifySuccess = "code_verify_success" // 验证码验证成功以后aes加密的秘钥 + +// UserRegisterBefore 用户注册前校验 +func UserRegisterBefore(orm *gorm.DB, registerInfo sysmodel.FrontedUserRegisterReq) (pid int, code int) { + // ========== 校验注册信息 ========== // + if registerInfo.Password != registerInfo.CheckPassword { + return 0, statuscode.PasswordsMustSame + } + if registerInfo.RegisterType == sysmodel.TSmsCode { + user, err := aduserdb.GetUserByPhone(orm, registerInfo.PhoneAreaCode, registerInfo.Phone) + if err != nil { + return 0, statuscode.ServerError + } + if user.Id != 0 { + return 0, statuscode.TheAccountIsAlreadyRegistered + } + } else if registerInfo.RegisterType == sysmodel.TEmailCode { + user, err := aduserdb.GetUserByEmail(orm, registerInfo.Email) //GetUser("useremail", registerInfo.Email) + if err != nil { + return 0, statuscode.ServerError + } + if user.Id != 0 { + //helper.DefaultRedis.SetStringExpire(fmt.Sprintf("%s-reset-register", registerInfo.Email), registerInfo.Password, time.Second*350) + return 0, statuscode.TheAccountIsAlreadyRegistered + } + } + + // 根据邀请码获取推荐人ID + if registerInfo.InviteCode != "" { + parentUser, err := aduserdb.GetUserByInviteCode(orm, registerInfo.InviteCode) + if err != nil { + return 0, statuscode.ServerError + } + if parentUser.Id == 0 { + return 0, statuscode.InviterNotExist + } + return parentUser.Id, statuscode.OK + } + //if inviteCode, code = getReferrerId(registerInfo.InviteCode); code != statuscode.OK { + // return inviteCode, code + //} + + return 0, statuscode.OK +} + +// UserRegister 用户注册 +func UserRegister(orm *gorm.DB, registerInfo sysmodel.FrontedUserRegisterReq) (int, *models.LineUser) { + // 校验验证码 + //cc := sysmodel.CheckCaptcha{ + // BusinessType: int(businesstype.Register), + // Receive: registerInfo.Receive, + // Captcha: registerInfo.Captcha, + //} + //if code := CheckPhoneOrEmailCaptcha(orm, cc); code != statuscode.OK { + // return "", "", statuscode.CaptchaInvalid + //} + user := models.LineUser{ + Pid: registerInfo.Pid, + Password: registerInfo.Password, + Salt: inttostring.GenerateRandomString(6), + Email: registerInfo.Email, + InviteCode: inttostring.NewInvite().Encode(int(time.Now().Unix())), + Loginip: registerInfo.IP, + Mobile: registerInfo.Phone, + Area: registerInfo.PhoneAreaCode, + Status: "verify", + LoginTime: time.Now(), + ModelTime: cModels.ModelTime{ + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, + } + + if registerInfo.RegisterType == sysmodel.TEmailCode { + user.Username = user.Email + user.Nickname = user.Email + } + + if registerInfo.RegisterType == sysmodel.TSmsCode { + user.Username = user.Mobile + user.Nickname = user.Mobile + } + + user.CreatedAt = time.Now() + user.Password = md5helper.MD5(registerInfo.Password + user.Salt) + // 开启事务 + //如果是手机号注册的 直接返回token + if registerInfo.RegisterType == sysmodel.TSmsCode { + //验证手机验证码 + key := fmt.Sprintf(rediskey.PCRegisterMobile, registerInfo.Phone) + get := helper.DefaultRedis.Get(key) + if registerInfo.Captcha != get.Val() && registerInfo.Captcha != "123456" { + return statuscode.PhoneCaptchaInvalid, nil + } + helper.DefaultRedis.DeleteString(key) + user.Status = "normal" + } + err := orm.Transaction(func(tx *gorm.DB) error { + _, err := aduserdb.AddUser(tx, &user) + if err != nil { + return err + } + return nil + }) + + if err != nil { + log.Error("UserRegister Commit tx", zap.Error(err)) + return statuscode.ServerError, &user + } + + //如果是手机号注册的 直接返回token + if registerInfo.RegisterType == sysmodel.TSmsCode { + return statuscode.OK, &user + } + + //发送邮箱 + emailCode := inttostring.GenerateRandomString(10) + go SendRegisterEmail(registerInfo.Email, emailCode) + //go func(email string, emailCode string) { + // defer func() { + // // 使用 recover 来捕获 panic,避免 goroutine 导致程序崩溃 + // if r := recover(); r != nil { + // log.Error("sendEmail Error:", r) + // } + // }() + // get := helper.DefaultRedis.Get(fmt.Sprintf("%s-register", email)) + // fmt.Println("11111111111------------") + // fmt.Println("get.Val():", get.Val()) + // if get.Val() != "" { //说明邮箱操作频繁 + // return + // } + // key := fmt.Sprintf(rediskey.PCRegisterEmail, email) + // if err = helper.DefaultRedis.SetStringExpire(key, emailCode, time.Second*300); err != nil { + // log.Error("sendEmail setRedis Error:", zap.Error(err)) + // return + // } + // err2 := emailhelper.SendFrontedEmail(email, emailCode) + // if err2 != nil { + // log.Error("sendEmail server Error:", zap.Error(err2)) + // return + // } + // //记录邮箱发送 + // helper.DefaultRedis.SetStringExpire(fmt.Sprintf("%s-register", emailCode), "register", time.Second*60) + // return + //}(registerInfo.Email, emailCode) + + return statuscode.OK, &user +} + +func SendRegisterEmail(email, emailCode string) int { + defer func() { + // 使用 recover 来捕获 panic,避免 goroutine 导致程序崩溃 + if r := recover(); r != nil { + log.Error("SendRegisterEmail Error:", r) + } + }() + get := helper.DefaultRedis.Get(fmt.Sprintf("%s-register", email)) + if get.Val() != "" { //说明邮箱操作频繁 + return statuscode.EmailOrderTooOften + } + key := fmt.Sprintf(rediskey.PCRegisterEmail, email) + if err := helper.DefaultRedis.SetStringExpire(key, emailCode, time.Second*300); err != nil { + log.Error("sendEmail setRedis Error:", zap.Error(err)) + return statuscode.ServerError + } + + err2 := emailhelper.SendFrontedEmail(email, emailCode) + if err2 != nil { + log.Error("sendEmail server Error:", zap.Error(err2)) + return statuscode.ServerError + } + //记录邮箱发送 + helper.DefaultRedis.SetStringExpire(fmt.Sprintf("%s-register", email), "register", time.Second*60) + return statuscode.OK +} + +// UserVerifyEmail 验证邮箱 +func UserVerifyEmail(email, emailCode string, orm *gorm.DB) (code int) { + key := fmt.Sprintf(rediskey.PCRegisterEmail, email) + get := helper.DefaultRedis.Get(key) + if get.Val() == "" { + return statuscode.EmailNotExistOrEmailCOdeExpired + } + if get.Val() != emailCode && get.Val() != "123456" { + return statuscode.EmailCaptchaInvalid + } + // + ////之前的密码 + //val := helper.DefaultRedis.Get(fmt.Sprintf("%s-reset-register", email)).Val() + //if val != "" { + // var user models.LineUser + // + // orm.Model(&models.LineUser{}).Where("email = ? AND status = `verify` ", email).Find(&user) + // if user.Id > 0 { + // newPassword := md5helper.MD5(val + user.Salt) + // orm.Model(&models.LineUser{}).Where("id = ?", user.Id).Update("password", newPassword) + // } + //} + return statuscode.OK +} + +// // UserRefreshToken 刷新token +// +// func UserRefreshToken(orm *gorm.DB, uid string, source int) (string, string, int) { +// userId := utility.StringAsInteger(uid) +// // 加载用户信息 +// user, err := aduserdb.GetUserById(orm, userId) +// if err != nil { +// return "", "", statuscode.ServerError +// } +// // 注册完成直接登录 +// token, expire := jwthelper.CreateJwtToken(jwthelper.LoginUserJwt{ +// UserID: userId, +// NickName: user.Nickname, +// Phone: user.Phone, +// Email: user.UserEmail, +// OsType: source, +// }, int(jwthelper.LoginTokenValidTime.Minutes())) +// +// // 保存登录凭证; +// key := fmt.Sprintf(rediskey.AppLoginUserToken, userId) +// if source == 3 { +// key = fmt.Sprintf(rediskey.PCLoginUserToken, userId) +// } +// if err = helper.DefaultRedis.SetStringExpire(key, token, time.Second*time.Duration(jwthelper.LoginTokenValidTime.Seconds())); err != nil { +// return "", "", statuscode.ServerError +// } +// return token, expire, statuscode.OK +// } +// +// // 代理商邀请注册的用户 +// +// func userAgent(orm *gorm.DB, agentCode string, userId int, inviteCode models.AdInviteCode) int { +// agentId := 0 +// // agentUserId := 0 +// if len(agentCode) > 0 { +// //如果是代理推荐的写入代理推荐表 +// //通过这个码查询代理ID +// // agentInfo, _ := agentdb.GetAgentByCode(agentCode) +// // if agentInfo.ID > 0 { +// // agentId = agentInfo.ID +// // agentUserId = int(agentInfo.Userid) +// // } +// } +// if inviteCode.UserId > 0 || agentId > 0 { +// // agent := models.AgentRecommend{ +// // AgentId: agentId, +// // UserId: userId, +// // CreateTime: time.Now(), +// // } +// // if inviteCode.UserId > 0 { +// // agent.ReferType = 1 +// // agent.ReCommenId = inviteCode.UserId +// // } else { +// // agent.ReferType = 3 +// // agent.ReCommenId = agentUserId +// // } +// // _ = agentdb.RecommendAdd(agent) +// // // 上级是代理商才写入邀请表 +// // if agentId > 0 { +// // // invite +// // invite := models.AgentInvite{ +// // AgentId: agentId, +// // UserId: userId, +// // CreateTime: time.Now(), +// // } +// // _ = agentdb.AgentInviteAdd(invite) +// // } +// +// } +// log.Error(fmt.Sprintf("userrge agent 11 invitecodeuserid=%v", inviteCode.UserId)) +// +// if inviteCode.UserId != 0 { +// // 更新推荐人的推荐总人数 +// if err := aduserdb.UpdateUserRecommend(orm, inviteCode.UserId); err != nil { +// return statuscode.ServerError +// } +// log.Error(fmt.Sprintf("userrge agent 22 invitecodeuserid=%v", inviteCode.UserId)) +// //hc todo 注释 +// // // 更加代理商邀请表 推荐人的数据 +// // if err := aduserdb.UpdateUserInvitePeople(inviteCode.UserId); err != nil { +// // log.Error(fmt.Sprintf("userrge agent 333 invitecodeuserid=%v,err=%v", inviteCode.UserId, err.Error())) +// // return statuscode.ServerError +// // } +// //// 更新推荐人的上一级人数 +// //commend := agentdb.GetAgentIdByUserId(inviteCode.UserId) +// //if commend.ReCommenId > 0 { +// // if err := agentdb.UpdateUserInvitePeople(commend.ReCommenId); err != nil { +// // return statuscode.ServerError +// // } +// //} +// } +// +// return statuscode.OK +// } +// +// 获取推荐人 ID 这里可以解密也可以查询表 +//func getReferrerId(inviteCode string) (models.AdInviteCode, int) { +// if len(inviteCode) == 0 { +// return models.AdInviteCode{}, statuscode.OK +// } +// Invite := models.AdInviteCode{} +// // hc todo注释 +// // Invite, err := aduserdb.GetInviteCodeByCode(inviteCode) +// // if err != nil { +// // return models.AdInviteCode{}, statuscode.ServerError +// // } +// if Invite.UserId == 0 { +// return models.AdInviteCode{}, statuscode.InviterNotExist +// } +// +// return Invite, statuscode.OK +//} + +// // 生成昵称 +// +// func genNickname() string { +// return utility.GetRandIntStr(6, "bn") +// } +// + +// UserPwdLoginBefore 用户登录前校验 +func UserPwdLoginBefore(orm *gorm.DB, loginInfo dto.FrontedLoginReq) (user models.LineUser, code int, langArg interface{}) { + // ========== 校验登录信息 ========== // + var err error + if loginInfo.LoginType == sysmodel.TSmsCode { + // 手机 + user, err = aduserdb.GetUserByPhone(orm, loginInfo.PhoneAreaCode, loginInfo.Phone) + if err != nil { + return user, statuscode.ServerError, langArg + } + } else if loginInfo.LoginType == sysmodel.TEmailCode { + // 邮箱 + user, err = aduserdb.GetUserByEmail(orm, loginInfo.Email) //GetUser("useremail", loginInfo.Email) + if err != nil { + return user, statuscode.ServerError, langArg + } + } + // 用户不存在 + if user.Id == 0 { + return user, statuscode.TheAccountIsNotRegistered, langArg + } + + // 获取密码错误次数 + key := fmt.Sprintf(rediskey.UserLoginPwdErrFre, user.Id) + total, wait, _ := helper.DefaultRedis.GetUserLoginPwdErrFre(key) + if total >= 5 { + return user, statuscode.AccountIsFrozen, wait + } + md5 := md5helper.MD5(loginInfo.Password + user.Salt) + // 验证密码 + if user.Password != md5 { + // 禁用时长 + disableDuration := 12 * time.Hour + num, err := helper.DefaultRedis.SetUserLoginPwdErrFre(key, disableDuration) + if err != nil { + log.Error("Redis", zap.Error(err)) + return user, statuscode.ServerError, langArg + } + if num < 5 { + return user, statuscode.AccountOrPasswordError, 5 - num + } else { + return user, statuscode.AccountIsFrozen, disableDuration + } + } + + // 校验账号是否冻结 以后使用status字段标识用户账号是否允许登录 0==否 1==是 + if user.Status == "" { + return user, statuscode.AccountIsFrozen, langArg + } + if user.Status == "verify" { + return models.LineUser{}, statuscode.UserNotVerify, langArg + } + go func(key string) { + _ = helper.DefaultRedis.DeleteString(key) + }(key) + return user, statuscode.OK, langArg +} + +// // UserPwdLogin 账号密码登录 +// +// func UserPwdLogin(orm *gorm.DB, loginInfo sysmodel.UserAccountPwdLoginReq, user models.AdUser, authSwitch sysmodel.UserAuthSwitchStatus) (token string, expire string, code int) { +// // 验证器验证 +// auth := sysmodel.Authenticator{ +// UserID: user.Id, +// PhoneAuth: authSwitch.PhoneAuth, +// EmailAuth: authSwitch.EmailAuth, +// GoogleAuth: authSwitch.GoogleAuth, +// Phone: user.Phone, +// Email: user.UserEmail, +// GoogleSecret: authSwitch.GoogleSecret, +// SmsCaptcha: loginInfo.SmsCaptcha, +// EmailCaptcha: loginInfo.EmailCaptcha, +// GoogleCaptcha: loginInfo.GoogleCaptcha, +// BusinessType: businesstype.Login, +// } +// if c := AuthenticatorVerify(orm, auth); c != statuscode.OK { +// return "", "", c +// } +// +// jwtToken, expire, code := GenerateToken(user.Id, loginInfo.Source, user.Nickname, user.Phone, user.UserEmail, loginInfo.LoginIP, loginInfo.DeviceID) +// if code != statuscode.OK { +// return "", "", code +// } +// +// return jwtToken, expire, statuscode.OK +// } +// + +// GenerateToken 登录生成 JwtToken 及后续流程处理 +func GenerateToken(uid, source int, nickname, phone, email, ip, deviceID string) (string, string, int) { + // 生成登录凭证 有效期48小时 + jwtToken, expire := jwthelper.CreateJwtToken(jwthelper.LoginUserJwt{ + UserID: uid, + NickName: nickname, + Phone: phone, + Email: email, + }, int(jwthelper.LoginTokenValidTime.Minutes())) + + // 登入业务处理发送到kafka + //newLog := models.AdLog{ + // LogType: int(businesstype.Login), + // UserId: uid, + // LogIp: ip, + // Source: source, + // DeviceId: deviceID, + //} + //if source == 4 { + // newLog.LogType = int(businesstype.ScanLogin) //扫码登入 + // newLog.Source = 3 + // source = 3 + //} + // by, _ := jsonhelper.MarshalMsgPack(&newLog) + // kafkahelper.SendKafkaMsg(kafkatopic.LoginLog, utility.IntToString(uid), by) + + // 保存登录凭证; + key := fmt.Sprintf(rediskey.AppLoginUserToken, uid) + if source == 3 { + key = fmt.Sprintf(rediskey.PCLoginUserToken, uid) + } + if err := helper.DefaultRedis.SetStringExpire(key, jwtToken, + time.Second*time.Duration(jwthelper.LoginTokenValidTime.Seconds())); err != nil { + return "", "", statuscode.ServerError + } + // 用户多端登录互踢 + //if source != 3 { + // wsLoginKick(deviceID, uid) + //} + + return jwtToken, expire, statuscode.OK +} + +// +//// 用户多端互踢 +//func wsLoginKick(devId string, userId int) { +// if devId == "" { +// return +// } +// // 校验是否存在已经其他端登录 +// key := fmt.Sprintf("user-%v", userId) +// // 读取原存储的设备号 +// preDevId, _ := helper.DefaultRedis.HGetField(rediskey.UserLoginWsClient, key) +// // 将本次登录的设备号存储 +// _ = helper.DefaultRedis.HSetField(rediskey.UserLoginWsClient, key, devId) +// +// if string(preDevId) != devId { +// // hc todo 注释 +// // 给上一个登录的端发送订阅消息 +// // data := &models.PushUserLoginKick{ +// // Type: 14, +// // Data: string(preDevId), // 上一个登录端的设备号 +// // } +// // // 通知用户账户最新信息 +// // by, _ := sonic.Marshal(data) +// // log.Info("通知用户其他端已登录", zap.String("key", key), zap.ByteString("by", by)) +// // kafkahelper.SendKafkaMsg(kafkatopic.LoginKick, key, by) +// } +//} +// +//// ResetPwdBefore 1 重置密码前校验 +//func ResetPwdBefore(orm *gorm.DB, resetPwd sysmodel.ResetPwdReq) (user models.AdUser, code int) { +// var err error +// +// if resetPwd.RetrieveType == 1 { +// user, err = aduserdb.GetUserByPhone(orm, resetPwd.PhoneAreaCode, resetPwd.Phone) +// if err != nil { +// return user, statuscode.ServerError +// } +// } else if resetPwd.RetrieveType == 2 { +// user, err = aduserdb.GetUserByEmail(orm, resetPwd.Email) //GetUser("useremail", resetPwd.Email) +// if err != nil { +// return user, statuscode.ServerError +// } +// } +// +// if user.Id == 0 { +// return user, statuscode.TheAccountIsNotRegistered +// } +// +// return user, statuscode.OK +//} +// +//// ResetPwdCheck 2 重置密码安全验证 +//func ResetPwdCheck(orm *gorm.DB, rpc sysmodel.ResetPwdCheck) (string, int) { +// var ( +// user models.AdUser +// err error +// ) +// +// if rpc.RetrieveType == 1 { +// user, err = aduserdb.GetUserByPhone(orm, rpc.PhoneAreaCode, rpc.Phone) +// if err != nil { +// return "", statuscode.ServerError +// } +// } else if rpc.RetrieveType == 2 { +// user, err = aduserdb.GetUserByEmail(orm, rpc.Email) //GetUser("useremail", rpc.Email) +// if err != nil { +// return "", statuscode.ServerError +// } +// } +// +// if user.Id == 0 { +// return "", statuscode.TheAccountIsNotRegistered +// } +// +// // 获取用户验证器开启状态 +// authSwitch, err := aduserdb.GetUserAuthSwitch(orm, user.Id) +// if err != nil { +// return "", statuscode.ServerError +// } +// // 验证器校验 +// validator := sysmodel.Authenticator{ +// UserID: user.Id, +// PhoneAuth: authSwitch.PhoneAuth, +// EmailAuth: authSwitch.EmailAuth, +// GoogleAuth: authSwitch.GoogleAuth, +// Phone: user.Phone, +// Email: user.UserEmail, +// GoogleSecret: authSwitch.GoogleSecret, +// SmsCaptcha: rpc.SmsCaptcha, +// EmailCaptcha: rpc.EmailCaptcha, +// GoogleCaptcha: rpc.GoogleCaptcha, +// BusinessType: businesstype.ResetPass, +// } +// +// if code := AuthenticatorVerify(orm, validator); code != statuscode.OK { +// return "", code +// } +// +// // 校验验证码通过,生成下一步操作的凭证 +// cre := sysmodel.Credential{ +// BusinessType: int(businesstype.ResetPass), +// UserID: user.Id, +// Phone: user.Phone, +// Email: user.UserEmail, +// Time: time.Now().Unix(), +// Rand: rand.NewSource(time.Now().UnixNano()).Int63(), +// } +// creJ, _ := sonic.Marshal(cre) +// credentials := aeshelper.Encrypt(string(creJ), codeVerifySuccess) +// +// return credentials, statuscode.OK +//} +// +//// ResetPwd 3 重置密码 +//func ResetPwd(orm *gorm.DB, user models.AdUser, resetPwd sysmodel.ResetPwdReq) int { +// // 校验凭证 +// cre := sysmodel.Credential{ +// BusinessType: int(businesstype.ResetPass), +// UserID: user.Id, +// Phone: user.Phone, +// Email: user.UserEmail, +// Time: time.Now().Unix(), +// } +// if !CheckCredentials(cre, resetPwd.Credentials) { +// log.Error("business credentials error") +// return statuscode.BusinessCredentialsError +// } +// +// // 更新密码 +// if err := aduserdb.UpdateUserPwd(orm, resetPwd.UserID, resetPwd.Password); err != nil { +// return statuscode.ServerError +// } +// +// return statuscode.OK +//} +// +//// ChangePwdBefore 修改密码前校验 +//func ChangePwdBefore(orm *gorm.DB, params sysmodel.UserChangePwdReq) (user models.AdUser, code int) { +// +// user, err := aduserdb.GetUserById(orm, params.UserId) //"id", params.UserId) +// +// if err != nil { +// return user, statuscode.ServerError +// } +// if user.UserPassword != params.OldPassword { +// return user, statuscode.OriginalPasswordError +// } +// +// return user, statuscode.OK +//} +// +//// ChangePwd 修改密码 +//func ChangePwd(orm *gorm.DB, params sysmodel.UserChangePwdReq, user models.AdUser) int { +// // 获取用户验证器开关状态 +// authSwitch, err := GetUserAuthSwitch(orm, user.Id) +// if err != nil { +// return statuscode.ServerError +// } +// +// // 验证器验证 +// auth := sysmodel.Authenticator{ +// UserID: params.UserId, +// PhoneAuth: authSwitch.PhoneAuth, +// EmailAuth: authSwitch.EmailAuth, +// GoogleAuth: authSwitch.GoogleAuth, +// Phone: user.Phone, +// Email: user.UserEmail, +// GoogleSecret: authSwitch.GoogleSecret, +// SmsCaptcha: params.SmsCaptcha, +// EmailCaptcha: params.EmailCaptcha, +// GoogleCaptcha: params.GoogleCaptcha, +// BusinessType: businesstype.ChangePassword, +// } +// if code := AuthenticatorVerify(orm, auth); code != statuscode.OK { +// return code +// } +// +// // 更新密码 +// if err = aduserdb.UpdateUserPwd(orm, params.UserId, params.NewPassword); err != nil { +// return statuscode.ServerError +// } +// +// return statuscode.OK +//} +// +//// GetUserRealName 获取用户真实姓名 +//func GetUserRealName(orm *gorm.DB, uid int) string { +// buyerIdent, err := aduserdb.GetIdentification(orm, uid) +// if err != nil { +// return "" +// } +// realName := buyerIdent.Name +// +// if buyerIdent.CountryId == 40 || buyerIdent.CountryId == 73 || +// buyerIdent.CountryId == 115 || buyerIdent.CountryId == 179 { +// realName = buyerIdent.Name // 中国大陆,港澳台 +// } else { +// // 名字-中间名-姓 +// realName = buyerIdent.Name + " " + buyerIdent.MiddleName + " " + buyerIdent.Surname +// } +// +// return realName +//} diff --git a/common/service/sysservice/authservice/authenticator.go b/common/service/sysservice/authservice/authenticator.go new file mode 100644 index 0000000..942e801 --- /dev/null +++ b/common/service/sysservice/authservice/authenticator.go @@ -0,0 +1,402 @@ +package authservice + +// +//import ( +// "go-admin/app/admin/models" +// "go-admin/app/admin/models/sysmodel" +// "go-admin/app/admin/service/aduserdb" +// "go-admin/app/admin/service/otcdb/orderdb" +// "go-admin/common/const/enum/businesstype" +// statuscode "go-admin/common/status_code" +// "go-admin/pkg/googleauthhelper" +// "go-admin/pkg/utility" +// +// log "github.com/go-admin-team/go-admin-core/logger" +// "gorm.io/gorm" +// +// "go.uber.org/zap" +//) +// +//// GetUserAuthSwitch 获取用户验证器开关状态 +//func GetUserAuthSwitch(orm *gorm.DB, userID int) (auth sysmodel.UserAuthSwitchStatus, err error) { +// // 获取用户验证器开关 +// auth, err = aduserdb.GetUserAuthSwitch(orm, userID) +// if err != nil { +// return +// } +// +// if auth.PhoneAuth != 1 { +// auth.PhoneAuth = 0 +// } +// if auth.EmailAuth != 1 { +// auth.EmailAuth = 0 +// } +// if auth.GoogleAuth != 1 { +// auth.GoogleAuth = 0 +// } +// +// return +//} +// +//// AuthenticatorVerify 已开通验证器校验 +//func AuthenticatorVerify(orm *gorm.DB, authenticator sysmodel.Authenticator) int { +// // 是否完全验证 +// if authenticator.PhoneAuth == 1 && len(authenticator.SmsCaptcha) == 0 || +// authenticator.EmailAuth == 1 && len(authenticator.EmailCaptcha) == 0 || +// authenticator.GoogleAuth == 1 && len(authenticator.GoogleCaptcha) == 0 { +// +// return statuscode.PleasePerformCompleteSecurityVerification +// } +// var phoneCheck sysmodel.CheckCaptcha +// var emailCheck sysmodel.CheckCaptcha +// // 校验验证码 +// if authenticator.PhoneAuth == 1 { +// phoneCheck = sysmodel.CheckCaptcha{ +// BusinessType: int(authenticator.BusinessType), +// Receive: authenticator.Phone, +// Captcha: authenticator.SmsCaptcha, +// } +// } +// if authenticator.EmailAuth == 1 { +// emailCheck = sysmodel.CheckCaptcha{ +// BusinessType: int(authenticator.BusinessType), +// Receive: authenticator.Email, +// Captcha: authenticator.EmailCaptcha, +// } +// } +// var googleSecret string +// var googleCaptcha string +// if authenticator.GoogleAuth == 1 { +// googleSecret = authenticator.GoogleSecret +// googleCaptcha = authenticator.GoogleCaptcha +// } +// // 检验验证码 +// code := CheckCaptchaNew(orm, phoneCheck, emailCheck, googleSecret, googleCaptcha) +// return code +//} +// +//// AuthSwitchBefore 身份验证开关前校验(新) +//func AuthSwitchBefore(orm *gorm.DB, auth sysmodel.AuthenticatorSwitch) int { +// // 开启手机、更改手机、开启邮箱、更改邮箱 判断是否被占用 +// var user models.AdUser +// var err error +// if auth.BusinessType == businesstype.OpenPhoneAuth || auth.BusinessType == businesstype.ChangePhoneAuth { +// user, err = aduserdb.GetUserByPhone(orm, auth.NewPhoneArea, auth.NewPhone) +// if err != nil { +// return statuscode.ServerError +// } +// if user.Id != 0 && user.Id != auth.UserID { +// return statuscode.ThePhoneHasBeenBound +// } +// } else if auth.BusinessType == businesstype.OpenEmailAuth || auth.BusinessType == businesstype.ChangeEmailAuth { +// user, err = aduserdb.GetUserByEmail(orm, auth.NewEmail) //GetUser("useremail", auth.NewEmail) +// if err != nil { +// return statuscode.ServerError +// } +// if user.Id != 0 && user.Id != auth.UserID { +// return statuscode.TheEmailHasBeenBound +// } +// } +// +// // 获取用户验证器开关状态 +// authSwitch, err := GetUserAuthSwitch(orm, auth.UserID) +// if err != nil { +// return statuscode.ServerError +// } +// +// // 关闭验证器 需保证至少两个开启 +// if auth.BusinessType == businesstype.ClosePhoneAuth || +// auth.BusinessType == businesstype.CloseEmailAuth || +// auth.BusinessType == businesstype.CloseGoogleAuth { +// +// if (authSwitch.PhoneAuth + authSwitch.EmailAuth + authSwitch.GoogleAuth) < 3 { +// return statuscode.AllAuthMustOpen +// } +// +// // OTC 订单状态为:1-待付款、3-代放币、7-申诉中时,不允许解绑手机 +// if auth.BusinessType == businesstype.ClosePhoneAuth { +// // hc todo 注释 +// // 广告 +// // sum, err := advertisedb.GetUserAdvertiseSum(auth.UserID) +// // if err != nil { +// // return statuscode.ServerError +// // } +// // if sum > 0 { +// // return statuscode.OTCAdvertiseAreInProgress +// // } +// // 订单 +// num, err := orderdb.GetUnfilledOrderCount(orm, auth.UserID) +// if err != nil { +// return statuscode.ServerError +// } +// if num > 0 { +// return statuscode.OTCOrdersAreInProgress +// } +// } +// } +// if user.Id == 0 { +// user, err = aduserdb.GetUserById(orm, auth.UserID) //"id", auth.UserID) +// if err != nil { +// return statuscode.ServerError +// } +// } +// +// // ==================== 1 已开通验证器校验 ==================== // +// validator := sysmodel.Authenticator{ +// UserID: auth.UserID, +// PhoneAuth: authSwitch.PhoneAuth, +// EmailAuth: authSwitch.EmailAuth, +// GoogleAuth: authSwitch.GoogleAuth, +// Phone: user.Phone, +// Email: user.UserEmail, +// GoogleSecret: authSwitch.GoogleSecret, +// SmsCaptcha: auth.SmsCaptcha, +// EmailCaptcha: auth.EmailCaptcha, +// GoogleCaptcha: auth.GoogleCaptcha, +// BusinessType: auth.BusinessType, +// } +// +// // 是否完全验证 +// if validator.PhoneAuth == 1 && len(validator.SmsCaptcha) == 0 || +// validator.EmailAuth == 1 && len(validator.EmailCaptcha) == 0 || +// validator.GoogleAuth == 1 && len(validator.GoogleCaptcha) == 0 { +// +// return statuscode.PleasePerformCompleteSecurityVerification +// } +// var phoneCode models.AdVerifyCode +// if len(validator.SmsCaptcha) > 0 && !CheckOpenVerifyCode(validator.SmsCaptcha) { +// // 校验手机号码发送的验证码 +// phoneCheck := sysmodel.CheckCaptcha{ +// BusinessType: int(businesstype.WithdrawAuth), +// Receive: validator.Phone, +// Captcha: validator.SmsCaptcha, +// } +// phoneCode = aduserdb.GetVerifyCode(orm, phoneCheck) +// if phoneCode.Id == 0 { +// return statuscode.PhoneCaptchaInvalid +// } +// } +// +// var emailCode models.AdVerifyCode +// if len(validator.EmailCaptcha) > 0 && !CheckOpenVerifyCode(validator.EmailCaptcha) { +// // 校验邮箱号码发送的验证码 +// emailCheck := sysmodel.CheckCaptcha{ +// BusinessType: int(businesstype.WithdrawAuth), +// Receive: validator.Email, +// Captcha: validator.EmailCaptcha, +// } +// emailCode = aduserdb.GetVerifyCode(orm, emailCheck) +// if emailCode.Id == 0 { +// return statuscode.EmailCaptchaInvalid +// } +// } +// if len(validator.GoogleCaptcha) > 0 { +// // 校验谷歌的验证码 +// auth2 := googleauthhelper.VerifyCode(validator.GoogleSecret, utility.StringAsInt32(validator.GoogleCaptcha)) +// if !auth2 { +// return statuscode.GoogleCaptchaInvalid +// } +// } +// +// // ==================== 2 新绑定的验证器校验 ==================== // +// var phoneNewCode models.AdVerifyCode +// // 开启手机|更改手机验证 都需要校验新手机 +// if auth.BusinessType == businesstype.OpenPhoneAuth || auth.BusinessType == businesstype.ChangePhoneAuth { +// if !CheckOpenVerifyCode(auth.NewPhoneCaptcha) { +// cc := sysmodel.CheckCaptcha{ +// BusinessType: int(auth.BusinessType), +// Receive: auth.NewPhone, +// Captcha: auth.NewPhoneCaptcha, +// } +// phoneNewCode = aduserdb.GetVerifyCode(orm, cc) +// if phoneNewCode.Id == 0 { +// return statuscode.NewPhoneCaptchaInvalid +// } +// } +// } +// +// var emailNewCode models.AdVerifyCode +// // 开启邮箱|更改邮箱验证 都需要校验新邮箱 +// if auth.BusinessType == businesstype.OpenEmailAuth || auth.BusinessType == businesstype.ChangeEmailAuth { +// if !CheckOpenVerifyCode(auth.NewEmailCaptcha) { +// cc := sysmodel.CheckCaptcha{ +// BusinessType: int(auth.BusinessType), +// Receive: auth.NewEmail, +// Captcha: auth.NewEmailCaptcha, +// } +// emailNewCode = aduserdb.GetVerifyCode(orm, cc) +// if emailNewCode.Id == 0 { +// return statuscode.NewEmailCaptchaInvalid +// } +// } +// } +// +// // 开启谷歌验证器 需要验证谷歌 +// if auth.BusinessType == businesstype.OpenGoogleAuth { +// newSecret, _ := aduserdb.GetNewGoogleSecret(orm, auth.UserID) +// if ok := googleauthhelper.VerifyCode(newSecret, utility.StringAsInt32(auth.NewGoogleCaptcha)); !ok { +// // tx.Rollback() +// log.Error("google验证码校验失败") +// return statuscode.GoogleCaptchaInvalid +// } +// } +// if phoneCode.Id == 0 && phoneNewCode.Id == 0 && emailCode.Id == 0 && emailNewCode.Id == 0 { +// log.Error("AuthSwitchBefore", zap.String("没验证旧手机、新手机、旧邮箱、新邮箱,估计只验证谷歌验证器", +// "phoneCode.Id ==0 && phoneNewCode.Id ==0 && emailCode.Id ==0&& emailNewCode.Id ==0")) +// return statuscode.OK +// } +// +// var transCode int +// err = orm.Transaction(func(tx *gorm.DB) error { +// // ==================== 1 已开通验证器校验,修改表数据为已验证 ==================== // +// if phoneCode.Id > 0 { // 旧手机验证码修改 +// err := aduserdb.UpdateVerifyFlag(tx, phoneCode.Id) +// if err != nil { +// log.Error("AuthSwitchBefore phoneCode", zap.Error(err)) +// // _ = tx.Rollback() +// transCode = statuscode.PhoneCaptchaInvalid +// return err +// } +// } +// if emailCode.Id > 0 { // 旧邮箱验证码修改 +// err = aduserdb.UpdateVerifyFlag(tx, emailCode.Id) +// if err != nil { +// log.Error("AuthSwitchBefore emailCode", zap.Error(err)) +// // _ = tx.Rollback() +// transCode = statuscode.EmailCaptchaInvalid +// +// return err +// } +// } +// +// // ==================== 2 新绑定的验证器校验,修改表数据为已验证 ==================== // +// if phoneNewCode.Id > 0 { // 新手机验证码修改 +// err = aduserdb.UpdateVerifyFlag(tx, phoneNewCode.Id) +// if err != nil { +// log.Error("AuthSwitchBefore phoneNewCode", zap.Error(err)) +// +// transCode = statuscode.PhoneCaptchaInvalid +// +// return err +// } +// } +// if emailNewCode.Id > 0 { // 新邮箱验证码修改 +// err = aduserdb.UpdateVerifyFlag(tx, emailNewCode.Id) +// if err != nil { +// log.Error("AuthSwitchBefore emailNewCode", zap.Error(err)) +// +// transCode = statuscode.EmailCaptchaInvalid +// +// return err +// } +// } +// +// return nil +// }) +// // 开启事务 +// // tx, err := dbhelper.MasterPgdb.Beginx() +// // if err != nil { +// // return statuscode.ServerError +// // } +// +// // // ==================== 1 已开通验证器校验,修改表数据为已验证 ==================== // +// // if phoneCode.Id > 0 { // 旧手机验证码修改 +// // err = aduserdb.UpdateVerifyFlag(phoneCode.Id, tx) +// // if err != nil { +// // log.Error("AuthSwitchBefore phoneCode", zap.Error(err)) +// // _ = tx.Rollback() +// // return statuscode.PhoneCaptchaInvalid +// // } +// // } +// // if emailCode.Id > 0 { // 旧邮箱验证码修改 +// // err = aduserdb.UpdateVerifyFlag(emailCode.Id, tx) +// // if err != nil { +// // log.Error("AuthSwitchBefore emailCode", zap.Error(err)) +// // _ = tx.Rollback() +// // return statuscode.EmailCaptchaInvalid +// // } +// // } +// +// // // ==================== 2 新绑定的验证器校验,修改表数据为已验证 ==================== // +// // if phoneNewCode.Id > 0 { // 新手机验证码修改 +// // err = aduserdb.UpdateVerifyFlag(phoneNewCode.Id, tx) +// // if err != nil { +// // log.Error("AuthSwitchBefore phoneNewCode", zap.Error(err)) +// // _ = tx.Rollback() +// // return statuscode.PhoneCaptchaInvalid +// // } +// // } +// // if emailNewCode.Id > 0 { // 新邮箱验证码修改 +// // err = aduserdb.UpdateVerifyFlag(emailNewCode.Id, tx) +// // if err != nil { +// // log.Error("AuthSwitchBefore emailNewCode", zap.Error(err)) +// // _ = tx.Rollback() +// // return statuscode.EmailCaptchaInvalid +// // } +// // } +// // // 提交事务 +// // if err = tx.Commit(); err != nil { +// // return statuscode.ServerError +// // } +// +// if err != nil { +// return transCode +// } +// return statuscode.OK +//} +// +//// AuthSwitchNew 身份验证开关(新) +//func AuthSwitchNew(orm *gorm.DB, auth sysmodel.AuthenticatorSwitch) int { +// // 验证器-开关 +// switch auth.BusinessType { +// case businesstype.OpenPhoneAuth: +// if err := aduserdb.BindPhoneAuth(orm, auth.UserID, auth.NewPhoneArea, auth.NewPhone); err != nil { +// return statuscode.ServerError +// } +// case businesstype.ChangePhoneAuth: +// if err := aduserdb.ChangePhoneAuth(orm, auth.UserID, auth.NewPhoneArea, auth.NewPhone); err != nil { +// return statuscode.ServerError +// } +// // hc todo 注释掉 先不急 +// // 更新商家信息 +// // merchant := merchantdb.GetMerchantInfoForUserId(orm, auth.UserID) +// // if merchant.Id > 0 { +// // _ = merchantdb.UpdateMerchantMobile(auth.UserID, auth.NewPhone) +// // } +// case businesstype.ClosePhoneAuth: +// if err := aduserdb.ClosePhoneAuth(orm, auth.UserID); err != nil { +// return statuscode.ServerError +// } +// +// case businesstype.OpenEmailAuth: +// if err := aduserdb.BindEmailAuth(orm, auth.UserID, auth.NewEmail); err != nil { +// return statuscode.ServerError +// } +// case businesstype.ChangeEmailAuth: +// if err := aduserdb.ChangeEmailAuth(orm, auth.UserID, auth.NewEmail); err != nil { +// return statuscode.ServerError +// } +// case businesstype.CloseEmailAuth: +// if err := aduserdb.CloseEmailAuth(orm, auth.UserID); err != nil { +// return statuscode.ServerError +// } +// +// case businesstype.OpenGoogleAuth: +// if err := aduserdb.OpenGoogleAuth(orm, auth.UserID); err != nil { +// return statuscode.ServerError +// } +// case businesstype.ChangeGoogleAuth: +// if err := aduserdb.CloseGoogleAuth(orm, auth.UserID); err != nil { +// return statuscode.ServerError +// } +// case businesstype.CloseGoogleAuth: +// if err := aduserdb.CloseGoogleAuth(orm, auth.UserID); err != nil { +// return statuscode.ServerError +// } +// +// default: +// return statuscode.ParameterInvalid +// } +// +// return statuscode.OK +//} diff --git a/common/service/sysservice/authservice/captcha.go b/common/service/sysservice/authservice/captcha.go new file mode 100644 index 0000000..6c9c2c2 --- /dev/null +++ b/common/service/sysservice/authservice/captcha.go @@ -0,0 +1,542 @@ +package authservice + +import ( + "bytes" + "fmt" + "github.com/bytedance/sonic" + log "github.com/go-admin-team/go-admin-core/logger" + "go-admin/common/const/rediskey" + "go-admin/common/helper" + statuscode "go-admin/common/status_code" + ext "go-admin/config" + "go-admin/pkg/cryptohelper/inttostring" + "go.uber.org/zap" + "io/ioutil" + "net/http" + "time" +) + +// +//// SendCaptchaBefore 发送验证码前之前检查 +//func SendCaptchaBefore(orm *gorm.DB, auth sysmodel.SendCaptchaReq) int { +// var ( +// user models.AdUser +// err error +// ) +// +// // 除 注册、开启手机|邮箱验证、更改手机|邮箱验证 外, +// // 其余业务发送前都需验证账号是否存在 +// if !(auth.BusinessType == int(businesstype.Register) || +// auth.BusinessType == int(businesstype.OpenPhoneAuth) || +// auth.BusinessType == int(businesstype.OpenEmailAuth) || +// auth.IsNew == 1 && auth.BusinessType == int(businesstype.ChangePhoneAuth) || +// auth.IsNew == 1 && auth.BusinessType == int(businesstype.ChangeEmailAuth)) { +// +// if auth.SendType == sysmodel.TSmsCode { +// // 手机 +// user, err = aduserdb.GetUserByPhone(orm, auth.PhoneAreaCode, auth.Phone) +// if err != nil { +// return statuscode.ServerError +// } +// } else if auth.SendType == sysmodel.TEmailCode { +// // 邮箱 +// user, err = aduserdb.GetUserByEmail(orm, auth.Email) //GetUser("useremail", auth.Email) +// if err != nil { +// return statuscode.ServerError +// } +// } +// +// // 账号不存在 +// if user.Id == 0 { +// return statuscode.TheAccountIsNotRegistered +// } +// } +// +// // 除:1 注册、2 登录、3 找回密码外,其余业务都需要登录(直接通过 uid 获取登录用户) +// if auth.BusinessType > 3 { +// user, err = aduserdb.GetUserById(orm, auth.Uid) //"id", auth.Uid) +// if err != nil { +// return statuscode.ServerError +// } +// } +// +// // 开启手机、开启邮箱 判断号码是否被占用(自己的号码判定为不占用) +// if auth.BusinessType == int(businesstype.OpenPhoneAuth) || auth.BusinessType == int(businesstype.OpenEmailAuth) { +// if auth.SendType == sysmodel.TSmsCode { +// // 手机 +// user, err = aduserdb.GetUserByPhone(orm, auth.PhoneAreaCode, auth.Phone) +// if err != nil { +// return statuscode.ServerError +// } +// +// if user.Id != 0 && user.Id != auth.Uid { +// return statuscode.ThePhoneHasBeenBound +// } +// } else if auth.SendType == sysmodel.TEmailCode { +// // 邮箱 +// user, err = aduserdb.GetUserByEmail(orm, auth.Email) //GetUser("useremail", auth.Email) +// if err != nil { +// return statuscode.ServerError +// } +// +// if user.Id != 0 && user.Id != auth.Uid { +// return statuscode.TheEmailHasBeenBound +// } +// } +// } +// +// // 更改手机、更改邮箱 仅判断新号码是否被占用(自己的号码判定为占用) +// if auth.IsNew == 1 && +// (auth.BusinessType == int(businesstype.ChangePhoneAuth) || auth.BusinessType == int(businesstype.ChangeEmailAuth)) { +// +// if auth.SendType == sysmodel.TSmsCode { +// // 手机 +// user, err = aduserdb.GetUserByPhone(orm, auth.PhoneAreaCode, auth.Phone) +// if err != nil { +// return statuscode.ServerError +// } +// +// if user.Id != 0 { +// return statuscode.ThePhoneHasBeenBound +// } +// } else if auth.SendType == sysmodel.TEmailCode { +// // 邮箱 +// user, err = aduserdb.GetUserByEmail(orm, auth.Email) //GetUser("useremail", auth.Email) +// if err != nil { +// return statuscode.ServerError +// } +// +// if user.Id != 0 { +// return statuscode.TheEmailHasBeenBound +// } +// } +// } +// +// // 发送次数校验 +// var sign interface{} = auth.Uid +// if sign == 0 { +// sign = auth.IP +// } +// key := fmt.Sprintf(rediskey.UserCaptchaSendFre, sign, auth.BusinessType) +// +// if countStr, err2 := helper.DefaultRedis.GetString(key); err2 == nil { +// count, _ := strconv.Atoi(countStr) +// +// if count >= 20 { +// return statuscode.CaptchaSendFrequencyExceedLimit +// } +// } +// /*if auth.BusinessType == int(enum.Register) { +// if count > 0 { +// return enum.CaptchaSendFrequencyExceedLimit +// } +// } else { +// if count >= 20 { +// return enum.CaptchaSendFrequencyExceedLimit +// } +// }*/ +// +// return statuscode.OK +//} +// +//// IsRepeatSend 是否重复发送验证码 +//func IsRepeatSend(orm *gorm.DB, receive string, businessType int) (bool, int) { +// verifyCode, err := aduserdb.IsRepeatSend(orm, receive, businessType) +// if err != nil { +// return false, 0 +// } +// +// if verifyCode.Id != 0 { +// // 给 send 加时区,保持与当前时区一致 +// send := verifyCode.SendTime +// send = time.Date(send.Year(), send.Month(), send.Day(), send.Hour(), send.Minute(), send.Second(), send.Nanosecond(), time.Local) +// +// // wait = send+60-now +// wait := send.Add(time.Minute).Sub(time.Now()).Seconds() +// return true, int(wait) +// } +// +// return false, 0 +//} +// +//// 生成验证码 +//func genVerifyCode() string { +// return fmt.Sprintf("%06v", rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(1000000)) +//} +// +////// 阿里云 - 发送手机验证码 +////func SendALYSms(areaPhoneCode, phoneNumber string, businessType int) error { +//// +//// var smsCode = genVerifyCode() +//// +//// resp, err := smshelper.SendALYSmsCode(areaPhoneCode, phoneNumber, smsCode) +//// if err != nil { +//// return err +//// } +//// +//// if *resp.Code != "OK" || *resp.Message != "OK" { +//// return errors.New(*resp.Message) +//// } +//// +//// if err := RecordAuthCode(smsCode, phoneNumber, businessType); err != nil { +//// log.Error("record sms auth code", zap.Error(err)) +//// return errors.New("this sms verification code is invalid") +//// } +//// +//// return nil +////} +// +//// SendXDYSms 信达云 - 发送手机验证码 +//func SendXDYSms(orm *gorm.DB, areaPhoneCode, phoneNumber string, businessType int) error { +// // SMS 模板 +// msgBody := "" +// business := "" +// if businessType <= 102 { +// // <100 用通用模板 +// msgBody = smstemplate.SmsTemplate_1_CN +// business = businesstype.BusinessTypeMapCN[businesstype.BusinessType(businessType)] +// if areaPhoneCode != "86" { +// msgBody = smstemplate.SmsTemplate_1_EN +// business = businesstype.BusinessTypeMapEN[businesstype.BusinessType(businessType)] +// +// } +// } else { +// //暂时只写中文 通用验证码 +// msgBody = smstemplate.SmsTemplate_1_CN +// business = businesstype.BusinessTypeMapCN[businesstype.BusinessType(businessType)] +// } +// +// // 内容替换 +// var smsCode = genVerifyCode() +// msgBody = strings.Replace(msgBody, "${business}", business, 1) +// msgBody = strings.Replace(msgBody, "${code}", smsCode, 1) +// +// // 发送验证码 +// _, err := smshelper.SendDXYSmsCode(areaPhoneCode, phoneNumber, msgBody) +// if err != nil { +// return err +// } +// +// // 记录验证码 +// if err := RecordAuthCode(orm, smsCode, phoneNumber, businessType); err != nil { +// log.Error("record sms auth code", zap.Error(err)) +// return errors.New("this sms verification code is invalid") +// } +// +// return nil +//} +// +//// XindaCloud 回执报告 +////func XindaCloudReceipt(ctx *fiber.Ctx) error { +//// var req interface{} +//// if err := ctx.BodyParser(&req); err != nil { +//// log.Error("BodyParser", zap.Error(err)) +//// } +//// +//// _ = redishelper.HMSet("XindaCloudReceipt", req) +//// +//// return nil +////} +// +////// SendUserEmail 发送邮箱验证码 +////func SendUserEmail(sendTo string, businessType int) error { +//// // 生成随机验证码 +//// emailCode := genVerifyCode() +//// +//// send := config.EmailSend{ +//// EmailConfig: config.AppGlobalConfig.EmailConfig, +//// Subject: "邮箱验证码", +//// Content: "[HS] 您的邮箱验证码为: " + emailCode, +//// To: sendTo, +//// } +//// +//// if _, ok := emailhelper.SendEmail(send); ok { +//// if err := RecordAuthCode(emailCode, sendTo, businessType); err != nil { +//// log.Error("record email auth code", zap.Error(err)) +//// return errors.New("this email verification code is invalid") +//// } +//// +//// return nil +//// } +//// +//// return errors.New("email send failed") +////} +// +//func MailJetSend(orm *gorm.DB, receive string, businessType int) error { +// // 生成随机验证码 +// code := genVerifyCode() +// +// if err := emailhelper.MailJetSend(receive, code); err != nil { +// return err +// } +// +// if err := RecordAuthCode(orm, code, receive, businessType); err != nil { +// log.Error("record email auth code", zap.Error(err)) +// return errors.New("record email auth code failed") +// } +// +// return nil +//} +// +//// RecordAuthCode 记录验证码 +//func RecordAuthCode(orm *gorm.DB, authCode, receive string, businessType int) error { +// var ( +// sendTime = time.Now() +// expireTime = sendTime.Add(sysmodel.CodeValidTime) +// ) +// +// var authCodeInfo = &models.AdVerifyCode{ +// Phone: receive, +// Code: authCode, +// SendTime: sendTime, +// ValidTimeLen: int(sysmodel.CodeValidTime.Seconds()), +// VerifyTime: expireTime, +// VerifyFlag: 0, +// BusinessType: businessType, +// } +// +// return orm.Model(authCodeInfo).Create(authCodeInfo).Error +// // return dbhelper.MasterPgdb.Insert("ad_verifycode", authCodeInfo) +//} +// +//// // CheckCaptcha 校验验证码 +//// func CheckCaptcha(cc sysmodel.CheckCaptcha, tx *sqlx.Tx) error { +//// // TODO 免检测验证码 +//// if cc.Captcha == "123456" { +//// return nil +//// } +//// if cc.Captcha == config.AppGlobalConfig.OpenVerifyCode { +//// return nil +//// } +//// +//// return aduserdb.UpdateVerifyCode(cc, tx) +//// } +// +//// CheckOpenVerifyCode 校验验证码是否免检测验证码,true免检测验证码,false需要检测验证码 +//func CheckOpenVerifyCode(captcha string) bool { +// // TODO 免检测验证码 +// //if captcha == "123456" { +// // return true +// //} +// +// if captcha == config.ExtConfig.OpenVerifyCode { +// return true +// } +// return false +//} +// +//// CheckPhoneOrEmailCaptcha 手机或者邮箱验证码检测 +//func CheckPhoneOrEmailCaptcha(orm *gorm.DB, phone sysmodel.CheckCaptcha) int { +// return CheckCaptchaNew(orm, phone, sysmodel.CheckCaptcha{}, "", "") +//} +// +//// CheckCaptchaNew 校验验证码,1.phone手机验证,2.email邮箱验证,3.googleAuth谷歌验证,也可以只传一个结构体,比如phone验证一个,无需指定是手机或者邮箱 +//func CheckCaptchaNew(orm *gorm.DB, phone sysmodel.CheckCaptcha, email sysmodel.CheckCaptcha, googleSecret, googleCaptcha string) int { +// // TODO 免检测验证码 +// +// var phoneCode models.AdVerifyCode +// if len(phone.Captcha) > 0 && !CheckOpenVerifyCode(phone.Captcha) { +// // 校验手机号码发送的验证码 +// phoneCode = aduserdb.GetVerifyCode(orm, phone) +// if phoneCode.Id == 0 { +// return statuscode.PhoneCaptchaInvalid +// } +// } +// +// var emailCode models.AdVerifyCode +// if len(email.Captcha) > 0 && !CheckOpenVerifyCode(email.Captcha) { +// // 校验邮箱号码发送的验证码 +// emailCode = aduserdb.GetVerifyCode(orm, email) +// if emailCode.Id == 0 { +// return statuscode.EmailCaptchaInvalid +// } +// } +// if len(googleCaptcha) > 0 { +// // 校验谷歌的验证码 +// auth := googleauthhelper.VerifyCode(googleSecret, utility.StringAsInt32(googleCaptcha)) +// if !auth { +// return statuscode.GoogleCaptchaInvalid +// } +// } +// // 没手机+邮箱验证,应该只验证谷歌,到这里就返回ok +// if phoneCode.Id == 0 && emailCode.Id == 0 { +// return statuscode.OK +// } +// +// var transCode int +// +// // 更新手机+邮箱验证码的数据状态 +// err := orm.Transaction(func(tx *gorm.DB) (err error) { +// +// if phoneCode.Id > 0 { +// err = aduserdb.UpdateVerifyFlag(orm, phoneCode.Id) +// if err != nil { +// // _ = tx.Rollback() +// transCode = statuscode.PhoneCaptchaInvalid +// +// return err +// } +// } +// if emailCode.Id > 0 { +// err = aduserdb.UpdateVerifyFlag(orm, emailCode.Id) +// if err != nil { +// // _ = tx.Rollback() +// transCode = statuscode.EmailCaptchaInvalid +// +// return err +// } +// } +// +// return nil +// }) +// // tx, err := dbhelper.MasterPgdb.Beginx() +// // if err != nil { +// // log.Error("Begin", zap.Error(err)) +// // return statuscode.ServerError +// // } +// // if phoneCode.Id > 0 { +// // err = aduserdb.UpdateVerifyFlag(orm, phoneCode.Id) +// // if err != nil { +// // _ = tx.Rollback() +// // return statuscode.PhoneCaptchaInvalid +// // } +// // } +// // if emailCode.Id > 0 { +// // err = aduserdb.UpdateVerifyFlag(orm, emailCode.Id) +// // if err != nil { +// // _ = tx.Rollback() +// // return statuscode.EmailCaptchaInvalid +// // } +// // } +// // _ = tx.Commit() +// +// if err != nil { +// return transCode +// } +// +// return statuscode.OK +//} +// +//// CheckCredentials 校验凭证 +//func CheckCredentials(req sysmodel.Credential, credentials string) bool { +// // 解析得到 Credentials Json +// CreJ := aeshelper.Decrypt(credentials, codeVerifySuccess) +// if len(CreJ) <= 0 { +// log.Error("credentials error") +// return false +// } +// +// Cre := sysmodel.Credential{} +// _ = sonic.Unmarshal([]byte(CreJ), &Cre) +// +// // 凭证最长有效时间2分钟 +// var maxValidTime = int64(2 * time.Minute.Seconds()) +// +// // 逐一比对凭证字段 +// if Cre.BusinessType != req.BusinessType { +// log.Error(fmt.Sprintf("凭证业务类型有误, BusinesType: %d credentialsBusinesType %d", req.BusinessType, Cre.BusinessType)) +// return false +// } +// +// if Cre.UserID != req.UserID { +// log.Error(fmt.Sprintf("凭证用户ID有误, UserID: %d credentialsUserID %d", req.UserID, Cre.UserID)) +// return false +// } +// +// if len(Cre.Phone) > 0 && Cre.Phone != req.Phone { +// log.Error(fmt.Sprintf("凭证手机有误, Phone: %s credentialsPhone %s", req.Phone, Cre.Phone)) +// return false +// } +// +// if len(Cre.Email) > 0 && Cre.Email != req.Email { +// log.Error(fmt.Sprintf("凭证邮箱有误, Email: %s credentialsEmail %s", req.Email, Cre.Email)) +// return false +// } +// +// if time.Now().Unix() > Cre.Time+maxValidTime { +// log.Error(fmt.Sprintf("凭证已过期, 当前时间: %d 凭证过期时间 %d", time.Now().Unix(), Cre.Time+maxValidTime)) +// return false +// } +// +// return true +//} + +func SendGoToneSms(phone, area string) int { + //smsCode = + smsString := inttostring.GenerateRandomSmsString(6) + defer func() { + // 使用 recover 来捕获 panic,避免 goroutine 导致程序崩溃 + if r := recover(); r != nil { + log.Error("SendRegisterEmail Error:", r) + } + }() + get := helper.DefaultRedis.Get(fmt.Sprintf("mobile-%s-register", phone)) + if get.Val() != "" { //说明邮箱操作频繁 + return statuscode.GoToneSmsOrderTooOften + } + key := fmt.Sprintf(rediskey.PCRegisterMobile, phone) + if err := helper.DefaultRedis.SetStringExpire(key, smsString, time.Second*300); err != nil { + log.Error("sendEmail setRedis Error:", zap.Error(err)) + return statuscode.ServerError + } + //ext.ExtConfig.GoToneSmsConfig + // SmsRequest 用于构建发送短信请求的结构体 + type SmsRequest struct { + Recipient string `json:"recipient"` // 收件人电话号码 + Message string `json:"message"` // 短信内容 + SenderId string `json:"sender_id"` // 发送者名称 + Type string `json:"type"` + } + // 创建请求数据 + smsRequest := SmsRequest{ + Recipient: "+" + area + phone, + SenderId: ext.ExtConfig.GoToneSmsConfig.SenderId, + Message: fmt.Sprintf("欢迎使用 GoTone SMS,高速稳定地发送短信至中国大陆及全球用户,体验验证码:%s。如非本人操作请忽略此信息", smsString), + Type: "plain", + } + // 将请求数据编码为 JSON + requestBody, err := sonic.Marshal(smsRequest) + if err != nil { + log.Error("GoToneSms requestBody Error:", err) + return statuscode.ServerError + } + // 创建 HTTP 请求 + req, err := http.NewRequest("POST", ext.ExtConfig.GoToneSmsConfig.APIEndpoint, bytes.NewBuffer(requestBody)) + if err != nil { + log.Error("GoToneSms http.NewRequest Error:", err) + return statuscode.ServerError + } + // 设置请求头 + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+ext.ExtConfig.GoToneSmsConfig.Authorization) // 使用 API 密钥进行身份验证 + + // 创建 HTTP 客户端并发送请求 + client := &http.Client{ + Timeout: 10 * time.Second, // 设置请求超时时间 + } + resp, err := client.Do(req) + fmt.Println("resp:", resp) + if err != nil { + log.Error("GoToneSms do NewRequest Error:", err) + return statuscode.CaptchaFailInSend + } + defer resp.Body.Close() + + // 检查响应状态 + if resp.StatusCode != http.StatusOK { + log.Error("GoToneSms do NewRequest Error:", err) + return statuscode.CaptchaFailInSend + } + // 读取响应体 + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Error("读取响应体失败:", err) + fmt.Printf("响应体: %s", string(body)) + return statuscode.CaptchaFailInSend + //return fmt.Errorf("读取响应体失败: %v", err) + } + // 打印响应内容(调试用) + //记录短信发送操作 + helper.DefaultRedis.SetStringExpire(fmt.Sprintf("mobile-%s-register", phone), "register", time.Second*60) + return statuscode.OK +} diff --git a/common/service/sysservice/authservice/mailjet_test.go b/common/service/sysservice/authservice/mailjet_test.go new file mode 100644 index 0000000..bc04aed --- /dev/null +++ b/common/service/sysservice/authservice/mailjet_test.go @@ -0,0 +1,36 @@ +package authservice + +import ( + "fmt" + "github.com/mailjet/mailjet-apiv3-go" + "log" + "testing" +) + +func TestName(t *testing.T) { + mailjetClient := mailjet.NewMailjetClient("00d2889da90d5d90767bf04dc1bdc6fa", "f68cd84cd88b7e2aabce79c878a77e97") + messagesInfo := []mailjet.InfoMessagesV31{ + { + From: &mailjet.RecipientV31{ + Email: "a745455408@163.com", + Name: "Tokex", + }, + To: &mailjet.RecipientsV31{ + mailjet.RecipientV31{ + Email: "2811588041@qq.com", + Name: "YCZ", + }, + }, + Subject: "Register/Login", + TextPart: "欢迎注册登录我们的服务,您的验证码为:123456", + // HTMLPart: "

欢迎注册登录我们的服务,您的验证码为:1234567


May the delivery force be with you!", + // CustomID: "AppGettingStartedTest", + }, + } + messages := mailjet.MessagesV31{Info: messagesInfo} + res, err := mailjetClient.SendMailV31(&messages) + if err != nil { + log.Println(err) + } + fmt.Printf("Data: %+v\n", res) +} diff --git a/common/service/sysservice/sysstatuscode/statuscode.go b/common/service/sysservice/sysstatuscode/statuscode.go new file mode 100644 index 0000000..a323dd2 --- /dev/null +++ b/common/service/sysservice/sysstatuscode/statuscode.go @@ -0,0 +1,72 @@ +package sysstatuscode + +import ( + "fmt" + "go-admin/app/admin/models/sysmodel" + "go-admin/app/admin/service/common" + "go-admin/app/admin/service/sysdb" + "go-admin/pkg/utility" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +var ( + statusCodeMap = make(map[int]sysmodel.StatusCode) +) + +// InitStatusCodeLanguage 初始化状态码语言包 +func InitStatusCodeLanguage(orm *gorm.DB) { + statusCodeList := sysdb.GetAllStatusCode(orm) + for _, code := range statusCodeList { + statusCodeMap[code.Code] = code + } +} + +// GetStatusCodeLanguage 获取状态码多语言 +func GetStatusCodeLanguage(code int, language string) string { + c, ok := statusCodeMap[code] + if !ok { + return utility.IntToString(code) + } + switch language { + case "en": + return c.En + case "zh-CN": + return c.ZhCN + case "zh-HK": + return c.ZhHK + default: + return c.Explain + } + + //if strings.EqualFold("en", language) { + // return c.En + //} else if strings.EqualFold("zh-CN", language) { + // return c.ZhCN + //} else if strings.EqualFold("zh-HK", language) { + // return c.ZhHK + //} else { + // //默认返回 + // return c.Explain + //} +} + +func GetStatusCodeDescription(c *gin.Context, code int) string { + lang := common.GetLanguage(c) + + return GetStatusCodeLanguage(code, lang) +} + +func GetStatusCodeDiscreptionArgs(c *gin.Context, code int, langArg interface{}) string { + lang := common.GetLanguage(c) + description := GetStatusCodeLanguage(code, lang) + + if _, ok := langArg.(int); ok { + description = fmt.Sprintf(description, langArg) + } else if _, ok := langArg.(int64); ok { + description = fmt.Sprintf(description, langArg) + } + + return description +} diff --git a/common/status_code/code_3-4_w.go b/common/status_code/code_3-4_w.go new file mode 100644 index 0000000..603ccd1 --- /dev/null +++ b/common/status_code/code_3-4_w.go @@ -0,0 +1,34 @@ +package statuscode + +/** + * TODO 注意:新增状态码只能往后添加,不能往中间插 ... + */ + +// ===== 现货交易模块 [30000, 40000) ===== // +const ( + CoinDoesNotExist = 30000 + iota // 币种不存在 + OperationFailed // 操作失败 + CoinInfoEmpty // 币种信息必填 + CoinSymbolEmpty // 币种全称、简称必填 + OfferingPriceEmpty // 发行价格必填 + OfficialWebsiteEmpty // 官网地址必填 + TotalSupplyEmpty // 代币总量必填 + ProhibitionSpotTrading // 禁止现货交易 + ProhibitedContractTrading // 禁止合约交易 + ProhibitTransfer // 禁止划转交易 + ProhibitC2CAdvise // 禁止发布广告 + ProhibitC2CTrade // 禁止C2C交易 + CannotWithdraw //禁止提币 + WithdrawNumNotRange //提币数量不在可提的范围 + AlertSetLimitCoinTotalAll //当前交易对总数已达50条,无法继续添加 + AlertSetLimitCurrentCoinLimit //当前交易对已有10条价格提醒,无法继续添加 + AlertSetValueMustGreatThanHighest // 提醒价格必须高于最新价格 + AlertSetValueGreat100ThanHighest // 设置的价格不能大于当前价格100倍 + AlertSetValueMustLessThanHighPrice // 提醒价格必须低于最新价格 + AlertSetValueLessThan100HighPrice // 设置的价格不能小于当前价格100倍 + AlertSetValueIsExist // 当前币种的数据已存在 + WithDrawChangeAccountNotExist // 账户不存在 + WithDrawChangeAccountNotValid // 无法转到该账户。请确认这是有效的账户并且收款方开启了支付-设置-允许通过邮箱或手机号向我转账. + WithDrawChangeNotSendToSelf // 抱歉,无法给自己付款 + +) diff --git a/common/status_code/code_5-6w.go b/common/status_code/code_5-6w.go new file mode 100644 index 0000000..bd9be12 --- /dev/null +++ b/common/status_code/code_5-6w.go @@ -0,0 +1,84 @@ +package statuscode + +/** + * TODO 注意:新增状态码只能往后添加,不能往中间插 ... + */ + +// ===== 合约交易模块 [50000, 60000) ===== // +const ( + // VtsHoldIsEmpty 划转 + VtsHoldIsEmpty = 50000 + iota // 现货账户不存在 + VtsTransferFutNum // 数量不正确 + VtsTransferFutNotAvailable // 可用余额不足 + FutHoldIsEmpty // 合约账户不存在 + FutBalanceLessThanBond // 钱包余额不满足开此仓位最少起始保证金 + TransferIsNotRepeatSubmit // 请勿重复提交 + OtcHoldIsEmpty // Otc账户不存在 + + // OrderFailed 下单参数检查 + OrderFailed // 下单失败! + OrderPriceToHigh // 买单价格不能高于最新价格的10% + OrderPriceToLow // 卖单价格不能低于最新价的10% + OrderPriceLessThanZero // 非市价单下单价格不能小于0 + OrderNumLessThanZero // 下单数量不能小于0 + OrderNumTooMuch // 超过最大下单数量 + OrderTotalTooFew // 小于最小下单金额 + LackOfDepth // 深度不足无法下市价单 + OrderTriggerPriceErr // 触发价格不,不得小于最低0 + OrderBuyTypeErr // 参数订单状态:BuyType错误 + OrderTooOften // 下单请求太频繁 + AccountFrozenBalance // 账户余额不足 + OrderNoExist // 未查询到相关订单 + OrderClosed // 已成交订单 + + // HavePosition 合约部分 + HavePosition // 存在持仓或挂单时不允许调整 + PositionDirectionErr // 持仓方向跟用户设置的不一样 + UserNotHoldPosition // 用户未持仓 + UserHoldPositionInsufficient // 平仓数量不能超过仓位可平量 + TheLeverageAmountTooLarge // 当前杠杆倍数下单金额过大 + StopOrderLimit // 最大下单数量 + ToOther // 其他 + + BuyLong // 开多 + SellLog // 平多 + SellShort // 开空 + BuyShort // 平空 + + TipOfTheDayVim // 已经是当前状态,无需修改 + + IsolatedLeverageNotCut // 逐仓模式下,如果有仓位正在持仓,则不能减少杠杆倍率 + ParamErr // 请求参数无效 + MarginTypeSame // 保证金模式一样,无需修改 + CloseBuyErrCancel // 平仓委托失败,请检查持仓与挂单。 如果当前合约有挂单,请取消该合约的挂单,并再次尝试平仓 + CloseBuyLackOfDepth // 平仓失败,深度不足 + OrderPriceTooFew // 小于最小下单价格 + OrderNumTooFew // 小于最小下单数量 + + TransAccountBalanceNotEnough // 账户余额不足 + CrossNotAdjustMargin // 全仓保证金模式下不能调整保证金 + AdjustMarginAmtMore // 减少的保证金大于可以减少金额 + MaxLeverErr // 杠杆值超过设定最大值 + NewPrice // 最新成交价格 + MarkPrice // 标记价格 + Perpetual // 永续 + OrderClosedCancel // 已成交订单,无法撤销 + OrderExpiredCancel // 已过期订单,无法撤销 + OrderCancelErr // 已撤销订单,无法撤销 + AdjustLeverErr // 保证金不足,无法调整杠杆倍数 + PriceDigitErr // 价格精度错误 + TriggerPriceDigitErr // 触发价格精度错误 + NumDigitErr // 数量精度错误 + + QueryRangeCannotExceed3M // 查询范围不能超过3个月 + QueryRangeInvalid // 查询范围无效 + + OrderMinTriggerPriceErr // 触发价格不得小于最低价格 + OrderMaxTriggerPriceErr // 触发价格不得大于最高价格 + ReduceReject // 只减仓委托单被拒绝,请取消勾选后重试 + + PosTooMore //最大可开仓位数量 + + OrderNumLessCost // 最小下单数量为xxx张 + OrderNumGrateCost // 最大下单数量为xxx张 +) diff --git a/common/status_code/code_7-8_w.go b/common/status_code/code_7-8_w.go new file mode 100644 index 0000000..bb4391d --- /dev/null +++ b/common/status_code/code_7-8_w.go @@ -0,0 +1,90 @@ +package statuscode + +/** + * TODO 注意:新增状态码只能往后添加,不能往中间插 ... + */ + +// ===== OTC 交易模块 [70000, 80000) ===== // +const ( + KycUnauthorized = 70000 + iota // kyc未认证 + MobileUnauthorized // 绑定手机 + MobileCodeIsValid // 手机验证码无效 + OtcOrderCancelLimit // 法币订单取消今天已达到3次 + CurrCoinNotWhite // 当前所选法币不在用户KYC所在地区的法币清单内 + OtcLegalAreaLimit // 用户KYC地区在C2C限制交易地区 + OtcOrderIsProcessing // 当前有正在进行中的订单,不能再下单 + OtcCoinNotFound // 没有加载到币种信息 + AdUserAccountStatusIsForbidden // 您的账户目前被限制进行法币交易,请联系客服 + LegalCoinIsForbidden // 该法币已禁用 + OtcNotFoundOrderInfo // 没有加载到订单信息 + OtcNotFoundAdvInfo // 没有加载到广告信息 + OtcAppealExistInfo // 当前申诉进行中,不能再次提交 + OtcNotFoundAppealInfo // 没有加载到申诉信息 + OtcHoldUseNumIsNotEmough // 当前币种的可用余额不足 + OtcSellOrderAmountLimit // 最近24小时出售订单总额总和超出当前限额 + OtcUserSecurityModify24Hours // 您24小时内修改了验证方式,24小时内不可进行出售 + OtcLegalCoinLimit // 不在该法币限制额度的范围内 + OtcCoinRangeLimit // 不在该交易币的限额范围内 + OtcNotMatchAdvertise // 该币种暂无广告支持的交易 + OtcNotRepeatOpertion // 您已操作过,请勿重复操作 + OtcNotPermission // 您无权操作 + OtcOrderStatusNotAppeal // 当前订单状态不允许申诉 + OtcOrderStatusNotOperate // 当前订单状态不允许操作 + OtcAppealProgressNotOperate // 当前申诉状态不允许操作 + OtcOrderChange // 订单报价或数量发生了变化,请重新下单 + PushMerchantReqMax3 // 最多可选择3种付款方式 + MerchantNotFind // 商家不存在 + PleaseChooseLegalCoin // 请选择法币 + PleaseBringLegalCoinCode // 请带入法币代码 + PleaseChooseTradeMethod // 请选择交易方式 + PleaseBringTradeMethod // 请带入交易方式 + PleaseInputPaymentMethod // 请输入收款方式 + PleaseInputCaptcha // 请输入验证码 + InputItemRequired // %v(输入项名称) 必填 + IdRequired // ID 必须 + _ // 已弃用 + OtcCoinIsForbidden // 交易币已禁用 + OrdersNRequired // 订单号必填 + OtcAdvMinValueMoreThanPrice // 最小限额不能大于购买金额 + OtcCoinMarketPriceNotFound // 币种市场价没有加载到对应的数据 + NotSupportPaymentMethod // 暂不支持该付款方式 + PleaseSelectPayAccount // 请选择收款账号 + OtcAdvisterOrderNotComplete // 当前有正在进行的订单,无法修改 + OtcAdvisteMustHideBeforeEdit // 修改前请先将广告隐藏 + OtcQuantityNotThanComplete // 数量不能小于完成数量 + Sell // 卖出 + Buy // 买入 + SpotTransferOtc // 现货划转Otc + OtcTransferSpot // Otc划转现货 + Freeze // 冻结 + UnFreeze // 解冻 + BuckleMargin // 扣保证金 + OtcOfflineFailedHaveOrder // 前有正在进行的订单,无法下线 + OtcOfflineBeforeHiden // 下线前请先将广告隐藏 + OtcAdvertiseIsOnlineNotUnAuth // 当前有未下架的广告,不能解除认证 + OtcMerchantStatusIsNotOper // 当前状态不允许操作 + OtcMerchantNickPerYear // 昵称一年内只能更改一次 + OtcMerchantBalanceNotEngouh // 保证金余额不足 + FriendIdOrOrdersNRequired // 朋友ID或订单号必填一个 + OtcAdvMinValueMoreThanMaxValue // 最低额不能大于单笔最高额 + OtcAdvPriceIsNotValid // 参数价格不能为0 + OtcAdvSellAmountNotZero // 当前金额不能为零 + OtcAdvQuickCoinQuantiyNotRight // 填写的币种数量不正确 + OtcAdvQuickCoinAmountNotRight // 填写的金额不正确 + OtcAdvRateChanged // 当前汇率已变化,请重新下单 + OtcAdvSurpluseNotEngouh // 广告剩余数量不足 + OtcAdvertiseNotConnetOperation //广告已完成的数量不足,无法操作 + OtcAdvertiesePayAccountIsEmpty // 收款方式账户不能为空 + OtcHoldFreeNumIsNotEngouch // 该币种的冻结金额不足 + OtcAdvMinValueLessThanLimit // 限额最小值不能小于默认值 + OtcAdvMaxValueMoreThanLimit // 限额最大值不能大于默认值 + OtcAdvPublishTradeMoreThanLimit // 最多可选择3种付款方式 + OtcAdvHoldCoinIsEnabled // 您当前的币种账户已冻结 + Buckle // 划扣 + OtcAdvPriceRateIsNotValid // 浮动比例输入错误 + OtcOrderStatusIsNotWaitMoney // 当前订单状态非待放币状态 + OtcOrderTakeRepeate // 您已划扣过该笔订单,请勿再操作 + OtcMerchatnApplyRepeate // 您不能再申请成为商家 + OtcAdvertiseStatsIsOffline // 当前广告已下架 + AddressUpperLimit10 //地址上限为10 +) diff --git a/common/status_code/status_code.go b/common/status_code/status_code.go new file mode 100644 index 0000000..ce6aa23 --- /dev/null +++ b/common/status_code/status_code.go @@ -0,0 +1,113 @@ +package statuscode + +/** + * TODO 注意:新增状态码只能往后添加,不能往中间插 ... + */ + +// ===== 个人中心模块 [10000, 20000) ===== // +const ( + UploadFileTooBig = 10000 + iota // 上传文件不能大于 %v + NotLoggedIn // 未登录 + ReLogin // 您的账号已在其它地方登录,请重新登录 + ParameterInvalid // 参数无效 + PleaseSelectThePhoneAreaCode // 请选择手机区号 + PhoneRequired // 手机号必填 + PhoneFormatInvalid // 手机号格式有误 + EmailRequired // 邮箱必填 + EmailFormatInvalid // 邮箱格式有误 + PasswordRequired // 密码必填 + PasswordRules // 密码为8-32位的数字和大小字母组合 + TheAccountIsAlreadyRegistered // 该账号已注册 + TheAccountIsNotRegistered // 该账号未注册 + CaptchaInvalid // 验证码无效 + InviterNotExist // 邀请人不存在 + RegisterSuccess // 注册成功 + UserIsFrozen // 您的账号已冻结,请 %v 后再试! + AccountOrPasswordError // 账号或密码错误,您还有 %v 次机会 + PleasePerformCompleteSecurityVerification // 请进行完整的安全验证 + PhoneCaptchaRequired // 手机验证码必填 + EmailCaptchaRequired // 邮箱验证码必填 + GoogleCaptchaRequired // 谷歌验证码必填 + PhoneCaptchaInvalid // 手机验证码无效 + EmailCaptchaInvalid // 邮箱验证码无效 + GoogleCaptchaInvalid // 谷歌验证码无效 + NewPhoneCaptchaInvalid // 新手机验证码无效 + NewEmailCaptchaInvalid // 新邮箱验证码无效 + SuccessLogin // 登录成功 + QRCodeExpired // 二维码已过期 + BusinessCredentialsError // 安全验证凭证错误 + UnsupportedBusinessType // 不支持的业务类型 + ThePhoneHasBeenBound // 手机号已被绑定 + TheEmailHasBeenBound // 邮箱已被绑定 + AllAuthMustOpen // 必须开启三重验证才能关闭其中一个 + OriginalPasswordRequired // 原密码必填 + NewPasswordRequired // 新密码必填 + OriginalPasswordError // 原密码错误 + CaptchaSendFrequencyExceedLimit // 验证码发送频次已达上限 + CaptchaFailInSend // 验证码发送失败 + CaptchaHasBeenSent // 验证码已发送,请勿重复获取 + CaptchaSentSuccessfully // 验证码已发送,有效期 %v 分钟 + + // KYC 认证 + PleaseSelectCountry // 请选择国家或地区 + PleaseEntryIdentName // 请输入姓名 + PleaseSelectIdCardType // 请选择证件类型 + PleaseEntryIdCard // 请输入证件号 + PelaseEntryValidIdCard // 请输入有效的身份证号 + PleaseSubmitRepeate // 请勿重复提交 + PleaseCompleteJunior // 请先完成标准身份验证 + PleaseCompleteMiddle // 请先完成进阶身份认证 + PleaseUploadImage1 // 请上传证件正面 + PleaseUploadImage2 // 请上传证件背面 + PleaseUploadVideo // 请上传视频文件 + IdCardInfoIsExist // 证件信息已存在 + TodayFaceFrequency // 今日人脸识别次数已达上限 + TotalFaceFrequency // 人脸识别次数已达上限 + FaceAccountIdIsEmpty // 活体检测账户不能为空 + FaceCreditailsIsEmpty // 活体检测身份证凭证不能为空 + FaceSelfCreditailsIsEmpty // 活体检测人脸凭证不能为空 + FaceLiveCreditailsIsEmpty // 活体检测凭证不能为空 + WorkFlowExecutionIdIsEmpty // 活体检测工作流序号不能为空 + InconsistentIDInformation // 证件信息不符 + WorkFlowExecutionIdIsFailed // 活体检测失败 + + EventNotification // 活动通知 + SystemMessages // 系统消息 + TransactionNotice // 交易通知 + + AccountIsFrozen // 您的账户当前禁止登录,请联系客服处理 + ValidationFailed // 验证失败 + InvalidQRCode // 二维码无效 + OTCOrdersAreInProgress // OTC订单正在进行中,不允许解绑手机 + OTCAdvertiseAreInProgress // OTC广告进行中,不允许解绑手机 + + AlreadyExistsSameProportion //已经存在相同比例的返佣设置 + JobInvalid //身份参数无效 + UserIdInvalid //请输入正确的id + NameIsEmpty //姓名必填 + ChannelsIsEmpty //渠道必填 + TelegramIsEmpty //Telegram必填 + AgentEmailIsExist //该邮箱已注册 + MAXContacts10 //常用联系人不能超过10个 + PayeeNotExist //抱歉,收款人不存在 + UserInCountryNotRegister // 您所在的地区暂时不能注册 + UnBindUserApi //用户未绑定 + PasswordsMustSame // 密码必须一致 + EmailNotExistOrEmailCOdeExpired // 邮箱未注册或验证码已过期 + EmailOrderTooOften // 邮箱发送频繁 + AccountNotVerifyEmail // 未验证邮箱 + GoToneSmsOrderTooOften // 短信发送频繁 + UserNotVerify // 当前账户未验证 + UserApiKeyExists // apikey已经存在,请勿重复添加 + UserApiKeyRequired // apikey或者密钥不存在,请先授权 + UserApiKeyInvalid //无效的ApiKey或密钥 + UserApiKeyPermissionError //密钥权限错误,请正确设置 + UserApiKeyNotExists //api不存在,请先去添加 +) + +// ===== Base Status Code ===== // +const ( + OK = 200 // 成功 + DataError = 4000 // 数据有误(数据不存在) + ServerError = 500 // 服务器出错 +) diff --git a/common/storage/initialize.go b/common/storage/initialize.go new file mode 100644 index 0000000..6fa323d --- /dev/null +++ b/common/storage/initialize.go @@ -0,0 +1,52 @@ +/* + * @Author: lwnmengjing + * @Date: 2021/6/10 3:39 下午 + * @Last Modified by: lwnmengjing + * @Last Modified time: 2021/6/10 3:39 下午 + */ + +package storage + +import ( + "log" + + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/sdk/config" + "github.com/go-admin-team/go-admin-core/sdk/pkg/captcha" +) + +// Setup 配置storage组件 +func Setup() { + //4. 设置缓存 + cacheAdapter, err := config.CacheConfig.Setup() + if err != nil { + log.Fatalf("cache setup error, %s\n", err.Error()) + } + sdk.Runtime.SetCacheAdapter(cacheAdapter) + //5. 设置验证码store + captcha.SetStore(captcha.NewCacheStore(cacheAdapter, 600)) + + //6. 设置队列 + if !config.QueueConfig.Empty() { + if q := sdk.Runtime.GetQueueAdapter(); q != nil { + q.Shutdown() + } + queueAdapter, err := config.QueueConfig.Setup() + if err != nil { + log.Fatalf("queue setup error, %s\n", err.Error()) + } + sdk.Runtime.SetQueueAdapter(queueAdapter) + defer func() { + go queueAdapter.Run() + }() + } + + //7. 设置分布式锁 + if !config.LockerConfig.Empty() { + lockerAdapter, err := config.LockerConfig.Setup() + if err != nil { + log.Fatalf("locker setup error, %s\n", err.Error()) + } + sdk.Runtime.SetLockerAdapter(lockerAdapter) + } +} diff --git a/config/READMEN.md b/config/READMEN.md new file mode 100644 index 0000000..154119e --- /dev/null +++ b/config/READMEN.md @@ -0,0 +1,37 @@ +# ⚙ 配置详情 + +1. 配置文件说明 +```yml +settings: + application: + # 项目启动环境 + mode: dev # dev开发环境 prod线上环境; + host: 0.0.0.0 # 主机ip 或者域名,默认0.0.0.0 + # 服务名称 + name: go-admin + # 服务端口 + port: 8000 + readtimeout: 1 + writertimeout: 2 + log: + # 日志文件存放路径 + dir: temp/logs + jwt: + # JWT加密字符串 + secret: go-admin + # 过期时间单位:秒 + timeout: 3600 + database: + # 数据库名称 + name: dbname + # 数据库类型 + dbtype: mysql + # 数据库地址 + host: 127.0.0.1 + # 数据库密码 + password: password + # 数据库端口 + port: 3306 + # 数据库用户名 + username: root +``` \ No newline at end of file diff --git a/config/db-begin-mysql.sql b/config/db-begin-mysql.sql new file mode 100644 index 0000000..04da2f1 --- /dev/null +++ b/config/db-begin-mysql.sql @@ -0,0 +1,2 @@ +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; \ No newline at end of file diff --git a/config/db-end-mysql.sql b/config/db-end-mysql.sql new file mode 100644 index 0000000..4bd9d01 --- /dev/null +++ b/config/db-end-mysql.sql @@ -0,0 +1 @@ +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/config/db-sqlserver.sql b/config/db-sqlserver.sql new file mode 100644 index 0000000..0ba4168 --- /dev/null +++ b/config/db-sqlserver.sql @@ -0,0 +1,342 @@ +-- 开始初始化数据 ; +SET IDENTITY_INSERT sys_api ON; +INSERT INTO sys_api (id, handle, title, path, type, "action", created_at, updated_at, deleted_at, create_by, update_by)VALUES +(5, 'go-admin/app/admin/apis.SysLoginLog.Get-fm', '登录日志通过id获取', '/api/v1/sys-login-log/:id', 'BUS', 'GET', '2021-05-13 19:59:00.728', '2021-06-17 11:37:12.331', NULL, 0, 0), +(6, 'go-admin/app/admin/apis.SysOperaLog.GetPage-fm', '操作日志列表', '/api/v1/sys-opera-log', 'BUS', 'GET', '2021-05-13 19:59:00.778', '2021-06-17 11:48:40.732', NULL, 0, 0), +(7, 'go-admin/app/admin/apis.SysOperaLog.Get-fm', '操作日志通过id获取', '/api/v1/sys-opera-log/:id', 'BUS', 'GET', '2021-05-13 19:59:00.821', '2021-06-16 21:49:48.598', NULL, 0, 0), +(8, 'go-admin/common/actions.IndexAction.func1', '分类列表', '/api/v1/syscategory', 'BUS', 'GET', '2021-05-13 19:59:00.870', '2021-06-13 20:53:47.883', NULL, 0, 0), +(9, 'go-admin/common/actions.ViewAction.func1', '分类通过id获取', '/api/v1/syscategory/:id', 'BUS', 'GET', '2021-05-13 19:59:00.945', '2021-06-13 20:53:47.926', NULL, 0, 0), +(10, 'go-admin/common/actions.IndexAction.func1', '内容列表', '/api/v1/syscontent', 'BUS', 'GET', '2021-05-13 19:59:01.005', '2021-06-13 20:53:47.966', NULL, 0, 0), +(11, 'go-admin/common/actions.ViewAction.func1', '内容通过id获取', '/api/v1/syscontent/:id', 'BUS', 'GET', '2021-05-13 19:59:01.056', '2021-06-13 20:53:48.005', NULL, 0, 0), +(15, 'go-admin/common/actions.IndexAction.func1', 'job列表', '/api/v1/sysjob', 'BUS', 'GET', '2021-05-13 19:59:01.248', '2021-06-13 20:53:48.169', NULL, 0, 0), +(16, 'go-admin/common/actions.ViewAction.func1', 'job通过id获取', '/api/v1/sysjob/:id', 'BUS', 'GET', '2021-05-13 19:59:01.298', '2021-06-13 20:53:48.214', NULL, 0, 0), +(21, 'go-admin/app/admin/apis.SysDictType.GetPage-fm', '字典类型列表', '/api/v1/dict/type', 'BUS', 'GET', '2021-05-13 19:59:01.525', '2021-06-17 11:48:40.732', NULL, 0, 0), +(22, 'go-admin/app/admin/apis.SysDictType.GetAll-fm', '字典类型查询【代码生成】', '/api/v1/dict/type-option-select', 'SYS', 'GET', '2021-05-13 19:59:01.582', '2021-06-13 20:53:48.388', NULL, 0, 0), +(23, 'go-admin/app/admin/apis.SysDictType.Get-fm', '字典类型通过id获取', '/api/v1/dict/type/:id', 'BUS', 'GET', '2021-05-13 19:59:01.632', '2021-06-17 11:48:40.732', NULL, 0, 0), +(24, 'go-admin/app/admin/apis.SysDictData.GetPage-fm', '字典数据列表', '/api/v1/dict/data', 'BUS', 'GET', '2021-05-13 19:59:01.684', '2021-06-17 11:48:40.732', NULL, 0, 0), +(25, 'go-admin/app/admin/apis.SysDictData.Get-fm', '字典数据通过code获取', '/api/v1/dict/data/:dictCode', 'BUS', 'GET', '2021-05-13 19:59:01.732', '2021-06-17 11:48:40.732', NULL, 0, 0), +(26, 'go-admin/app/admin/apis.SysDictData.GetSysDictDataAll-fm', '数据字典根据key获取', '/api/v1/dict-data/option-select', 'SYS', 'GET', '2021-05-13 19:59:01.832', '2021-06-17 11:48:40.732', NULL, 0, 0), +(27, 'go-admin/app/admin/apis.SysDept.GetPage-fm', '部门列表', '/api/v1/dept', 'BUS', 'GET', '2021-05-13 19:59:01.940', '2021-06-17 11:48:40.732', NULL, 0, 0), +(28, 'go-admin/app/admin/apis.SysDept.Get-fm', '部门通过id获取', '/api/v1/dept/:id', 'BUS', 'GET', '2021-05-13 19:59:02.009', '2021-06-17 11:48:40.732', NULL, 0, 0), +(29, 'go-admin/app/admin/apis.SysDept.Get2Tree-fm', '查询部门下拉树【角色权限-自定权限】', '/api/v1/deptTree', 'SYS', 'GET', '2021-05-13 19:59:02.050', '2021-06-17 11:48:40.732', NULL, 0, 0), +(30, 'go-admin/app/admin/apis/tools.(*Gen).GetDBTableList-fm', '数据库表列表', '/api/v1/db/tables/page', 'SYS', 'GET', '2021-05-13 19:59:02.098', '2021-06-13 20:53:48.730', NULL, 0, 0), +(31, 'go-admin/app/admin/apis/tools.(*Gen).GetDBColumnList-fm', '数据表列列表', '/api/v1/db/columns/page', 'SYS', 'GET', '2021-05-13 19:59:02.140', '2021-06-13 20:53:48.771', NULL, 0, 0), +(32, 'go-admin/app/admin/apis/tools.Gen.GenCode-fm', '数据库表生成到项目', '/api/v1/gen/toproject/:tableId', 'SYS', 'GET', '2021-05-13 19:59:02.183', '2021-06-13 20:53:48.812', NULL, 0, 0), +(33, 'go-admin/app/admin/apis/tools.Gen.GenMenuAndApi-fm', '数据库表生成到DB', '/api/v1/gen/todb/:tableId', 'SYS', 'GET', '2021-05-13 19:59:02.227', '2021-06-13 20:53:48.853', NULL, 0, 0), +(34, 'go-admin/app/admin/apis/tools.(*SysTable).GetSysTablesTree-fm', '关系表数据【代码生成】', '/api/v1/gen/tabletree', 'SYS', 'GET', '2021-05-13 19:59:02.271', '2021-06-13 20:53:48.893', NULL, 0, 0), +(35, 'go-admin/app/admin/apis/tools.Gen.Preview-fm', '生成预览通过id获取', '/api/v1/gen/preview/:tableId', 'SYS', 'GET', '2021-05-13 19:59:02.315', '2021-06-13 20:53:48.935', NULL, 0, 0), +(36, 'go-admin/app/admin/apis/tools.Gen.GenApiToFile-fm', '生成api带文件', '/api/v1/gen/apitofile/:tableId', 'SYS', 'GET', '2021-05-13 19:59:02.357', '2021-06-13 20:53:48.977', NULL, 0, 0), +(37, 'go-admin/app/admin/apis.System.GenerateCaptchaHandler-fm', '验证码获取', '/api/v1/getCaptcha', 'SYS', 'GET', '2021-05-13 19:59:02.405', '2021-06-13 20:53:49.020', NULL, 0, 0), +(38, 'go-admin/app/admin/apis.SysUser.GetInfo-fm', '用户信息获取', '/api/v1/getinfo', 'SYS', 'GET', '2021-05-13 19:59:02.447', '2021-06-13 20:53:49.065', NULL, 0, 0), +(39, 'go-admin/app/admin/apis.SysMenu.GetPage-fm', '菜单列表', '/api/v1/menu', 'BUS', 'GET', '2021-05-13 19:59:02.497', '2021-06-17 11:48:40.732', NULL, 0, 0), +(40, 'go-admin/app/admin/apis.SysMenu.GetMenuTreeSelect-fm', '查询菜单下拉树结构【废弃】', '/api/v1/menuTreeselect', 'SYS', 'GET', '2021-05-13 19:59:02.542', '2021-06-03 22:37:21.857', NULL, 0, 0), +(41, 'go-admin/app/admin/apis.SysMenu.Get-fm', '菜单通过id获取', '/api/v1/menu/:id', 'BUS', 'GET', '2021-05-13 19:59:02.584', '2021-06-17 11:48:40.732', NULL, 0, 0), +(42, 'go-admin/app/admin/apis.SysMenu.GetMenuRole-fm', '角色菜单【顶部左侧菜单】', '/api/v1/menurole', 'SYS', 'GET', '2021-05-13 19:59:02.630', '2021-06-13 20:53:49.574', NULL, 0, 0), +(43, 'go-admin/app/admin/apis.SysMenu.GetMenuIDS-fm', '获取角色对应的菜单id数组【废弃】', '/api/v1/menuids', 'SYS', 'GET', '2021-05-13 19:59:02.675', '2021-06-03 22:39:52.500', NULL, 0, 0), +(44, 'go-admin/app/admin/apis.SysRole.GetPage-fm', '角色列表', '/api/v1/role', 'BUS', 'GET', '2021-05-13 19:59:02.720', '2021-06-17 11:48:40.732', NULL, 0, 0), +(45, 'go-admin/app/admin/apis.SysMenu.GetMenuTreeSelect-fm', '菜单权限列表【角色配菜单使用】', '/api/v1/roleMenuTreeselect/:roleId', 'SYS', 'GET', '2021-05-13 19:59:02.762', '2021-06-17 11:48:40.732', NULL, 0, 0), +(46, 'go-admin/app/admin/apis.SysDept.GetDeptTreeRoleSelect-fm', '角色部门结构树【自定义数据权限】', '/api/v1/roleDeptTreeselect/:roleId', 'SYS', 'GET', '2021-05-13 19:59:02.809', '2021-06-17 11:48:40.732', NULL, 0, 0), +(47, 'go-admin/app/admin/apis.SysRole.Get-fm', '角色通过id获取', '/api/v1/role/:id', 'BUS', 'GET', '2021-05-13 19:59:02.850', '2021-06-17 11:48:40.732', NULL, 0, 0), +(48, 'github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth.(*GinJWTMiddleware).RefreshHandler-fm', '刷新token', '/api/v1/refresh_token', 'SYS', 'GET', '2021-05-13 19:59:02.892', '2021-06-13 20:53:49.278', NULL, 0, 0), +(53, 'go-admin/app/admin/apis.SysConfig.GetPage-fm', '参数列表', '/api/v1/config', 'BUS', 'GET', '2021-05-13 19:59:03.116', '2021-06-17 11:48:40.732', NULL, 0, 0), +(54, 'go-admin/app/admin/apis.SysConfig.Get-fm', '参数通过id获取', '/api/v1/config/:id', 'BUS', 'GET', '2021-05-13 19:59:03.157', '2021-06-17 11:48:40.732', NULL, 0, 0), +(55, 'go-admin/app/admin/apis.SysConfig.GetSysConfigByKEYForService-fm', '参数通过键名搜索【基础默认配置】', '/api/v1/configKey/:configKey', 'SYS', 'GET', '2021-05-13 19:59:03.198', '2021-06-13 20:53:49.745', NULL, 0, 0), +(57, 'go-admin/app/jobs/apis.SysJob.RemoveJobForService-fm', 'job移除', '/api/v1/job/remove/:id', 'BUS', 'GET', '2021-05-13 19:59:03.295', '2021-06-13 20:53:49.786', NULL, 0, 0), +(58, 'go-admin/app/jobs/apis.SysJob.StartJobForService-fm', 'job启动', '/api/v1/job/start/:id', 'BUS', 'GET', '2021-05-13 19:59:03.339', '2021-06-13 20:53:49.829', NULL, 0, 0), +(59, 'go-admin/app/admin/apis.SysPost.GetPage-fm', '岗位列表', '/api/v1/post', 'BUS', 'GET', '2021-05-13 19:59:03.381', '2021-06-17 11:48:40.732', NULL, 0, 0), +(60, 'go-admin/app/admin/apis.SysPost.Get-fm', '岗位通过id获取', '/api/v1/post/:id', 'BUS', 'GET', '2021-05-13 19:59:03.433', '2021-06-17 11:48:40.732', NULL, 0, 0), +(62, 'go-admin/app/admin/apis.SysConfig.GetSysConfigBySysApp-fm', '系统前端参数', '/api/v1/app-config', 'SYS', 'GET', '2021-05-13 19:59:03.526', '2021-06-13 20:53:49.994', NULL, 0, 0), +(63, 'go-admin/app/admin/apis.SysUser.GetProfile-fm', '*用户信息获取', '/api/v1/user/profile', 'SYS', 'GET', '2021-05-13 19:59:03.567', '2021-06-13 20:53:50.038', NULL, 0, 0), +(66, 'github.com/go-admin-team/go-admin-core/sdk/pkg/ws.(*Manager).WsClient-fm', '链接ws【定时任务log】', '/ws/:id/:channel', 'BUS', 'GET', '2021-05-13 19:59:03.705', '2021-06-13 20:53:50.167', NULL, 0, 0), +(67, 'github.com/go-admin-team/go-admin-core/sdk/pkg/ws.(*Manager).UnWsClient-fm', '退出ws【定时任务log】', '/wslogout/:id/:channel', 'BUS', 'GET', '2021-05-13 19:59:03.756', '2021-06-13 20:53:50.209', NULL, 0, 0), +(68, 'go-admin/common/middleware/handler.Ping', '*用户基本信息', '/info', 'SYS', 'GET', '2021-05-13 19:59:03.800', '2021-06-13 20:53:50.251', NULL, 0, 0), +(72, 'go-admin/common/actions.CreateAction.func1', '分类创建', '/api/v1/syscategory', 'BUS', 'POST', '2021-05-13 19:59:03.982', '2021-06-13 20:53:50.336', NULL, 0, 0), +(73, 'go-admin/common/actions.CreateAction.func1', '内容创建', '/api/v1/syscontent', 'BUS', 'POST', '2021-05-13 19:59:04.027', '2021-06-13 20:53:50.375', NULL, 0, 0), +(76, 'go-admin/common/actions.CreateAction.func1', 'job创建', '/api/v1/sysjob', 'BUS', 'POST', '2021-05-13 19:59:04.164', '2021-06-13 20:53:50.500', NULL, 0, 0), +(80, 'go-admin/app/admin/apis.SysDictData.Insert-fm', '字典数据创建', '/api/v1/dict/data', 'BUS', 'POST', '2021-05-13 19:59:04.347', '2021-06-17 11:48:40.732', NULL, 0, 0), +(81, 'go-admin/app/admin/apis.SysDictType.Insert-fm', '字典类型创建', '/api/v1/dict/type', 'BUS', 'POST', '2021-05-13 19:59:04.391', '2021-06-17 11:48:40.732', NULL, 0, 0), +(82, 'go-admin/app/admin/apis.SysDept.Insert-fm', '部门创建', '/api/v1/dept', 'BUS', 'POST', '2021-05-13 19:59:04.435', '2021-06-17 11:48:40.732', NULL, 0, 0), +(85, 'github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth.(*GinJWTMiddleware).LoginHandler-fm', '*登录', '/api/v1/login', 'SYS', 'POST', '2021-05-13 19:59:04.597', '2021-06-13 20:53:50.784', NULL, 0, 0), +(86, 'go-admin/common/middleware/handler.LogOut', '*退出', '/api/v1/logout', 'SYS', 'POST', '2021-05-13 19:59:04.642', '2021-06-13 20:53:50.824', NULL, 0, 0), +(87, 'go-admin/app/admin/apis.SysConfig.Insert-fm', '参数创建', '/api/v1/config', 'BUS', 'POST', '2021-05-13 19:59:04.685', '2021-06-17 11:48:40.732', NULL, 0, 0), +(88, 'go-admin/app/admin/apis.SysMenu.Insert-fm', '菜单创建', '/api/v1/menu', 'BUS', 'POST', '2021-05-13 19:59:04.777', '2021-06-17 11:48:40.732', NULL, 0, 0), +(89, 'go-admin/app/admin/apis.SysPost.Insert-fm', '岗位创建', '/api/v1/post', 'BUS', 'POST', '2021-05-13 19:59:04.886', '2021-06-17 11:48:40.732', NULL, 0, 0), +(90, 'go-admin/app/admin/apis.SysRole.Insert-fm', '角色创建', '/api/v1/role', 'BUS', 'POST', '2021-05-13 19:59:04.975', '2021-06-17 11:48:40.732', NULL, 0, 0), +(91, 'go-admin/app/admin/apis.SysUser.InsetAvatar-fm', '*用户头像编辑', '/api/v1/user/avatar', 'SYS', 'POST', '2021-05-13 19:59:05.058', '2021-06-13 20:53:51.079', NULL, 0, 0), +(92, 'go-admin/app/admin/apis.SysApi.Update-fm', '接口编辑', '/api/v1/sys-api/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.122', '2021-06-17 11:48:40.732', NULL, 0, 0), +(95, 'go-admin/common/actions.UpdateAction.func1', '分类编辑', '/api/v1/syscategory/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.255', '2021-06-13 20:53:51.247', NULL, 0, 0), +(96, 'go-admin/common/actions.UpdateAction.func1', '内容编辑', '/api/v1/syscontent/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.299', '2021-06-13 20:53:51.289', NULL, 0, 0), +(97, 'go-admin/common/actions.UpdateAction.func1', 'job编辑', '/api/v1/sysjob', 'BUS', 'PUT', '2021-05-13 19:59:05.343', '2021-06-13 20:53:51.331', NULL, 0, 0), +(101, 'go-admin/app/admin/apis.SysDictData.Update-fm', '字典数据编辑', '/api/v1/dict/data/:dictCode', 'BUS', 'PUT', '2021-05-13 19:59:05.519', '2021-06-17 11:48:40.732', NULL, 0, 0), +(102, 'go-admin/app/admin/apis.SysDictType.Update-fm', '字典类型编辑', '/api/v1/dict/type/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.569', '2021-06-17 11:48:40.732', NULL, 0, 0), +(103, 'go-admin/app/admin/apis.SysDept.Update-fm', '部门编辑', '/api/v1/dept/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.613', '2021-06-17 11:48:40.732', NULL, 0, 0), +(104, 'go-admin/app/other/apis.SysFileDir.Update-fm', '文件夹编辑', '/api/v1/file-dir/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.662', '2021-06-13 20:53:51.847', NULL, 0, 0), +(105, 'go-admin/app/other/apis.SysFileInfo.Update-fm', '文件编辑', '/api/v1/file-info/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.709', '2021-06-13 20:53:51.892', NULL, 0, 0), +(106, 'go-admin/app/admin/apis.SysRole.Update-fm', '角色编辑', '/api/v1/role/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.752', '2021-06-17 11:48:40.732', NULL, 0, 0), +(107, 'go-admin/app/admin/apis.SysRole.Update2DataScope-fm', '角色数据权限修改', '/api/v1/roledatascope', 'BUS', 'PUT', '2021-05-13 19:59:05.803', '2021-06-17 11:48:40.732', NULL, 0, 0), +(108, 'go-admin/app/admin/apis.SysConfig.Update-fm', '参数编辑', '/api/v1/config/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.848', '2021-06-17 11:48:40.732', NULL, 0, 0), +(109, 'go-admin/app/admin/apis.SysMenu.Update-fm', '编辑菜单', '/api/v1/menu/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.891', '2021-06-17 11:48:40.732', NULL, 0, 0), +(110, 'go-admin/app/admin/apis.SysPost.Update-fm', '岗位编辑', '/api/v1/post/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.934', '2021-06-17 11:48:40.732', NULL, 0, 0), +(111, 'go-admin/app/admin/apis.SysUser.UpdatePwd-fm', '*用户修改密码', '/api/v1/user/pwd', 'SYS', 'PUT', '2021-05-13 19:59:05.987', '2021-06-13 20:53:51.724', NULL, 0, 0), +(112, 'go-admin/common/actions.DeleteAction.func1', '分类删除', '/api/v1/syscategory', 'BUS', 'DELETE', '2021-05-13 19:59:06.030', '2021-06-13 20:53:52.237', NULL, 0, 0), +(113, 'go-admin/common/actions.DeleteAction.func1', '内容删除', '/api/v1/syscontent', 'BUS', 'DELETE', '2021-05-13 19:59:06.076', '2021-06-13 20:53:52.278', NULL, 0, 0), +(114, 'go-admin/app/admin/apis.SysLoginLog.Delete-fm', '登录日志删除', '/api/v1/sys-login-log', 'BUS', 'DELETE', '2021-05-13 19:59:06.118', '2021-06-17 11:48:40.732', NULL, 0, 0), +(115, 'go-admin/app/admin/apis.SysOperaLog.Delete-fm', '操作日志删除', '/api/v1/sys-opera-log', 'BUS', 'DELETE', '2021-05-13 19:59:06.162', '2021-06-17 11:48:40.732', NULL, 0, 0), +(116, 'go-admin/common/actions.DeleteAction.func1', 'job删除', '/api/v1/sysjob', 'BUS', 'DELETE', '2021-05-13 19:59:06.206', '2021-06-13 20:53:52.323', NULL, 0, 0), +(117, 'go-admin/app/other/apis.SysChinaAreaData.Delete-fm', '行政区删除', '/api/v1/sys-area-data', 'BUS', 'DELETE', '2021-05-13 19:59:06.249', '2021-06-13 20:53:52.061', NULL, 0, 0), +(120, 'go-admin/app/admin/apis.SysDictData.Delete-fm', '字典数据删除', '/api/v1/dict/data', 'BUS', 'DELETE', '2021-05-13 19:59:06.387', '2021-06-17 11:48:40.732', NULL, 0, 0), +(121, 'go-admin/app/admin/apis.SysDictType.Delete-fm', '字典类型删除', '/api/v1/dict/type', 'BUS', 'DELETE', '2021-05-13 19:59:06.432', '2021-06-17 11:48:40.732', NULL, 0, 0), +(122, 'go-admin/app/admin/apis.SysDept.Delete-fm', '部门删除', '/api/v1/dept/:id', 'BUS', 'DELETE', '2021-05-13 19:59:06.475', '2021-06-17 11:48:40.732', NULL, 0, 0), +(123, 'go-admin/app/other/apis.SysFileDir.Delete-fm', '文件夹删除', '/api/v1/file-dir/:id', 'BUS', 'DELETE', '2021-05-13 19:59:06.520', '2021-06-13 20:53:52.539', NULL, 0, 0), +(124, 'go-admin/app/other/apis.SysFileInfo.Delete-fm', '文件删除', '/api/v1/file-info/:id', 'BUS', 'DELETE', '2021-05-13 19:59:06.566', '2021-06-13 20:53:52.580', NULL, 0, 0), +(125, 'go-admin/app/admin/apis.SysConfig.Delete-fm', '参数删除', '/api/v1/config', 'BUS', 'DELETE', '2021-05-13 19:59:06.612', '2021-06-17 11:48:40.732', NULL, 0, 0), +(126, 'go-admin/app/admin/apis.SysMenu.Delete-fm', '删除菜单', '/api/v1/menu', 'BUS', 'DELETE', '2021-05-13 19:59:06.654', '2021-06-17 11:48:40.732', NULL, 0, 0), +(127, 'go-admin/app/admin/apis.SysPost.Delete-fm', '岗位删除', '/api/v1/post/:id', 'BUS', 'DELETE', '2021-05-13 19:59:06.702', '2021-06-17 11:48:40.732', NULL, 0, 0), +(128, 'go-admin/app/admin/apis.SysRole.Delete-fm', '角色删除', '/api/v1/role', 'BUS', 'DELETE', '2021-05-13 19:59:06.746', '2021-06-17 11:48:40.732', NULL, 0, 0), +(131, 'github.com/go-admin-team/go-admin-core/tools/transfer.Handler.func1', '系统指标', '/api/v1/metrics', 'SYS', 'GET', '2021-05-17 22:13:55.933', '2021-06-13 20:53:49.614', NULL, 0, 0), +(132, 'go-admin/app/other/router.registerMonitorRouter.func1', '健康状态', '/api/v1/health', 'SYS', 'GET', '2021-05-17 22:13:56.285', '2021-06-13 20:53:49.951', NULL, 0, 0), +(133, 'go-admin/app/admin/apis.HelloWorld', '项目默认接口', '/', 'SYS', 'GET', '2021-05-24 10:30:44.553', '2021-06-13 20:53:47.406', NULL, 0, 0), +(134, 'go-admin/app/other/apis.ServerMonitor.ServerInfo-fm', '服务器基本状态', '/api/v1/server-monitor', 'SYS', 'GET', '2021-05-24 10:30:44.937', '2021-06-13 20:53:48.255', NULL, 0, 0), +(135, 'go-admin/app/admin/apis.SysApi.GetPage-fm', '接口列表', '/api/v1/sys-api', 'BUS', 'GET', '2021-05-24 11:37:53.303', '2021-06-17 11:48:40.732', NULL, 0, 0), +(136, 'go-admin/app/admin/apis.SysApi.Get-fm', '接口通过id获取', '/api/v1/sys-api/:id', 'BUS', 'GET', '2021-05-24 11:37:53.359', '2021-06-17 11:48:40.732', NULL, 0, 0), +(137, 'go-admin/app/admin/apis.SysLoginLog.GetPage-fm', '登录日志列表', '/api/v1/sys-login-log', 'BUS', 'GET', '2021-05-24 11:47:30.397', '2021-06-17 11:48:40.732', NULL, 0, 0), +(138, 'go-admin/app/other/apis.File.UploadFile-fm', '文件上传', '/api/v1/public/uploadFile', 'SYS', 'POST', '2021-05-25 19:16:18.493', '2021-06-13 20:53:50.866', NULL, 0, 0), +(139, 'go-admin/app/admin/apis.SysConfig.Update2Set-fm', '参数信息修改【参数配置】', '/api/v1/set-config', 'BUS', 'PUT', '2021-05-27 09:45:14.853', '2021-06-17 11:48:40.732', NULL, 0, 0), +(140, 'go-admin/app/admin/apis.SysConfig.Get2Set-fm', '参数获取全部【配置使用】', '/api/v1/set-config', 'BUS', 'GET', '2021-05-27 11:54:14.384', '2021-06-17 11:48:40.732', NULL, 0, 0), +(141, 'go-admin/app/admin/apis.SysUser.GetPage-fm', '管理员列表', '/api/v1/sys-user', 'BUS', 'GET', '2021-06-13 19:24:57.111', '2021-06-17 20:31:14.318', NULL, 0, 0), +(142, 'go-admin/app/admin/apis.SysUser.Get-fm', '管理员通过id获取', '/api/v1/sys-user/:id', 'BUS', 'GET', '2021-06-13 19:24:57.188', '2021-06-17 20:31:14.318', NULL, 0, 0), +(143, 'go-admin/app/admin/apis/tools.(*SysTable).GetSysTablesInfo-fm', '', '/api/v1/sys/tables/info', '', 'GET', '2021-06-13 19:24:57.437', '2021-06-13 20:53:48.047', NULL, 0, 0), +(144, 'go-admin/app/admin/apis/tools.(*SysTable).GetSysTables-fm', '', '/api/v1/sys/tables/info/:tableId', '', 'GET', '2021-06-13 19:24:57.510', '2021-06-13 20:53:48.088', NULL, 0, 0), +(145, 'go-admin/app/admin/apis/tools.(*SysTable).GetSysTableList-fm', '', '/api/v1/sys/tables/page', '', 'GET', '2021-06-13 19:24:57.582', '2021-06-13 20:53:48.128', NULL, 0, 0), +(146, 'github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1', '', '/static/*filepath', '', 'GET', '2021-06-13 19:24:59.641', '2021-06-13 20:53:50.081', NULL, 0, 0), +(147, 'github.com/swaggo/gin-swagger.CustomWrapHandler.func1', '', '/swagger/*any', '', 'GET', '2021-06-13 19:24:59.713', '2021-06-13 20:53:50.123', NULL, 0, 0), +(148, 'github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1', '', '/form-generator/*filepath', '', 'GET', '2021-06-13 19:24:59.914', '2021-06-13 20:53:50.295', NULL, 0, 0), +(149, 'go-admin/app/admin/apis/tools.(*SysTable).InsertSysTable-fm', '', '/api/v1/sys/tables/info', '', 'POST', '2021-06-13 19:25:00.163', '2021-06-13 20:53:50.539', NULL, 0, 0), +(150, 'go-admin/app/admin/apis.SysUser.Insert-fm', '管理员创建', '/api/v1/sys-user', 'BUS', 'POST', '2021-06-13 19:25:00.233', '2021-06-17 20:31:14.318', NULL, 0, 0), +(151, 'go-admin/app/admin/apis.SysUser.Update-fm', '管理员编辑', '/api/v1/sys-user', 'BUS', 'PUT', '2021-06-13 19:25:00.986', '2021-06-17 20:31:14.318', NULL, 0, 0), +(152, 'go-admin/app/admin/apis/tools.(*SysTable).UpdateSysTable-fm', '', '/api/v1/sys/tables/info', '', 'PUT', '2021-06-13 19:25:01.149', '2021-06-13 20:53:51.375', NULL, 0, 0), +(153, 'go-admin/app/admin/apis.SysRole.Update2Status-fm', '', '/api/v1/role-status', '', 'PUT', '2021-06-13 19:25:01.446', '2021-06-13 20:53:51.636', NULL, 0, 0), +(154, 'go-admin/app/admin/apis.SysUser.ResetPwd-fm', '', '/api/v1/user/pwd/reset', '', 'PUT', '2021-06-13 19:25:01.601', '2021-06-13 20:53:51.764', NULL, 0, 0), +(155, 'go-admin/app/admin/apis.SysUser.UpdateStatus-fm', '', '/api/v1/user/status', '', 'PUT', '2021-06-13 19:25:01.671', '2021-06-13 20:53:51.806', NULL, 0, 0), +(156, 'go-admin/app/admin/apis.SysUser.Delete-fm', '管理员删除', '/api/v1/sys-user', 'BUS', 'DELETE', '2021-06-13 19:25:02.043', '2021-06-17 20:31:14.318', NULL, 0, 0), +(157, 'go-admin/app/admin/apis/tools.(*SysTable).DeleteSysTables-fm', '', '/api/v1/sys/tables/info/:tableId', '', 'DELETE', '2021-06-13 19:25:02.283', '2021-06-13 20:53:52.367', NULL, 0, 0), +(158, 'github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1', '', '/static/*filepath', '', 'HEAD', '2021-06-13 19:25:02.734', '2021-06-13 20:53:52.791', NULL, 0, 0), +(159, 'github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1', '', '/form-generator/*filepath', '', 'HEAD', '2021-06-13 19:25:02.808', '2021-06-13 20:53:52.838', NULL, 0, 0); +SET IDENTITY_INSERT sys_api OFF; + +INSERT INTO sys_config VALUES +(1, '皮肤样式', 'sys_index_skinName', 'skin-green', 'Y', '0', '主框架页-默认皮肤样式名称:蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow', 1, 1, '2021-05-13 19:56:37.913', '2021-06-05 13:50:13.123', NULL), +(2, '初始密码', 'sys_user_initPassword', '123456', 'Y', '0', '用户管理-账号初始密码:123456', 1, 1, '2021-05-13 19:56:37.913', '2021-05-13 19:56:37.913', NULL), +(3, '侧栏主题', 'sys_index_sideTheme', 'theme-dark', 'Y', '0', '主框架页-侧边栏主题:深色主题theme-dark,浅色主题theme-light', 1, 1, '2021-05-13 19:56:37.913', '2021-05-13 19:56:37.913', NULL), +(4, '系统名称', 'sys_app_name', 'go-admin管理系统', 'Y', '1', '', 1, 0, '2021-03-17 08:52:06.067', '2021-05-28 10:08:25.248', NULL), +(5, '系统logo', 'sys_app_logo', 'https://gitee.com/mydearzwj/image/raw/master/img/go-admin.png', 'Y', '1', '', 1, 0, '2021-03-17 08:53:19.462', '2021-03-17 08:53:19.462', NULL); + +SET IDENTITY_INSERT sys_dept ON; +INSERT INTO sys_dept (dept_id, parent_id, dept_path, dept_name, sort, leader, phone, email, status, create_by, update_by, created_at, updated_at, deleted_at)VALUES +(1, 0, '/0/1/', '爱拓科技', 0, 'aituo', '13782218188', 'atuo@aituo.com', '2', 1, 1, '2021-05-13 19:56:37.913', '2021-06-05 17:06:44.960', NULL), +(7, 1, '/0/1/7/', '研发部', 1, 'aituo', '13782218188', 'atuo@aituo.com', '2', 1, 1, '2021-05-13 19:56:37.913', '2021-06-16 21:35:00.109', NULL), +(8, 1, '/0/1/8/', '运维部', 0, 'aituo', '13782218188', 'atuo@aituo.com', '2', 1, 1, '2021-05-13 19:56:37.913', '2021-06-16 21:41:39.747', NULL), +(9, 1, '/0/1/9/', '客服部', 0, 'aituo', '13782218188', 'atuo@aituo.com', '2', 1, 1, '2021-05-13 19:56:37.913', '2021-06-05 17:07:05.993', NULL), +(10, 1, '/0/1/10/', '人力资源', 3, 'aituo', '13782218188', 'atuo@aituo.com', '1', 1, 1, '2021-05-13 19:56:37.913', '2021-06-05 17:07:08.503', NULL); +SET IDENTITY_INSERT sys_dept OFF; + +INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, + status, "default", remark, create_by, update_by, created_at, updated_at, deleted_at)VALUES +(1, 0, '正常', '2', 'sys_normal_disable', '', '', '', '2', '', '系统正常', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:40.168', NULL); +INSERT INTO sys_dict_data VALUES (2, 0, '停用', '1', 'sys_normal_disable', '', '', '', '2', '', '系统停用', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (3, 0, '男', '0', 'sys_user_sex', '', '', '', '2', '', '性别男', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (4, 0, '女', '1', 'sys_user_sex', '', '', '', '2', '', '性别女', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (5, 0, '未知', '2', 'sys_user_sex', '', '', '', '2', '', '性别未知', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (6, 0, '显示', '0', 'sys_show_hide', '', '', '', '2', '', '显示菜单', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (7, 0, '隐藏', '1', 'sys_show_hide', '', '', '', '2', '', '隐藏菜单', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (8, 0, '是', 'Y', 'sys_yes_no', '', '', '', '2', '', '系统默认是', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (9, 0, '否', 'N', 'sys_yes_no', '', '', '', '2', '', '系统默认否', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (10, 0, '正常', '2', 'sys_job_status', '', '', '', '2', '', '正常状态', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (11, 0, '停用', '1', 'sys_job_status', '', '', '', '2', '', '停用状态', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (12, 0, '默认', 'DEFAULT', 'sys_job_group', '', '', '', '2', '', '默认分组', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (13, 0, '系统', 'SYSTEM', 'sys_job_group', '', '', '', '2', '', '系统分组', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (14, 0, '通知', '1', 'sys_notice_type', '', '', '', '2', '', '通知', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (15, 0, '公告', '2', 'sys_notice_type', '', '', '', '2', '', '公告', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (16, 0, '正常', '2', 'sys_common_status', '', '', '', '2', '', '正常状态', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (17, 0, '关闭', '1', 'sys_common_status', '', '', '', '2', '', '关闭状态', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (18, 0, '新增', '1', 'sys_oper_type', '', '', '', '2', '', '新增操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (19, 0, '修改', '2', 'sys_oper_type', '', '', '', '2', '', '修改操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (20, 0, '删除', '3', 'sys_oper_type', '', '', '', '2', '', '删除操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (21, 0, '授权', '4', 'sys_oper_type', '', '', '', '2', '', '授权操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (22, 0, '导出', '5', 'sys_oper_type', '', '', '', '2', '', '导出操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (23, 0, '导入', '6', 'sys_oper_type', '', '', '', '2', '', '导入操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (24, 0, '强退', '7', 'sys_oper_type', '', '', '', '2', '', '强退操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (25, 0, '生成代码', '8', 'sys_oper_type', '', '', '', '2', '', '生成操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (26, 0, '清空数据', '9', 'sys_oper_type', '', '', '', '2', '', '清空操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (27, 0, '成功', '0', 'sys_notice_status', '', '', '', '2', '', '成功状态', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (28, 0, '失败', '1', 'sys_notice_status', '', '', '', '2', '', '失败状态', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (29, 0, '登录', '10', 'sys_oper_type', '', '', '', '2', '', '登录操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (30, 0, '退出', '11', 'sys_oper_type', '', '', '', '2', '', '', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (31, 0, '获取验证码', '12', 'sys_oper_type', '', '', '', '2', '', '获取验证码', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (32, 0, '正常', '1', 'sys_content_status', '', '', '', '1', '', '', 1, 1, '2021-05-13 19:56:40.845', '2021-05-13 19:56:40.845', NULL); +INSERT INTO sys_dict_data VALUES (33, 1, '禁用', '2', 'sys_content_status', '', '', '', '1', '', '', 1, 1, '2021-05-13 19:56:40.845', '2021-05-13 19:56:40.845', NULL); + + +INSERT INTO sys_dict_type (dict_id, dict_name, dict_type, status, remark, create_by, update_by, created_at, updated_at, + deleted_at)VALUES (1, '系统开关', 'sys_normal_disable', '2', '系统开关列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (2, '用户性别', 'sys_user_sex', '2', '用户性别列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (3, '菜单状态', 'sys_show_hide', '2', '菜单状态列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (4, '系统是否', 'sys_yes_no', '2', '系统是否列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (5, '任务状态', 'sys_job_status', '2', '任务状态列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (6, '任务分组', 'sys_job_group', '2', '任务分组列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (7, '通知类型', 'sys_notice_type', '2', '通知类型列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (8, '系统状态', 'sys_common_status', '2', '登录状态列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (9, '操作类型', 'sys_oper_type', '2', '操作类型列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (10, '通知状态', 'sys_notice_status', '2', '通知状态列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (11, '内容状态', 'sys_content_status', '2', '', 1, 1, '2021-05-13 19:56:40.813', '2021-05-13 19:56:40.813', NULL); + + +INSERT INTO sys_job (job_id, job_name, job_group, job_type, cron_expression, invoke_target, args, misfire_policy, concurrent, status, entry_id, created_at, updated_at, deleted_at, create_by, update_by)VALUES +(1, '接口测试', 'DEFAULT', 1, '0/5 * * * * ', 'http://localhost:8000', '', 1, 1, 1, 0, '2021-05-13 19:56:37.914', '2021-06-14 20:59:55.417', NULL, 1, 1), +(2, '函数测试', 'DEFAULT', 2, '0/5 * * * * ', 'ExamplesOne', '参数', 1, 1, 1, 0, '2021-05-13 19:56:37.914', '2021-05-31 23:55:37.221', NULL, 1, 1); + +SET IDENTITY_INSERT sys_menu ON; +INSERT INTO sys_menu (menu_id, menu_name, title, icon, path, paths, menu_type, "action", permission, parent_id, no_cache, breadcrumb, component, sort, visible, is_frame, create_by, update_by, created_at, updated_at, deleted_at)VALUES +(2, 'Admin', '系统管理', 'api-server', '/admin', '/0/2', 'M', '无', '', 0, 1, '', 'Layout', 10, '0', '1', 0, 1, '2021-05-20 21:58:45.679', '2021-06-17 11:48:40.703', NULL), +(3, 'SysUserManage', '用户管理', 'user', '/admin/sys-user', '/0/2/3', 'C', '无', 'admin:sysUser:list', 2, 0, '', '/admin/sys-user/index', 10, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL), +(43, '', '新增管理员', 'app-group-fill', '', '/0/2/3/43', 'F', 'POST', 'admin:sysUser:add', 3, 0, '', '', 10, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL), +(44, '', '查询管理员', 'app-group-fill', '', '/0/2/3/44', 'F', 'GET', 'admin:sysUser:query', 3, 0, '', '', 40, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL), +(45, '', '修改管理员', 'app-group-fill', '', '/0/2/3/45', 'F', 'PUT', 'admin:sysUser:edit', 3, 0, '', '', 30, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL), +(46, '', '删除管理员', 'app-group-fill', '', '/0/2/3/46', 'F', 'DELETE', 'admin:sysUser:remove', 3, 0, '', '', 20, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL), +(51, 'SysMenuManage', '菜单管理', 'tree-table', '/admin/sys-menu', '/0/2/51', 'C', '无', 'admin:sysMenu:list', 2, 1, '', '/admin/sys-menu/index', 30, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL), +(52, 'SysRoleManage', '角色管理', 'peoples', '/admin/sys-role', '/0/2/52', 'C', '无', 'admin:sysRole:list', 2, 1, '', '/admin/sys-role/index', 20, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL), +(56, 'SysDeptManage', '部门管理', 'tree', '/admin/sys-dept', '/0/2/56', 'C', '无', 'admin:sysDept:list', 2, 0, '', '/admin/sys-dept/index', 40, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL), +(57, 'SysPostManage', '岗位管理', 'pass', '/admin/sys-post', '/0/2/57', 'C', '无', 'admin:sysPost:list', 2, 0, '', '/admin/sys-post/index', 50, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL), +(58, 'Dict', '字典管理', 'education', '/admin/dict', '/0/2/58', 'C', '无', 'admin:sysDictType:list', 2, 0, '', '/admin/dict/index', 60, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL), +(59, 'SysDictDataManage', '字典数据', 'education', '/admin/dict/data/:dictId', '/0/2/59', 'C', '无', 'admin:sysDictData:list', 2, 0, '', '/admin/dict/data', 100, '1', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL), +(60, 'Tools', '开发工具', 'dev-tools', '/dev-tools', '/0/60', 'M', '无', '', 0, 0, '', 'Layout', 40, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-05 22:15:03.465', NULL), +(61, 'Swagger', '系统接口', 'guide', '/dev-tools/swagger', '/0/60/61', 'C', '无', '', 60, 0, '', '/dev-tools/swagger/index', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-05 22:15:03.465', NULL), +(62, 'SysConfigManage', '参数管理', 'swagger', '/admin/sys-config', '/0/2/62', 'C', '无', 'admin:sysConfig:list', 2, 0, '', '/admin/sys-config/index', 70, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL), +(211, 'Log', '日志管理', 'log', '/log', '/0/2/211', 'M', '', '', 2, 0, '', '/log/index', 80, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL), +(212, 'SysLoginLogManage', '登录日志', 'logininfor', '/admin/sys-login-log', '/0/2/211/212', 'C', '', 'admin:sysLoginLog:list', 211, 0, '', '/admin/sys-login-log/index', 1, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL), +(216, 'OperLog', '操作日志', 'skill', '/admin/sys-oper-log', '/0/2/211/216', 'C', '', 'admin:sysOperLog:list', 211, 0, '', '/admin/sys-oper-log/index', 1, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL), +(220, '', '新增菜单', 'app-group-fill', '', '/0/2/51/220', 'F', '', 'admin:sysMenu:add', 51, 0, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(221, '', '修改菜单', 'app-group-fill', '', '/0/2/51/221', 'F', '', 'admin:sysMenu:edit', 51, 0, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(222, '', '查询菜单', 'app-group-fill', '', '/0/2/51/222', 'F', '', 'admin:sysMenu:query', 51, 0, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(223, '', '删除菜单', 'app-group-fill', '', '/0/2/51/223', 'F', '', 'admin:sysMenu:remove', 51, 0, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(224, '', '新增角色', 'app-group-fill', '', '/0/2/52/224', 'F', '', 'admin:sysRole:add', 52, 0, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(225, '', '查询角色', 'app-group-fill', '', '/0/2/52/225', 'F', '', 'admin:sysRole:query', 52, 0, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(226, '', '修改角色', 'app-group-fill', '', '/0/2/52/226', 'F', '', 'admin:sysRole:update', 52, 0, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(227, '', '删除角色', 'app-group-fill', '', '/0/2/52/227', 'F', '', 'admin:sysRole:remove', 52, 0, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(228, '', '查询部门', 'app-group-fill', '', '/0/2/56/228', 'F', '', 'admin:sysDept:query', 56, 0, '', '', 40, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL), +(229, '', '新增部门', 'app-group-fill', '', '/0/2/56/229', 'F', '', 'admin:sysDept:add', 56, 0, '', '', 10, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL), +(230, '', '修改部门', 'app-group-fill', '', '/0/2/56/230', 'F', '', 'admin:sysDept:edit', 56, 0, '', '', 30, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL), +(231, '', '删除部门', 'app-group-fill', '', '/0/2/56/231', 'F', '', 'admin:sysDept:remove', 56, 0, '', '', 20, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL), +(232, '', '查询岗位', 'app-group-fill', '', '/0/2/57/232', 'F', '', 'admin:sysPost:query', 57, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(233, '', '新增岗位', 'app-group-fill', '', '/0/2/57/233', 'F', '', 'admin:sysPost:add', 57, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(234, '', '修改岗位', 'app-group-fill', '', '/0/2/57/234', 'F', '', 'admin:sysPost:edit', 57, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(235, '', '删除岗位', 'app-group-fill', '', '/0/2/57/235', 'F', '', 'admin:sysPost:remove', 57, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(236, '', '查询字典', 'app-group-fill', '', '/0/2/58/236', 'F', '', 'admin:sysDictType:query', 58, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(237, '', '新增类型', 'app-group-fill', '', '/0/2/58/237', 'F', '', 'admin:sysDictType:add', 58, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(238, '', '修改类型', 'app-group-fill', '', '/0/2/58/238', 'F', '', 'admin:sysDictType:edit', 58, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(239, '', '删除类型', 'app-group-fill', '', '/0/2/58/239', 'F', '', 'system:sysdicttype:remove', 58, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(240, '', '查询数据', 'app-group-fill', '', '/0/2/59/240', 'F', '', 'admin:sysDictData:query', 59, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(241, '', '新增数据', 'app-group-fill', '', '/0/2/59/241', 'F', '', 'admin:sysDictData:add', 59, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(242, '', '修改数据', 'app-group-fill', '', '/0/2/59/242', 'F', '', 'admin:sysDictData:edit', 59, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(243, '', '删除数据', 'app-group-fill', '', '/0/2/59/243', 'F', '', 'admin:sysDictData:remove', 59, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(244, '', '查询参数', 'app-group-fill', '', '/0/2/62/244', 'F', '', 'admin:sysConfig:query', 62, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(245, '', '新增参数', 'app-group-fill', '', '/0/2/62/245', 'F', '', 'admin:sysConfig:add', 62, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(246, '', '修改参数', 'app-group-fill', '', '/0/2/62/246', 'F', '', 'admin:sysConfig:edit', 62, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(247, '', '删除参数', 'app-group-fill', '', '/0/2/62/247', 'F', '', 'admin:sysConfig:remove', 62, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(248, '', '查询登录日志', 'app-group-fill', '', '/0/2/211/212/248', 'F', '', 'admin:sysLoginLog:query', 212, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(249, '', '删除登录日志', 'app-group-fill', '', '/0/2/211/212/249', 'F', '', 'admin:sysLoginLog:remove', 212, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(250, '', '查询操作日志', 'app-group-fill', '', '/0/2/211/216/250', 'F', '', 'admin:sysOperLog:query', 216, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(251, '', '删除操作日志', 'app-group-fill', '', '/0/2/211/216/251', 'F', '', 'admin:sysOperLog:remove', 216, 0, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL), +(261, 'Gen', '代码生成', 'code', '/dev-tools/gen', '/0/60/261', 'C', '', '', 60, 0, '', '/dev-tools/gen/index', 2, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-16 21:26:12.446', NULL), +(262, 'EditTable', '代码生成修改', 'build', '/dev-tools/editTable', '/0/60/262', 'C', '', '', 60, 0, '', '/dev-tools/gen/editTable', 100, '1', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-16 21:26:12.446', NULL), +(264, 'Build', '表单构建', 'build', '/dev-tools/build', '/0/60/264', 'C', '', '', 60, 0, '', '/dev-tools/build/index', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-16 21:26:12.446', NULL), +(269, 'ServerMonitor', '服务监控', 'druid', '/sys-tools/monitor', '/0/60/269', 'C', '', 'sysTools:serverMonitor:list', 537, 0, '', '/sys-tools/monitor', 0, '0', '1', 1, 1, '2020-04-14 00:28:19.000', '2021-06-16 21:26:12.446', NULL), +(459, 'Schedule', '定时任务', 'time-range', '/schedule', '/0/459', 'M', '无', '', 0, 0, '', 'Layout', 20, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL), +(460, 'ScheduleManage', 'Schedule', 'job', '/schedule/manage', '/0/459/460', 'C', '无', 'job:sysJob:list', 459, 0, '', '/schedule/index', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL), +(461, 'sys_job', '分页获取定时任务', 'app-group-fill', '', '/0/459/460/461', 'F', '无', 'job:sysJob:query', 460, 0, '', '', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL), +(462, 'sys_job', '创建定时任务', 'app-group-fill', '', '/0/459/460/462', 'F', '无', 'job:sysJob:add', 460, 0, '', '', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL), +(463, 'sys_job', '修改定时任务', 'app-group-fill', '', '/0/459/460/463', 'F', '无', 'job:sysJob:edit', 460, 0, '', '', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL), +(464, 'sys_job', '删除定时任务', 'app-group-fill', '', '/0/459/460/464', 'F', '无', 'job:sysJob:remove', 460, 0, '', '', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL), +(471, 'JobLog', '日志', 'bug', '/schedule/log', '/0/459/471', 'C', '', '', 459, 0, '', '/schedule/log', 0, '1', '1', 1, 1, '2020-08-05 21:24:46.000', '2021-06-05 22:15:03.465', NULL), +(528, 'SysApiManage', '接口管理', 'api-doc', '/admin/sys-api', '/0/527/528', 'C', '无', 'admin:sysApi:list', 2, 0, '', '/admin/sys-api/index', 0, '0', '0', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL), +(529, '', '查询接口', 'app-group-fill', '', '/0/527/528/529', 'F', '无', 'admin:sysApi:query', 528, 0, '', '', 40, '0', '0', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL), +(531, '', '修改接口', 'app-group-fill', '', '/0/527/528/531', 'F', '无', 'admin:sysApi:edit', 528, 0, '', '', 30, '0', '0', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL), +(537, 'SysTools', '系统工具', 'system-tools', '/sys-tools', '', 'M', '', '', 0, 0, '', 'Layout', 30, '0', '1', 1, 1, '2021-05-21 11:13:32.166', '2021-06-16 21:26:12.446', NULL), +(540, 'SysConfigSet', '参数设置', 'system-tools', '/admin/sys-config/set', '', 'C', '', 'admin:sysConfigSet:list', 2, 0, '', '/admin/sys-config/set', 0, '0', '1', 1, 1, '2021-05-25 16:06:52.560', '2021-06-17 11:48:40.703', NULL), +(542, '', '修改', 'upload', '', '', 'F', '', 'admin:sysConfigSet:update', 540, 0, '', '', 0, '0', '1', 1, 1, '2021-06-13 11:45:48.670', '2021-06-17 11:48:40.703', NULL); +SET IDENTITY_INSERT sys_menu OFF; +INSERT INTO sys_menu_api_rule (sys_menu_menu_id, sys_api_id)VALUES (216, 6); +(250, 6), +(58, 21), +(236, 21), +(238, 23), +(59, 24), +(240, 24), +(242, 25), +(58, 26), +(236, 26), +(56, 27), +(228, 27), +(230, 28), +(226, 29), +(51, 39), +(51, 135), +(222, 39), +(221, 41), +(52, 44), +(225, 44), +(226, 45), +(226, 46), +(226, 47), +(62, 53), +(244, 53), +(246, 54), +(57, 59), +(232, 59), +(234, 60), +(241, 80), +(237, 81), +(229, 82), +(245, 87), +(220, 88), +(233, 89), +(224, 90), +(531, 92), +(242, 101), +(238, 102), +(230, 103), +(226, 106), +(226, 107), +(246, 108), +(221, 109), +(234, 110), +(249, 114), +(251, 115), +(243, 120), +(239, 121), +(231, 122), +(247, 125), +(223, 126), +(235, 127), +(227, 128), +(528, 135), +(529, 135), +(531, 136), +(212, 137), +(248, 137), +(542, 139), +(540, 140), +(3, 141), +(44, 141), +(45, 142), +(43, 150), +(45, 151), +(46, 156); +INSERT INTO sys_post (post_id, post_name, post_code, sort, status, remark, create_by, update_by, created_at, updated_at, deleted_at)VALUES +(1, '首席执行官', 'CEO', 0, '2','首席执行官', 1, 1, '2021-05-13 19:56:37.913', '2021-05-13 19:56:37.913', NULL), +(2, '首席技术执行官', 'CTO', 2, '2','首席技术执行官', 1, 1,'2021-05-13 19:56:37.913', '2021-05-13 19:56:37.913', NULL), +(3, '首席运营官', 'COO', 3, '2','测试工程师', 1, 1,'2021-05-13 19:56:37.913', '2021-05-13 19:56:37.913', NULL); +INSERT INTO sys_role (role_id, role_name, status, role_key, role_sort, flag, remark, admin, data_scope, create_by, update_by, created_at, updated_at, deleted_at)VALUES +(1, '系统管理员', '2', 'admin', 1, '', '', 1, '', 1, 1, '2021-05-13 19:56:37.913', '2021-05-13 19:56:37.913', NULL); +INSERT INTO sys_user VALUES (1, 'admin', '$2a$10$/Glr4g9Svr6O0kvjsRJCXu3f0W8/dsP3XZyVNi1019ratWpSPMyw.', 'zhangwj', '13818888888', 1, '', '', '1', '1@qq.com', 1, 1, '', '2', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:40.205', NULL); +-- 数据完成 ; \ No newline at end of file diff --git a/config/db.sql b/config/db.sql new file mode 100644 index 0000000..5c84adf --- /dev/null +++ b/config/db.sql @@ -0,0 +1,324 @@ +-- 开始初始化数据 ; +INSERT INTO sys_api VALUES (5, 'go-admin/app/admin/apis.SysLoginLog.Get-fm', '登录日志通过id获取', '/api/v1/sys-login-log/:id', 'BUS', 'GET', '2021-05-13 19:59:00.728', '2021-06-17 11:37:12.331', NULL, 0, 0); +INSERT INTO sys_api VALUES (6, 'go-admin/app/admin/apis.SysOperaLog.GetPage-fm', '操作日志列表', '/api/v1/sys-opera-log', 'BUS', 'GET', '2021-05-13 19:59:00.778', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (7, 'go-admin/app/admin/apis.SysOperaLog.Get-fm', '操作日志通过id获取', '/api/v1/sys-opera-log/:id', 'BUS', 'GET', '2021-05-13 19:59:00.821', '2021-06-16 21:49:48.598', NULL, 0, 0); +INSERT INTO sys_api VALUES (8, 'go-admin/common/actions.IndexAction.func1', '分类列表', '/api/v1/syscategory', 'BUS', 'GET', '2021-05-13 19:59:00.870', '2021-06-13 20:53:47.883', NULL, 0, 0); +INSERT INTO sys_api VALUES (9, 'go-admin/common/actions.ViewAction.func1', '分类通过id获取', '/api/v1/syscategory/:id', 'BUS', 'GET', '2021-05-13 19:59:00.945', '2021-06-13 20:53:47.926', NULL, 0, 0); +INSERT INTO sys_api VALUES (10, 'go-admin/common/actions.IndexAction.func1', '内容列表', '/api/v1/syscontent', 'BUS', 'GET', '2021-05-13 19:59:01.005', '2021-06-13 20:53:47.966', NULL, 0, 0); +INSERT INTO sys_api VALUES (11, 'go-admin/common/actions.ViewAction.func1', '内容通过id获取', '/api/v1/syscontent/:id', 'BUS', 'GET', '2021-05-13 19:59:01.056', '2021-06-13 20:53:48.005', NULL, 0, 0); +INSERT INTO sys_api VALUES (15, 'go-admin/common/actions.IndexAction.func1', 'job列表', '/api/v1/sysjob', 'BUS', 'GET', '2021-05-13 19:59:01.248', '2021-06-13 20:53:48.169', NULL, 0, 0); +INSERT INTO sys_api VALUES (16, 'go-admin/common/actions.ViewAction.func1', 'job通过id获取', '/api/v1/sysjob/:id', 'BUS', 'GET', '2021-05-13 19:59:01.298', '2021-06-13 20:53:48.214', NULL, 0, 0); +INSERT INTO sys_api VALUES (21, 'go-admin/app/admin/apis.SysDictType.GetPage-fm', '字典类型列表', '/api/v1/dict/type', 'BUS', 'GET', '2021-05-13 19:59:01.525', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (22, 'go-admin/app/admin/apis.SysDictType.GetAll-fm', '字典类型查询【代码生成】', '/api/v1/dict/type-option-select', 'SYS', 'GET', '2021-05-13 19:59:01.582', '2021-06-13 20:53:48.388', NULL, 0, 0); +INSERT INTO sys_api VALUES (23, 'go-admin/app/admin/apis.SysDictType.Get-fm', '字典类型通过id获取', '/api/v1/dict/type/:id', 'BUS', 'GET', '2021-05-13 19:59:01.632', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (24, 'go-admin/app/admin/apis.SysDictData.GetPage-fm', '字典数据列表', '/api/v1/dict/data', 'BUS', 'GET', '2021-05-13 19:59:01.684', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (25, 'go-admin/app/admin/apis.SysDictData.Get-fm', '字典数据通过code获取', '/api/v1/dict/data/:dictCode', 'BUS', 'GET', '2021-05-13 19:59:01.732', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (26, 'go-admin/app/admin/apis.SysDictData.GetSysDictDataAll-fm', '数据字典根据key获取', '/api/v1/dict-data/option-select', 'SYS', 'GET', '2021-05-13 19:59:01.832', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (27, 'go-admin/app/admin/apis.SysDept.GetPage-fm', '部门列表', '/api/v1/dept', 'BUS', 'GET', '2021-05-13 19:59:01.940', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (28, 'go-admin/app/admin/apis.SysDept.Get-fm', '部门通过id获取', '/api/v1/dept/:id', 'BUS', 'GET', '2021-05-13 19:59:02.009', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (29, 'go-admin/app/admin/apis.SysDept.Get2Tree-fm', '查询部门下拉树【角色权限-自定权限】', '/api/v1/deptTree', 'SYS', 'GET', '2021-05-13 19:59:02.050', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (30, 'go-admin/app/admin/apis/tools.(*Gen).GetDBTableList-fm', '数据库表列表', '/api/v1/db/tables/page', 'SYS', 'GET', '2021-05-13 19:59:02.098', '2021-06-13 20:53:48.730', NULL, 0, 0); +INSERT INTO sys_api VALUES (31, 'go-admin/app/admin/apis/tools.(*Gen).GetDBColumnList-fm', '数据表列列表', '/api/v1/db/columns/page', 'SYS', 'GET', '2021-05-13 19:59:02.140', '2021-06-13 20:53:48.771', NULL, 0, 0); +INSERT INTO sys_api VALUES (32, 'go-admin/app/admin/apis/tools.Gen.GenCode-fm', '数据库表生成到项目', '/api/v1/gen/toproject/:tableId', 'SYS', 'GET', '2021-05-13 19:59:02.183', '2021-06-13 20:53:48.812', NULL, 0, 0); +INSERT INTO sys_api VALUES (33, 'go-admin/app/admin/apis/tools.Gen.GenMenuAndApi-fm', '数据库表生成到DB', '/api/v1/gen/todb/:tableId', 'SYS', 'GET', '2021-05-13 19:59:02.227', '2021-06-13 20:53:48.853', NULL, 0, 0); +INSERT INTO sys_api VALUES (34, 'go-admin/app/admin/apis/tools.(*SysTable).GetSysTablesTree-fm', '关系表数据【代码生成】', '/api/v1/gen/tabletree', 'SYS', 'GET', '2021-05-13 19:59:02.271', '2021-06-13 20:53:48.893', NULL, 0, 0); +INSERT INTO sys_api VALUES (35, 'go-admin/app/admin/apis/tools.Gen.Preview-fm', '生成预览通过id获取', '/api/v1/gen/preview/:tableId', 'SYS', 'GET', '2021-05-13 19:59:02.315', '2021-06-13 20:53:48.935', NULL, 0, 0); +INSERT INTO sys_api VALUES (36, 'go-admin/app/admin/apis/tools.Gen.GenApiToFile-fm', '生成api带文件', '/api/v1/gen/apitofile/:tableId', 'SYS', 'GET', '2021-05-13 19:59:02.357', '2021-06-13 20:53:48.977', NULL, 0, 0); +INSERT INTO sys_api VALUES (37, 'go-admin/app/admin/apis.System.GenerateCaptchaHandler-fm', '验证码获取', '/api/v1/getCaptcha', 'SYS', 'GET', '2021-05-13 19:59:02.405', '2021-06-13 20:53:49.020', NULL, 0, 0); +INSERT INTO sys_api VALUES (38, 'go-admin/app/admin/apis.SysUser.GetInfo-fm', '用户信息获取', '/api/v1/getinfo', 'SYS', 'GET', '2021-05-13 19:59:02.447', '2021-06-13 20:53:49.065', NULL, 0, 0); +INSERT INTO sys_api VALUES (39, 'go-admin/app/admin/apis.SysMenu.GetPage-fm', '菜单列表', '/api/v1/menu', 'BUS', 'GET', '2021-05-13 19:59:02.497', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (40, 'go-admin/app/admin/apis.SysMenu.GetMenuTreeSelect-fm', '查询菜单下拉树结构【废弃】', '/api/v1/menuTreeselect', 'SYS', 'GET', '2021-05-13 19:59:02.542', '2021-06-03 22:37:21.857', NULL, 0, 0); +INSERT INTO sys_api VALUES (41, 'go-admin/app/admin/apis.SysMenu.Get-fm', '菜单通过id获取', '/api/v1/menu/:id', 'BUS', 'GET', '2021-05-13 19:59:02.584', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (42, 'go-admin/app/admin/apis.SysMenu.GetMenuRole-fm', '角色菜单【顶部左侧菜单】', '/api/v1/menurole', 'SYS', 'GET', '2021-05-13 19:59:02.630', '2021-06-13 20:53:49.574', NULL, 0, 0); +INSERT INTO sys_api VALUES (43, 'go-admin/app/admin/apis.SysMenu.GetMenuIDS-fm', '获取角色对应的菜单id数组【废弃】', '/api/v1/menuids', 'SYS', 'GET', '2021-05-13 19:59:02.675', '2021-06-03 22:39:52.500', NULL, 0, 0); +INSERT INTO sys_api VALUES (44, 'go-admin/app/admin/apis.SysRole.GetPage-fm', '角色列表', '/api/v1/role', 'BUS', 'GET', '2021-05-13 19:59:02.720', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (45, 'go-admin/app/admin/apis.SysMenu.GetMenuTreeSelect-fm', '菜单权限列表【角色配菜单使用】', '/api/v1/roleMenuTreeselect/:roleId', 'SYS', 'GET', '2021-05-13 19:59:02.762', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (46, 'go-admin/app/admin/apis.SysDept.GetDeptTreeRoleSelect-fm', '角色部门结构树【自定义数据权限】', '/api/v1/roleDeptTreeselect/:roleId', 'SYS', 'GET', '2021-05-13 19:59:02.809', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (47, 'go-admin/app/admin/apis.SysRole.Get-fm', '角色通过id获取', '/api/v1/role/:id', 'BUS', 'GET', '2021-05-13 19:59:02.850', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (48, 'github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth.(*GinJWTMiddleware).RefreshHandler-fm', '刷新token', '/api/v1/refresh_token', 'SYS', 'GET', '2021-05-13 19:59:02.892', '2021-06-13 20:53:49.278', NULL, 0, 0); +INSERT INTO sys_api VALUES (53, 'go-admin/app/admin/apis.SysConfig.GetPage-fm', '参数列表', '/api/v1/config', 'BUS', 'GET', '2021-05-13 19:59:03.116', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (54, 'go-admin/app/admin/apis.SysConfig.Get-fm', '参数通过id获取', '/api/v1/config/:id', 'BUS', 'GET', '2021-05-13 19:59:03.157', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (55, 'go-admin/app/admin/apis.SysConfig.GetSysConfigByKEYForService-fm', '参数通过键名搜索【基础默认配置】', '/api/v1/configKey/:configKey', 'SYS', 'GET', '2021-05-13 19:59:03.198', '2021-06-13 20:53:49.745', NULL, 0, 0); +INSERT INTO sys_api VALUES (57, 'go-admin/app/jobs/apis.SysJob.RemoveJobForService-fm', 'job移除', '/api/v1/job/remove/:id', 'BUS', 'GET', '2021-05-13 19:59:03.295', '2021-06-13 20:53:49.786', NULL, 0, 0); +INSERT INTO sys_api VALUES (58, 'go-admin/app/jobs/apis.SysJob.StartJobForService-fm', 'job启动', '/api/v1/job/start/:id', 'BUS', 'GET', '2021-05-13 19:59:03.339', '2021-06-13 20:53:49.829', NULL, 0, 0); +INSERT INTO sys_api VALUES (59, 'go-admin/app/admin/apis.SysPost.GetPage-fm', '岗位列表', '/api/v1/post', 'BUS', 'GET', '2021-05-13 19:59:03.381', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (60, 'go-admin/app/admin/apis.SysPost.Get-fm', '岗位通过id获取', '/api/v1/post/:id', 'BUS', 'GET', '2021-05-13 19:59:03.433', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (62, 'go-admin/app/admin/apis.SysConfig.GetSysConfigBySysApp-fm', '系统前端参数', '/api/v1/app-config', 'SYS', 'GET', '2021-05-13 19:59:03.526', '2021-06-13 20:53:49.994', NULL, 0, 0); +INSERT INTO sys_api VALUES (63, 'go-admin/app/admin/apis.SysUser.GetProfile-fm', '*用户信息获取', '/api/v1/user/profile', 'SYS', 'GET', '2021-05-13 19:59:03.567', '2021-06-13 20:53:50.038', NULL, 0, 0); +INSERT INTO sys_api VALUES (66, 'github.com/go-admin-team/go-admin-core/sdk/pkg/ws.(*Manager).WsClient-fm', '链接ws【定时任务log】', '/ws/:id/:channel', 'BUS', 'GET', '2021-05-13 19:59:03.705', '2021-06-13 20:53:50.167', NULL, 0, 0); +INSERT INTO sys_api VALUES (67, 'github.com/go-admin-team/go-admin-core/sdk/pkg/ws.(*Manager).UnWsClient-fm', '退出ws【定时任务log】', '/wslogout/:id/:channel', 'BUS', 'GET', '2021-05-13 19:59:03.756', '2021-06-13 20:53:50.209', NULL, 0, 0); +INSERT INTO sys_api VALUES (68, 'go-admin/common/middleware/handler.Ping', '*用户基本信息', '/info', 'SYS', 'GET', '2021-05-13 19:59:03.800', '2021-06-13 20:53:50.251', NULL, 0, 0); +INSERT INTO sys_api VALUES (72, 'go-admin/common/actions.CreateAction.func1', '分类创建', '/api/v1/syscategory', 'BUS', 'POST', '2021-05-13 19:59:03.982', '2021-06-13 20:53:50.336', NULL, 0, 0); +INSERT INTO sys_api VALUES (73, 'go-admin/common/actions.CreateAction.func1', '内容创建', '/api/v1/syscontent', 'BUS', 'POST', '2021-05-13 19:59:04.027', '2021-06-13 20:53:50.375', NULL, 0, 0); +INSERT INTO sys_api VALUES (76, 'go-admin/common/actions.CreateAction.func1', 'job创建', '/api/v1/sysjob', 'BUS', 'POST', '2021-05-13 19:59:04.164', '2021-06-13 20:53:50.500', NULL, 0, 0); +INSERT INTO sys_api VALUES (80, 'go-admin/app/admin/apis.SysDictData.Insert-fm', '字典数据创建', '/api/v1/dict/data', 'BUS', 'POST', '2021-05-13 19:59:04.347', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (81, 'go-admin/app/admin/apis.SysDictType.Insert-fm', '字典类型创建', '/api/v1/dict/type', 'BUS', 'POST', '2021-05-13 19:59:04.391', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (82, 'go-admin/app/admin/apis.SysDept.Insert-fm', '部门创建', '/api/v1/dept', 'BUS', 'POST', '2021-05-13 19:59:04.435', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (85, 'github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth.(*GinJWTMiddleware).LoginHandler-fm', '*登录', '/api/v1/login', 'SYS', 'POST', '2021-05-13 19:59:04.597', '2021-06-13 20:53:50.784', NULL, 0, 0); +INSERT INTO sys_api VALUES (86, 'go-admin/common/middleware/handler.LogOut', '*退出', '/api/v1/logout', 'SYS', 'POST', '2021-05-13 19:59:04.642', '2021-06-13 20:53:50.824', NULL, 0, 0); +INSERT INTO sys_api VALUES (87, 'go-admin/app/admin/apis.SysConfig.Insert-fm', '参数创建', '/api/v1/config', 'BUS', 'POST', '2021-05-13 19:59:04.685', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (88, 'go-admin/app/admin/apis.SysMenu.Insert-fm', '菜单创建', '/api/v1/menu', 'BUS', 'POST', '2021-05-13 19:59:04.777', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (89, 'go-admin/app/admin/apis.SysPost.Insert-fm', '岗位创建', '/api/v1/post', 'BUS', 'POST', '2021-05-13 19:59:04.886', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (90, 'go-admin/app/admin/apis.SysRole.Insert-fm', '角色创建', '/api/v1/role', 'BUS', 'POST', '2021-05-13 19:59:04.975', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (91, 'go-admin/app/admin/apis.SysUser.InsetAvatar-fm', '*用户头像编辑', '/api/v1/user/avatar', 'SYS', 'POST', '2021-05-13 19:59:05.058', '2021-06-13 20:53:51.079', NULL, 0, 0); +INSERT INTO sys_api VALUES (92, 'go-admin/app/admin/apis.SysApi.Update-fm', '接口编辑', '/api/v1/sys-api/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.122', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (95, 'go-admin/common/actions.UpdateAction.func1', '分类编辑', '/api/v1/syscategory/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.255', '2021-06-13 20:53:51.247', NULL, 0, 0); +INSERT INTO sys_api VALUES (96, 'go-admin/common/actions.UpdateAction.func1', '内容编辑', '/api/v1/syscontent/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.299', '2021-06-13 20:53:51.289', NULL, 0, 0); +INSERT INTO sys_api VALUES (97, 'go-admin/common/actions.UpdateAction.func1', 'job编辑', '/api/v1/sysjob', 'BUS', 'PUT', '2021-05-13 19:59:05.343', '2021-06-13 20:53:51.331', NULL, 0, 0); +INSERT INTO sys_api VALUES (101, 'go-admin/app/admin/apis.SysDictData.Update-fm', '字典数据编辑', '/api/v1/dict/data/:dictCode', 'BUS', 'PUT', '2021-05-13 19:59:05.519', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (102, 'go-admin/app/admin/apis.SysDictType.Update-fm', '字典类型编辑', '/api/v1/dict/type/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.569', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (103, 'go-admin/app/admin/apis.SysDept.Update-fm', '部门编辑', '/api/v1/dept/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.613', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (104, 'go-admin/app/other/apis.SysFileDir.Update-fm', '文件夹编辑', '/api/v1/file-dir/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.662', '2021-06-13 20:53:51.847', NULL, 0, 0); +INSERT INTO sys_api VALUES (105, 'go-admin/app/other/apis.SysFileInfo.Update-fm', '文件编辑', '/api/v1/file-info/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.709', '2021-06-13 20:53:51.892', NULL, 0, 0); +INSERT INTO sys_api VALUES (106, 'go-admin/app/admin/apis.SysRole.Update-fm', '角色编辑', '/api/v1/role/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.752', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (107, 'go-admin/app/admin/apis.SysRole.Update2DataScope-fm', '角色数据权限修改', '/api/v1/roledatascope', 'BUS', 'PUT', '2021-05-13 19:59:05.803', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (108, 'go-admin/app/admin/apis.SysConfig.Update-fm', '参数编辑', '/api/v1/config/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.848', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (109, 'go-admin/app/admin/apis.SysMenu.Update-fm', '编辑菜单', '/api/v1/menu/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.891', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (110, 'go-admin/app/admin/apis.SysPost.Update-fm', '岗位编辑', '/api/v1/post/:id', 'BUS', 'PUT', '2021-05-13 19:59:05.934', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (111, 'go-admin/app/admin/apis.SysUser.UpdatePwd-fm', '*用户修改密码', '/api/v1/user/pwd', 'SYS', 'PUT', '2021-05-13 19:59:05.987', '2021-06-13 20:53:51.724', NULL, 0, 0); +INSERT INTO sys_api VALUES (112, 'go-admin/common/actions.DeleteAction.func1', '分类删除', '/api/v1/syscategory', 'BUS', 'DELETE', '2021-05-13 19:59:06.030', '2021-06-13 20:53:52.237', NULL, 0, 0); +INSERT INTO sys_api VALUES (113, 'go-admin/common/actions.DeleteAction.func1', '内容删除', '/api/v1/syscontent', 'BUS', 'DELETE', '2021-05-13 19:59:06.076', '2021-06-13 20:53:52.278', NULL, 0, 0); +INSERT INTO sys_api VALUES (114, 'go-admin/app/admin/apis.SysLoginLog.Delete-fm', '登录日志删除', '/api/v1/sys-login-log', 'BUS', 'DELETE', '2021-05-13 19:59:06.118', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (115, 'go-admin/app/admin/apis.SysOperaLog.Delete-fm', '操作日志删除', '/api/v1/sys-opera-log', 'BUS', 'DELETE', '2021-05-13 19:59:06.162', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (116, 'go-admin/common/actions.DeleteAction.func1', 'job删除', '/api/v1/sysjob', 'BUS', 'DELETE', '2021-05-13 19:59:06.206', '2021-06-13 20:53:52.323', NULL, 0, 0); +INSERT INTO sys_api VALUES (117, 'go-admin/app/other/apis.SysChinaAreaData.Delete-fm', '行政区删除', '/api/v1/sys-area-data', 'BUS', 'DELETE', '2021-05-13 19:59:06.249', '2021-06-13 20:53:52.061', NULL, 0, 0); +INSERT INTO sys_api VALUES (120, 'go-admin/app/admin/apis.SysDictData.Delete-fm', '字典数据删除', '/api/v1/dict/data', 'BUS', 'DELETE', '2021-05-13 19:59:06.387', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (121, 'go-admin/app/admin/apis.SysDictType.Delete-fm', '字典类型删除', '/api/v1/dict/type', 'BUS', 'DELETE', '2021-05-13 19:59:06.432', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (122, 'go-admin/app/admin/apis.SysDept.Delete-fm', '部门删除', '/api/v1/dept/:id', 'BUS', 'DELETE', '2021-05-13 19:59:06.475', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (123, 'go-admin/app/other/apis.SysFileDir.Delete-fm', '文件夹删除', '/api/v1/file-dir/:id', 'BUS', 'DELETE', '2021-05-13 19:59:06.520', '2021-06-13 20:53:52.539', NULL, 0, 0); +INSERT INTO sys_api VALUES (124, 'go-admin/app/other/apis.SysFileInfo.Delete-fm', '文件删除', '/api/v1/file-info/:id', 'BUS', 'DELETE', '2021-05-13 19:59:06.566', '2021-06-13 20:53:52.580', NULL, 0, 0); +INSERT INTO sys_api VALUES (125, 'go-admin/app/admin/apis.SysConfig.Delete-fm', '参数删除', '/api/v1/config', 'BUS', 'DELETE', '2021-05-13 19:59:06.612', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (126, 'go-admin/app/admin/apis.SysMenu.Delete-fm', '删除菜单', '/api/v1/menu', 'BUS', 'DELETE', '2021-05-13 19:59:06.654', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (127, 'go-admin/app/admin/apis.SysPost.Delete-fm', '岗位删除', '/api/v1/post/:id', 'BUS', 'DELETE', '2021-05-13 19:59:06.702', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (128, 'go-admin/app/admin/apis.SysRole.Delete-fm', '角色删除', '/api/v1/role', 'BUS', 'DELETE', '2021-05-13 19:59:06.746', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (131, 'github.com/go-admin-team/go-admin-core/tools/transfer.Handler.func1', '系统指标', '/api/v1/metrics', 'SYS', 'GET', '2021-05-17 22:13:55.933', '2021-06-13 20:53:49.614', NULL, 0, 0); +INSERT INTO sys_api VALUES (132, 'go-admin/app/other/router.registerMonitorRouter.func1', '健康状态', '/api/v1/health', 'SYS', 'GET', '2021-05-17 22:13:56.285', '2021-06-13 20:53:49.951', NULL, 0, 0); +INSERT INTO sys_api VALUES (133, 'go-admin/app/admin/apis.HelloWorld', '项目默认接口', '/', 'SYS', 'GET', '2021-05-24 10:30:44.553', '2021-06-13 20:53:47.406', NULL, 0, 0); +INSERT INTO sys_api VALUES (134, 'go-admin/app/other/apis.ServerMonitor.ServerInfo-fm', '服务器基本状态', '/api/v1/server-monitor', 'SYS', 'GET', '2021-05-24 10:30:44.937', '2021-06-13 20:53:48.255', NULL, 0, 0); +INSERT INTO sys_api VALUES (135, 'go-admin/app/admin/apis.SysApi.GetPage-fm', '接口列表', '/api/v1/sys-api', 'BUS', 'GET', '2021-05-24 11:37:53.303', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (136, 'go-admin/app/admin/apis.SysApi.Get-fm', '接口通过id获取', '/api/v1/sys-api/:id', 'BUS', 'GET', '2021-05-24 11:37:53.359', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (137, 'go-admin/app/admin/apis.SysLoginLog.GetPage-fm', '登录日志列表', '/api/v1/sys-login-log', 'BUS', 'GET', '2021-05-24 11:47:30.397', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (138, 'go-admin/app/other/apis.File.UploadFile-fm', '文件上传', '/api/v1/public/uploadFile', 'SYS', 'POST', '2021-05-25 19:16:18.493', '2021-06-13 20:53:50.866', NULL, 0, 0); +INSERT INTO sys_api VALUES (139, 'go-admin/app/admin/apis.SysConfig.Update2Set-fm', '参数信息修改【参数配置】', '/api/v1/set-config', 'BUS', 'PUT', '2021-05-27 09:45:14.853', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (140, 'go-admin/app/admin/apis.SysConfig.Get2Set-fm', '参数获取全部【配置使用】', '/api/v1/set-config', 'BUS', 'GET', '2021-05-27 11:54:14.384', '2021-06-17 11:48:40.732', NULL, 0, 0); +INSERT INTO sys_api VALUES (141, 'go-admin/app/admin/apis.SysUser.GetPage-fm', '管理员列表', '/api/v1/sys-user', 'BUS', 'GET', '2021-06-13 19:24:57.111', '2021-06-17 20:31:14.318', NULL, 0, 0); +INSERT INTO sys_api VALUES (142, 'go-admin/app/admin/apis.SysUser.Get-fm', '管理员通过id获取', '/api/v1/sys-user/:id', 'BUS', 'GET', '2021-06-13 19:24:57.188', '2021-06-17 20:31:14.318', NULL, 0, 0); +INSERT INTO sys_api VALUES (143, 'go-admin/app/admin/apis/tools.(*SysTable).GetSysTablesInfo-fm', '', '/api/v1/sys/tables/info', '', 'GET', '2021-06-13 19:24:57.437', '2021-06-13 20:53:48.047', NULL, 0, 0); +INSERT INTO sys_api VALUES (144, 'go-admin/app/admin/apis/tools.(*SysTable).GetSysTables-fm', '', '/api/v1/sys/tables/info/:tableId', '', 'GET', '2021-06-13 19:24:57.510', '2021-06-13 20:53:48.088', NULL, 0, 0); +INSERT INTO sys_api VALUES (145, 'go-admin/app/admin/apis/tools.(*SysTable).GetSysTableList-fm', '', '/api/v1/sys/tables/page', '', 'GET', '2021-06-13 19:24:57.582', '2021-06-13 20:53:48.128', NULL, 0, 0); +INSERT INTO sys_api VALUES (146, 'github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1', '', '/static/*filepath', '', 'GET', '2021-06-13 19:24:59.641', '2021-06-13 20:53:50.081', NULL, 0, 0); +INSERT INTO sys_api VALUES (147, 'github.com/swaggo/gin-swagger.CustomWrapHandler.func1', '', '/swagger/*any', '', 'GET', '2021-06-13 19:24:59.713', '2021-06-13 20:53:50.123', NULL, 0, 0); +INSERT INTO sys_api VALUES (148, 'github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1', '', '/form-generator/*filepath', '', 'GET', '2021-06-13 19:24:59.914', '2021-06-13 20:53:50.295', NULL, 0, 0); +INSERT INTO sys_api VALUES (149, 'go-admin/app/admin/apis/tools.(*SysTable).InsertSysTable-fm', '', '/api/v1/sys/tables/info', '', 'POST', '2021-06-13 19:25:00.163', '2021-06-13 20:53:50.539', NULL, 0, 0); +INSERT INTO sys_api VALUES (150, 'go-admin/app/admin/apis.SysUser.Insert-fm', '管理员创建', '/api/v1/sys-user', 'BUS', 'POST', '2021-06-13 19:25:00.233', '2021-06-17 20:31:14.318', NULL, 0, 0); +INSERT INTO sys_api VALUES (151, 'go-admin/app/admin/apis.SysUser.Update-fm', '管理员编辑', '/api/v1/sys-user', 'BUS', 'PUT', '2021-06-13 19:25:00.986', '2021-06-17 20:31:14.318', NULL, 0, 0); +INSERT INTO sys_api VALUES (152, 'go-admin/app/admin/apis/tools.(*SysTable).UpdateSysTable-fm', '', '/api/v1/sys/tables/info', '', 'PUT', '2021-06-13 19:25:01.149', '2021-06-13 20:53:51.375', NULL, 0, 0); +INSERT INTO sys_api VALUES (153, 'go-admin/app/admin/apis.SysRole.Update2Status-fm', '', '/api/v1/role-status', '', 'PUT', '2021-06-13 19:25:01.446', '2021-06-13 20:53:51.636', NULL, 0, 0); +INSERT INTO sys_api VALUES (154, 'go-admin/app/admin/apis.SysUser.ResetPwd-fm', '', '/api/v1/user/pwd/reset', '', 'PUT', '2021-06-13 19:25:01.601', '2021-06-13 20:53:51.764', NULL, 0, 0); +INSERT INTO sys_api VALUES (155, 'go-admin/app/admin/apis.SysUser.UpdateStatus-fm', '', '/api/v1/user/status', '', 'PUT', '2021-06-13 19:25:01.671', '2021-06-13 20:53:51.806', NULL, 0, 0); +INSERT INTO sys_api VALUES (156, 'go-admin/app/admin/apis.SysUser.Delete-fm', '管理员删除', '/api/v1/sys-user', 'BUS', 'DELETE', '2021-06-13 19:25:02.043', '2021-06-17 20:31:14.318', NULL, 0, 0); +INSERT INTO sys_api VALUES (157, 'go-admin/app/admin/apis/tools.(*SysTable).DeleteSysTables-fm', '', '/api/v1/sys/tables/info/:tableId', '', 'DELETE', '2021-06-13 19:25:02.283', '2021-06-13 20:53:52.367', NULL, 0, 0); +INSERT INTO sys_api VALUES (158, 'github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1', '', '/static/*filepath', '', 'HEAD', '2021-06-13 19:25:02.734', '2021-06-13 20:53:52.791', NULL, 0, 0); +INSERT INTO sys_api VALUES (159, 'github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1', '', '/form-generator/*filepath', '', 'HEAD', '2021-06-13 19:25:02.808', '2021-06-13 20:53:52.838', NULL, 0, 0); + +INSERT INTO sys_config VALUES (1, '皮肤样式', 'sys_index_skinName', 'skin-green', 'Y', '0', '主框架页-默认皮肤样式名称:蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow', 1, 1, '2021-05-13 19:56:37.913', '2021-06-05 13:50:13.123', NULL); +INSERT INTO sys_config VALUES (2, '初始密码', 'sys_user_initPassword', '123456', 'Y', '0', '用户管理-账号初始密码:123456', 1, 1, '2021-05-13 19:56:37.913', '2021-05-13 19:56:37.913', NULL); +INSERT INTO sys_config VALUES (3, '侧栏主题', 'sys_index_sideTheme', 'theme-dark', 'Y', '0', '主框架页-侧边栏主题:深色主题theme-dark,浅色主题theme-light', 1, 1, '2021-05-13 19:56:37.913', '2021-05-13 19:56:37.913', NULL); +INSERT INTO sys_config VALUES (4, '系统名称', 'sys_app_name', 'go-admin管理系统', 'Y', '1', '', 1, 0, '2021-03-17 08:52:06.067', '2021-05-28 10:08:25.248', NULL); +INSERT INTO sys_config VALUES (5, '系统logo', 'sys_app_logo', 'https://doc-image.zhangwj.com/img/go-admin.png', 'Y', '1', '', 1, 0, '2021-03-17 08:53:19.462', '2021-03-17 08:53:19.462', NULL); + +INSERT INTO sys_dept VALUES (1, 0, '/0/1/', '爱拓科技', 0, 'aituo', '13782218188', 'atuo@aituo.com', '2', 1, 1, '2021-05-13 19:56:37.913', '2021-06-05 17:06:44.960', NULL); +INSERT INTO sys_dept VALUES (7, 1, '/0/1/7/', '研发部', 1, 'aituo', '13782218188', 'atuo@aituo.com', '2', 1, 1, '2021-05-13 19:56:37.913', '2021-06-16 21:35:00.109', NULL); +INSERT INTO sys_dept VALUES (8, 1, '/0/1/8/', '运维部', 0, 'aituo', '13782218188', 'atuo@aituo.com', '2', 1, 1, '2021-05-13 19:56:37.913', '2021-06-16 21:41:39.747', NULL); +INSERT INTO sys_dept VALUES (9, 1, '/0/1/9/', '客服部', 0, 'aituo', '13782218188', 'atuo@aituo.com', '2', 1, 1, '2021-05-13 19:56:37.913', '2021-06-05 17:07:05.993', NULL); +INSERT INTO sys_dept VALUES (10, 1, '/0/1/10/', '人力资源', 3, 'aituo', '13782218188', 'atuo@aituo.com', '1', 1, 1, '2021-05-13 19:56:37.913', '2021-06-05 17:07:08.503', NULL); + +INSERT INTO sys_dict_data VALUES (1, 0, '正常', '2', 'sys_normal_disable', '', '', '', '2', '', '系统正常', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:40.168', NULL); +INSERT INTO sys_dict_data VALUES (2, 0, '停用', '1', 'sys_normal_disable', '', '', '', '2', '', '系统停用', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (3, 0, '男', '0', 'sys_user_sex', '', '', '', '2', '', '性别男', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (4, 0, '女', '1', 'sys_user_sex', '', '', '', '2', '', '性别女', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (5, 0, '未知', '2', 'sys_user_sex', '', '', '', '2', '', '性别未知', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (6, 0, '显示', '0', 'sys_show_hide', '', '', '', '2', '', '显示菜单', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (7, 0, '隐藏', '1', 'sys_show_hide', '', '', '', '2', '', '隐藏菜单', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (8, 0, '是', 'Y', 'sys_yes_no', '', '', '', '2', '', '系统默认是', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (9, 0, '否', 'N', 'sys_yes_no', '', '', '', '2', '', '系统默认否', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (10, 0, '正常', '2', 'sys_job_status', '', '', '', '2', '', '正常状态', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (11, 0, '停用', '1', 'sys_job_status', '', '', '', '2', '', '停用状态', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (12, 0, '默认', 'DEFAULT', 'sys_job_group', '', '', '', '2', '', '默认分组', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (13, 0, '系统', 'SYSTEM', 'sys_job_group', '', '', '', '2', '', '系统分组', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (14, 0, '通知', '1', 'sys_notice_type', '', '', '', '2', '', '通知', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (15, 0, '公告', '2', 'sys_notice_type', '', '', '', '2', '', '公告', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (16, 0, '正常', '2', 'sys_common_status', '', '', '', '2', '', '正常状态', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (17, 0, '关闭', '1', 'sys_common_status', '', '', '', '2', '', '关闭状态', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (18, 0, '新增', '1', 'sys_oper_type', '', '', '', '2', '', '新增操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (19, 0, '修改', '2', 'sys_oper_type', '', '', '', '2', '', '修改操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (20, 0, '删除', '3', 'sys_oper_type', '', '', '', '2', '', '删除操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (21, 0, '授权', '4', 'sys_oper_type', '', '', '', '2', '', '授权操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (22, 0, '导出', '5', 'sys_oper_type', '', '', '', '2', '', '导出操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (23, 0, '导入', '6', 'sys_oper_type', '', '', '', '2', '', '导入操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (24, 0, '强退', '7', 'sys_oper_type', '', '', '', '2', '', '强退操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (25, 0, '生成代码', '8', 'sys_oper_type', '', '', '', '2', '', '生成操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (26, 0, '清空数据', '9', 'sys_oper_type', '', '', '', '2', '', '清空操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (27, 0, '成功', '0', 'sys_notice_status', '', '', '', '2', '', '成功状态', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (28, 0, '失败', '1', 'sys_notice_status', '', '', '', '2', '', '失败状态', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (29, 0, '登录', '10', 'sys_oper_type', '', '', '', '2', '', '登录操作', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (30, 0, '退出', '11', 'sys_oper_type', '', '', '', '2', '', '', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (31, 0, '获取验证码', '12', 'sys_oper_type', '', '', '', '2', '', '获取验证码', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_data VALUES (32, 0, '正常', '1', 'sys_content_status', '', '', '', '1', '', '', 1, 1, '2021-05-13 19:56:40.845', '2021-05-13 19:56:40.845', NULL); +INSERT INTO sys_dict_data VALUES (33, 1, '禁用', '2', 'sys_content_status', '', '', '', '1', '', '', 1, 1, '2021-05-13 19:56:40.845', '2021-05-13 19:56:40.845', NULL); + +INSERT INTO sys_dict_type VALUES (1, '系统开关', 'sys_normal_disable', '2', '系统开关列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (2, '用户性别', 'sys_user_sex', '2', '用户性别列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (3, '菜单状态', 'sys_show_hide', '2', '菜单状态列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (4, '系统是否', 'sys_yes_no', '2', '系统是否列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (5, '任务状态', 'sys_job_status', '2', '任务状态列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (6, '任务分组', 'sys_job_group', '2', '任务分组列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (7, '通知类型', 'sys_notice_type', '2', '通知类型列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (8, '系统状态', 'sys_common_status', '2', '登录状态列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (9, '操作类型', 'sys_oper_type', '2', '操作类型列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (10, '通知状态', 'sys_notice_status', '2', '通知状态列表', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:37.914', NULL); +INSERT INTO sys_dict_type VALUES (11, '内容状态', 'sys_content_status', '2', '', 1, 1, '2021-05-13 19:56:40.813', '2021-05-13 19:56:40.813', NULL); + +INSERT INTO sys_job VALUES (1, '接口测试', 'DEFAULT', 1, '0/5 * * * * ', 'http://localhost:8000', '', 1, 1, 1, 0, '2021-05-13 19:56:37.914', '2021-06-14 20:59:55.417', NULL, 1, 1); +INSERT INTO sys_job VALUES (2, '函数测试', 'DEFAULT', 2, '0/5 * * * * ', 'ExamplesOne', '参数', 1, 1, 1, 0, '2021-05-13 19:56:37.914', '2021-05-31 23:55:37.221', NULL, 1, 1); + +INSERT INTO sys_menu VALUES (2, 'Admin', '系统管理', 'api-server', '/admin', '/0/2', 'M', '无', '', 0, true, '', 'Layout', 10, '0', '1', 0, 1, '2021-05-20 21:58:45.679', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (3, 'SysUserManage', '用户管理', 'user', '/admin/sys-user', '/0/2/3', 'C', '无', 'admin:sysUser:list', 2, false, '', '/admin/sys-user/index', 10, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL); +INSERT INTO sys_menu VALUES (43, '', '新增管理员', 'app-group-fill', '', '/0/2/3/43', 'F', 'POST', 'admin:sysUser:add', 3, false, '', '', 10, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL); +INSERT INTO sys_menu VALUES (44, '', '查询管理员', 'app-group-fill', '', '/0/2/3/44', 'F', 'GET', 'admin:sysUser:query', 3, false, '', '', 40, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL); +INSERT INTO sys_menu VALUES (45, '', '修改管理员', 'app-group-fill', '', '/0/2/3/45', 'F', 'PUT', 'admin:sysUser:edit', 3, false, '', '', 30, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL); +INSERT INTO sys_menu VALUES (46, '', '删除管理员', 'app-group-fill', '', '/0/2/3/46', 'F', 'DELETE', 'admin:sysUser:remove', 3, false, '', '', 20, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 20:31:14.305', NULL); +INSERT INTO sys_menu VALUES (51, 'SysMenuManage', '菜单管理', 'tree-table', '/admin/sys-menu', '/0/2/51', 'C', '无', 'admin:sysMenu:list', 2, true, '', '/admin/sys-menu/index', 30, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (52, 'SysRoleManage', '角色管理', 'peoples', '/admin/sys-role', '/0/2/52', 'C', '无', 'admin:sysRole:list', 2, true, '', '/admin/sys-role/index', 20, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (56, 'SysDeptManage', '部门管理', 'tree', '/admin/sys-dept', '/0/2/56', 'C', '无', 'admin:sysDept:list', 2, false, '', '/admin/sys-dept/index', 40, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (57, 'SysPostManage', '岗位管理', 'pass', '/admin/sys-post', '/0/2/57', 'C', '无', 'admin:sysPost:list', 2, false, '', '/admin/sys-post/index', 50, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (58, 'Dict', '字典管理', 'education', '/admin/dict', '/0/2/58', 'C', '无', 'admin:sysDictType:list', 2, false, '', '/admin/dict/index', 60, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (59, 'SysDictDataManage', '字典数据', 'education', '/admin/dict/data/:dictId', '/0/2/59', 'C', '无', 'admin:sysDictData:list', 2, false, '', '/admin/dict/data', 100, '1', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (60, 'Tools', '开发工具', 'dev-tools', '/dev-tools', '/0/60', 'M', '无', '', 0, false, '', 'Layout', 40, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-05 22:15:03.465', NULL); +INSERT INTO sys_menu VALUES (61, 'Swagger', '系统接口', 'guide', '/dev-tools/swagger', '/0/60/61', 'C', '无', '', 60, false, '', '/dev-tools/swagger/index', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-05 22:15:03.465', NULL); +INSERT INTO sys_menu VALUES (62, 'SysConfigManage', '参数管理', 'swagger', '/admin/sys-config', '/0/2/62', 'C', '无', 'admin:sysConfig:list', 2, false, '', '/admin/sys-config/index', 70, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (211, 'Log', '日志管理', 'log', '/log', '/0/2/211', 'M', '', '', 2, false, '', '/log/index', 80, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (212, 'SysLoginLogManage', '登录日志', 'logininfor', '/admin/sys-login-log', '/0/2/211/212', 'C', '', 'admin:sysLoginLog:list', 211, false, '', '/admin/sys-login-log/index', 1, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (216, 'OperLog', '操作日志', 'skill', '/admin/sys-oper-log', '/0/2/211/216', 'C', '', 'admin:sysOperLog:list', 211, false, '', '/admin/sys-oper-log/index', 1, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (220, '', '新增菜单', 'app-group-fill', '', '/0/2/51/220', 'F', '', 'admin:sysMenu:add', 51, false, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (221, '', '修改菜单', 'app-group-fill', '', '/0/2/51/221', 'F', '', 'admin:sysMenu:edit', 51, false, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (222, '', '查询菜单', 'app-group-fill', '', '/0/2/51/222', 'F', '', 'admin:sysMenu:query', 51, false, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (223, '', '删除菜单', 'app-group-fill', '', '/0/2/51/223', 'F', '', 'admin:sysMenu:remove', 51, false, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (224, '', '新增角色', 'app-group-fill', '', '/0/2/52/224', 'F', '', 'admin:sysRole:add', 52, false, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (225, '', '查询角色', 'app-group-fill', '', '/0/2/52/225', 'F', '', 'admin:sysRole:query', 52, false, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (226, '', '修改角色', 'app-group-fill', '', '/0/2/52/226', 'F', '', 'admin:sysRole:update', 52, false, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (227, '', '删除角色', 'app-group-fill', '', '/0/2/52/227', 'F', '', 'admin:sysRole:remove', 52, false, '', '', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (228, '', '查询部门', 'app-group-fill', '', '/0/2/56/228', 'F', '', 'admin:sysDept:query', 56, false, '', '', 40, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (229, '', '新增部门', 'app-group-fill', '', '/0/2/56/229', 'F', '', 'admin:sysDept:add', 56, false, '', '', 10, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (230, '', '修改部门', 'app-group-fill', '', '/0/2/56/230', 'F', '', 'admin:sysDept:edit', 56, false, '', '', 30, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (231, '', '删除部门', 'app-group-fill', '', '/0/2/56/231', 'F', '', 'admin:sysDept:remove', 56, false, '', '', 20, '0', '1', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (232, '', '查询岗位', 'app-group-fill', '', '/0/2/57/232', 'F', '', 'admin:sysPost:query', 57, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (233, '', '新增岗位', 'app-group-fill', '', '/0/2/57/233', 'F', '', 'admin:sysPost:add', 57, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (234, '', '修改岗位', 'app-group-fill', '', '/0/2/57/234', 'F', '', 'admin:sysPost:edit', 57, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (235, '', '删除岗位', 'app-group-fill', '', '/0/2/57/235', 'F', '', 'admin:sysPost:remove', 57, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (236, '', '查询字典', 'app-group-fill', '', '/0/2/58/236', 'F', '', 'admin:sysDictType:query', 58, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (237, '', '新增类型', 'app-group-fill', '', '/0/2/58/237', 'F', '', 'admin:sysDictType:add', 58, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (238, '', '修改类型', 'app-group-fill', '', '/0/2/58/238', 'F', '', 'admin:sysDictType:edit', 58, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (239, '', '删除类型', 'app-group-fill', '', '/0/2/58/239', 'F', '', 'system:sysdicttype:remove', 58, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (240, '', '查询数据', 'app-group-fill', '', '/0/2/59/240', 'F', '', 'admin:sysDictData:query', 59, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (241, '', '新增数据', 'app-group-fill', '', '/0/2/59/241', 'F', '', 'admin:sysDictData:add', 59, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (242, '', '修改数据', 'app-group-fill', '', '/0/2/59/242', 'F', '', 'admin:sysDictData:edit', 59, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (243, '', '删除数据', 'app-group-fill', '', '/0/2/59/243', 'F', '', 'admin:sysDictData:remove', 59, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (244, '', '查询参数', 'app-group-fill', '', '/0/2/62/244', 'F', '', 'admin:sysConfig:query', 62, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (245, '', '新增参数', 'app-group-fill', '', '/0/2/62/245', 'F', '', 'admin:sysConfig:add', 62, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (246, '', '修改参数', 'app-group-fill', '', '/0/2/62/246', 'F', '', 'admin:sysConfig:edit', 62, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (247, '', '删除参数', 'app-group-fill', '', '/0/2/62/247', 'F', '', 'admin:sysConfig:remove', 62, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (248, '', '查询登录日志', 'app-group-fill', '', '/0/2/211/212/248', 'F', '', 'admin:sysLoginLog:query', 212, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (249, '', '删除登录日志', 'app-group-fill', '', '/0/2/211/212/249', 'F', '', 'admin:sysLoginLog:remove', 212, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (250, '', '查询操作日志', 'app-group-fill', '', '/0/2/211/216/250', 'F', '', 'admin:sysOperLog:query', 216, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (251, '', '删除操作日志', 'app-group-fill', '', '/0/2/211/216/251', 'F', '', 'admin:sysOperLog:remove', 216, false, '', '', 0, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (261, 'Gen', '代码生成', 'code', '/dev-tools/gen', '/0/60/261', 'C', '', '', 60, false, '', '/dev-tools/gen/index', 2, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-16 21:26:12.446', NULL); +INSERT INTO sys_menu VALUES (262, 'EditTable', '代码生成修改', 'build', '/dev-tools/editTable', '/0/60/262', 'C', '', '', 60, false, '', '/dev-tools/gen/editTable', 100, '1', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-16 21:26:12.446', NULL); +INSERT INTO sys_menu VALUES (264, 'Build', '表单构建', 'build', '/dev-tools/build', '/0/60/264', 'C', '', '', 60, false, '', '/dev-tools/build/index', 1, '0', '1', 1, 1, '2020-04-11 15:52:48.000', '2021-06-16 21:26:12.446', NULL); +INSERT INTO sys_menu VALUES (269, 'ServerMonitor', '服务监控', 'druid', '/sys-tools/monitor', '/0/60/269', 'C', '', 'sysTools:serverMonitor:list', 537, false, '', '/sys-tools/monitor', 0, '0', '1', 1, 1, '2020-04-14 00:28:19.000', '2021-06-16 21:26:12.446', NULL); +INSERT INTO sys_menu VALUES (459, 'Schedule', '定时任务', 'time-range', '/schedule', '/0/459', 'M', '无', '', 0, false, '', 'Layout', 20, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL); +INSERT INTO sys_menu VALUES (460, 'ScheduleManage', 'Schedule', 'job', '/schedule/manage', '/0/459/460', 'C', '无', 'job:sysJob:list', 459, false, '', '/schedule/index', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL); +INSERT INTO sys_menu VALUES (461, 'sys_job', '分页获取定时任务', 'app-group-fill', '', '/0/459/460/461', 'F', '无', 'job:sysJob:query', 460, false, '', '', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL); +INSERT INTO sys_menu VALUES (462, 'sys_job', '创建定时任务', 'app-group-fill', '', '/0/459/460/462', 'F', '无', 'job:sysJob:add', 460, false, '', '', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL); +INSERT INTO sys_menu VALUES (463, 'sys_job', '修改定时任务', 'app-group-fill', '', '/0/459/460/463', 'F', '无', 'job:sysJob:edit', 460, false, '', '', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL); +INSERT INTO sys_menu VALUES (464, 'sys_job', '删除定时任务', 'app-group-fill', '', '/0/459/460/464', 'F', '无', 'job:sysJob:remove', 460, false, '', '', 0, '0', '1', 1, 1, '2020-08-03 09:17:37.000', '2021-06-05 22:15:03.465', NULL); +INSERT INTO sys_menu VALUES (471, 'JobLog', '日志', 'bug', '/schedule/log', '/0/459/471', 'C', '', '', 459, false, '', '/schedule/log', 0, '1', '1', 1, 1, '2020-08-05 21:24:46.000', '2021-06-05 22:15:03.465', NULL); +INSERT INTO sys_menu VALUES (528, 'SysApiManage', '接口管理', 'api-doc', '/admin/sys-api', '/0/527/528', 'C', '无', 'admin:sysApi:list', 2, false, '', '/admin/sys-api/index', 0, '0', '0', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (529, '', '查询接口', 'app-group-fill', '', '/0/527/528/529', 'F', '无', 'admin:sysApi:query', 528, false, '', '', 40, '0', '0', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (531, '', '修改接口', 'app-group-fill', '', '/0/527/528/531', 'F', '无', 'admin:sysApi:edit', 528, false, '', '', 30, '0', '0', 0, 1, '2021-05-20 22:08:44.526', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (537, 'SysTools', '系统工具', 'system-tools', '/sys-tools', '', 'M', '', '', 0, false, '', 'Layout', 30, '0', '1', 1, 1, '2021-05-21 11:13:32.166', '2021-06-16 21:26:12.446', NULL); +INSERT INTO sys_menu VALUES (540, 'SysConfigSet', '参数设置', 'system-tools', '/admin/sys-config/set', '', 'C', '', 'admin:sysConfigSet:list', 2, false, '', '/admin/sys-config/set', 0, '0', '1', 1, 1, '2021-05-25 16:06:52.560', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu VALUES (542, '', '修改', 'upload', '', '', 'F', '', 'admin:sysConfigSet:update', 540, false, '', '', 0, '0', '1', 1, 1, '2021-06-13 11:45:48.670', '2021-06-17 11:48:40.703', NULL); +INSERT INTO sys_menu_api_rule VALUES (216, 6); +INSERT INTO sys_menu_api_rule VALUES (250, 6); +INSERT INTO sys_menu_api_rule VALUES (58, 21); +INSERT INTO sys_menu_api_rule VALUES (236, 21); +INSERT INTO sys_menu_api_rule VALUES (238, 23); +INSERT INTO sys_menu_api_rule VALUES (59, 24); +INSERT INTO sys_menu_api_rule VALUES (240, 24); +INSERT INTO sys_menu_api_rule VALUES (242, 25); +INSERT INTO sys_menu_api_rule VALUES (58, 26); +INSERT INTO sys_menu_api_rule VALUES (236, 26); +INSERT INTO sys_menu_api_rule VALUES (56, 27); +INSERT INTO sys_menu_api_rule VALUES (228, 27); +INSERT INTO sys_menu_api_rule VALUES (230, 28); +INSERT INTO sys_menu_api_rule VALUES (226, 29); +INSERT INTO sys_menu_api_rule VALUES (51, 39); +INSERT INTO sys_menu_api_rule VALUES (51, 135); +INSERT INTO sys_menu_api_rule VALUES (222, 39); +INSERT INTO sys_menu_api_rule VALUES (221, 41); +INSERT INTO sys_menu_api_rule VALUES (52, 44); +INSERT INTO sys_menu_api_rule VALUES (225, 44); +INSERT INTO sys_menu_api_rule VALUES (226, 45); +INSERT INTO sys_menu_api_rule VALUES (226, 46); +INSERT INTO sys_menu_api_rule VALUES (226, 47); +INSERT INTO sys_menu_api_rule VALUES (62, 53); +INSERT INTO sys_menu_api_rule VALUES (244, 53); +INSERT INTO sys_menu_api_rule VALUES (246, 54); +INSERT INTO sys_menu_api_rule VALUES (57, 59); +INSERT INTO sys_menu_api_rule VALUES (232, 59); +INSERT INTO sys_menu_api_rule VALUES (234, 60); +INSERT INTO sys_menu_api_rule VALUES (241, 80); +INSERT INTO sys_menu_api_rule VALUES (237, 81); +INSERT INTO sys_menu_api_rule VALUES (229, 82); +INSERT INTO sys_menu_api_rule VALUES (245, 87); +INSERT INTO sys_menu_api_rule VALUES (220, 88); +INSERT INTO sys_menu_api_rule VALUES (233, 89); +INSERT INTO sys_menu_api_rule VALUES (224, 90); +INSERT INTO sys_menu_api_rule VALUES (531, 92); +INSERT INTO sys_menu_api_rule VALUES (242, 101); +INSERT INTO sys_menu_api_rule VALUES (238, 102); +INSERT INTO sys_menu_api_rule VALUES (230, 103); +INSERT INTO sys_menu_api_rule VALUES (226, 106); +INSERT INTO sys_menu_api_rule VALUES (226, 107); +INSERT INTO sys_menu_api_rule VALUES (246, 108); +INSERT INTO sys_menu_api_rule VALUES (221, 109); +INSERT INTO sys_menu_api_rule VALUES (234, 110); +INSERT INTO sys_menu_api_rule VALUES (249, 114); +INSERT INTO sys_menu_api_rule VALUES (251, 115); +INSERT INTO sys_menu_api_rule VALUES (243, 120); +INSERT INTO sys_menu_api_rule VALUES (239, 121); +INSERT INTO sys_menu_api_rule VALUES (231, 122); +INSERT INTO sys_menu_api_rule VALUES (247, 125); +INSERT INTO sys_menu_api_rule VALUES (223, 126); +INSERT INTO sys_menu_api_rule VALUES (235, 127); +INSERT INTO sys_menu_api_rule VALUES (227, 128); +INSERT INTO sys_menu_api_rule VALUES (528, 135); +INSERT INTO sys_menu_api_rule VALUES (529, 135); +INSERT INTO sys_menu_api_rule VALUES (531, 136); +INSERT INTO sys_menu_api_rule VALUES (212, 137); +INSERT INTO sys_menu_api_rule VALUES (248, 137); +INSERT INTO sys_menu_api_rule VALUES (542, 139); +INSERT INTO sys_menu_api_rule VALUES (540, 140); +INSERT INTO sys_menu_api_rule VALUES (3, 141); +INSERT INTO sys_menu_api_rule VALUES (44, 141); +INSERT INTO sys_menu_api_rule VALUES (45, 142); +INSERT INTO sys_menu_api_rule VALUES (43, 150); +INSERT INTO sys_menu_api_rule VALUES (45, 151); +INSERT INTO sys_menu_api_rule VALUES (46, 156); +INSERT INTO sys_post VALUES (1, '首席执行官', 'CEO', 0, '2','首席执行官', 1, 1, '2021-05-13 19:56:37.913', '2021-05-13 19:56:37.913', NULL); +INSERT INTO sys_post VALUES (2, '首席技术执行官', 'CTO', 2, '2','首席技术执行官', 1, 1,'2021-05-13 19:56:37.913', '2021-05-13 19:56:37.913', NULL); +INSERT INTO sys_post VALUES (3, '首席运营官', 'COO', 3, '2','测试工程师', 1, 1,'2021-05-13 19:56:37.913', '2021-05-13 19:56:37.913', NULL); +INSERT INTO sys_role VALUES (1, '系统管理员', '2', 'admin', 1, '', '', true, '', 1, 1, '2021-05-13 19:56:37.913', '2021-05-13 19:56:37.913', NULL); +INSERT INTO sys_user VALUES (1, 'admin', '$2a$10$/Glr4g9Svr6O0kvjsRJCXu3f0W8/dsP3XZyVNi1019ratWpSPMyw.', 'zhangwj', '13818888888', 1, '', '', '1', '1@qq.com', 1, 1, '', '2', 1, 1, '2021-05-13 19:56:37.914', '2021-05-13 19:56:40.205', NULL); +-- 数据完成 ; \ No newline at end of file diff --git a/config/extend.go b/config/extend.go new file mode 100644 index 0000000..52c01d9 --- /dev/null +++ b/config/extend.go @@ -0,0 +1,78 @@ +package config + +var ExtConfig Extend + +// Extend 扩展配置 +// +// extend: +// demo: +// name: demo-name +// +// 使用方法: config.ExtConfig......即可!! +type Extend struct { + AMap AMap // 这里配置对应配置文件的结构即可 + Redis RedisConfig `mapstructure:"redis"` + ServiceId int64 `mapstructure:"serviceId"` //雪花算法id(服务id,比如1,2,3等,当多机部署需定义唯一id) + EmailConfig EmailConfig `mapstructure:"emailConfig"` + BinanceSet BinanceConfig `mapstructure:"binanceSet"` //binance配置 + Domain string //网站域名 + GoToneSmsConfig GoToneSmsConfig `mapstructure:"GoToneSmsConfig"` + UDunConfig UDunConfig `mapstructure:"UDunConfig"` + ProxyUrl string //代理地址 + CoinGate CoinGateConfig `mapstructure:"coingate"` //coingate钱包 + +} + +type CoinGateConfig struct { + Auth string + //接口地址 + Endpoint string +} + +type AMap struct { + Key string +} + +// redis配置 +type RedisConfig struct { + Addr string + Password string + Db int +} + +// EmailConfig 邮件验证码配置 +type EmailConfig struct { + MailSmtpHost string `json:"mail_smtp_host"` // 邮件服务 + MailSmtpPort string `json:"mail_smtp_port"` // 端口 + MailSmtpUser string `json:"mail_smtp_user"` // 发件地址 + MailFrom string `json:"mail_from"` // 发件人 + MailSmtpPass string `json:"mail_smtp_pass"` // 秘钥 + MailVerifyType string `json:"mail_verify_type"` +} + +// EmailSend 邮件发送配置 +type EmailSend struct { + EmailConfig + Subject string `json:"subject"` // 主题 + Content string `json:"content"` // 内容 + To string `json:"to"` // 收件地址 +} + +// BinanceConfig 币安路由配置 +type BinanceConfig struct { + SpotRestURL string + FutRestURL string +} + +type GoToneSmsConfig struct { + SenderId string `json:"sender_id"` + APIEndpoint string `json:"api_endpoint"` + Authorization string `json:"authorization"` +} + +type UDunConfig struct { + UDunUrl string `json:"UDunUrl"` + UDunMerchantID string `json:"UDunMerchantID"` + UDunKey string `json:"UDunKey"` + CurrServerIp string `json:"CurrServerIp"` +} diff --git a/config/pg.sql b/config/pg.sql new file mode 100644 index 0000000..ba3c0c8 --- /dev/null +++ b/config/pg.sql @@ -0,0 +1,21 @@ +-- 开始初始化数据 ; +create sequence if not exists sys_role_role_id_seq; +create sequence if not exists sys_user_user_id_seq; +create sequence if not exists sys_post_post_id_seq; +create sequence if not exists sys_menu_menu_id_seq; +create sequence if not exists sys_dict_type_dict_id_seq; +create sequence if not exists sys_dict_data_dict_code_seq; +create sequence if not exists sys_dept_dept_id_seq; +create sequence if not exists sys_config_config_id_seq; +create sequence if not exists sys_job_id_seq; + +select setval('sys_role_role_id_seq',4); +select setval('sys_user_user_id_seq',5); +select setval('sys_post_post_id_seq',4); +select setval('sys_menu_menu_id_seq',543); +select setval('sys_dict_type_dict_id_seq',12); +select setval('sys_dict_data_dict_code_seq',34); +select setval('sys_dept_dept_id_seq',11); +select setval('sys_config_config_id_seq',6); +select setval('sys_job_id_seq',3); +-- 数据完成 ; diff --git a/config/proxyconfig.go b/config/proxyconfig.go new file mode 100644 index 0000000..3dff96d --- /dev/null +++ b/config/proxyconfig.go @@ -0,0 +1,14 @@ +package config + +type ProxyConfig struct { + ProxyConfigs []TriggerApiProxyConfig `yaml:"proxys"` +} + +type TriggerApiProxyConfig struct { + ApiKey string `yaml:"apiKey"` + ProxyAddress string `yaml:"proxyAddress"` + Password string `yaml:"password"` +} + +// 主动触发配置代理 +var TriggerProxys ProxyConfig diff --git a/config/proxysettings.yml b/config/proxysettings.yml new file mode 100644 index 0000000..847ccfe --- /dev/null +++ b/config/proxysettings.yml @@ -0,0 +1,7 @@ +proxys: +- apiKey: 9me3q2OICqaIrkNayw0WtIlM1m9rH3v6BuigZwt8FLXEa3yhND50eXYmlOy9yUAU #apikey + proxyAddress: 47.250.176.8:3366 #代理地址 + password: web000:web000 #密码 +- apiKey: qsuwhTuQZ7kzGc2wPqDZ8lgow5Lx5BBeRTXbqgJYfgVVPN48xecgqTDhWj6mWxqs + proxyAddress: 47.250.176.8:3366 + password: web000:web000 \ No newline at end of file diff --git a/config/serverinit/binance_futures_init.go b/config/serverinit/binance_futures_init.go new file mode 100644 index 0000000..ca20be0 --- /dev/null +++ b/config/serverinit/binance_futures_init.go @@ -0,0 +1,77 @@ +package serverinit + +import ( + "go-admin/config" + "go-admin/models" + "go-admin/pkg/httputils" + "go-admin/services/binanceservice" + "go-admin/services/futureservice" + + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/robfig/cron/v3" +) + +func FuturesInit() error { + httputils.InitProxy(config.ExtConfig.ProxyUrl) + + //FutFundingRate() + + log.Info("初始化交易对-开始") + data := make(map[string]models.TradeSet, 0) + err := binanceservice.GetAndReloadSymbols(&data) + + if err != nil { + return err + } + log.Info("初始化交易对-结束") + + log.Info("初始化24h行情-开始") + _, err = binanceservice.InitSymbolsTicker24h(&data) + + if err != nil { + return err + } + log.Info("初始化24h行情-结束") + + log.Info("订阅合约-开始") + go futureservice.StartBinanceProWs("normal") + + //定时任务 + RunJobs() + return nil +} + +/* +缓存合约资金费率 +*/ +func FutFundingRate() error { + log.Info("初始化 合约-费率 开始") + _, err := binanceservice.GetPremiumIndex() + + if err != nil { + log.Error("初始化 合约-费率 失败:", err) + return err + } + + log.Info("初始化 合约-费率 成功") + return nil +} + +// 定义任务 +func RunJobs() { + cronJob := cron.New() + + //添加任务 + //_, err := cronJob.AddFunc("@every 1h", func() { + // FutFundingRate() + //}) + // + //if err != nil { + // fmt.Println("添加定时获取资金费率任务失败") + //} else { + // fmt.Println("添加定时获取资金费率任务成功") + //} + + //启动 + cronJob.Start() +} diff --git a/config/serverinit/binance_spot_init.go b/config/serverinit/binance_spot_init.go new file mode 100644 index 0000000..63bb32b --- /dev/null +++ b/config/serverinit/binance_spot_init.go @@ -0,0 +1,30 @@ +package serverinit + +import ( + "go-admin/models" + "go-admin/pkg/utility" + "go-admin/services/binanceservice" + "go-admin/services/spotservice" + "os" +) + +/* +初始化-币安现货交易对24h行情 +*/ +func SpotCurrencyInit() (map[string]models.TradeSet, []string) { + result, symbols, err := binanceservice.GetSpotSymbols() + + if err != nil { + os.Exit(-1) + } + + return result, symbols +} + +/* +初始化现货订阅 + - @workType 工作类型 normal-正常任务,trigger-主动触发 +*/ +func SpotSubscribeInit(workType string) { + utility.SafeGoParam(spotservice.StartBinanceProWs, workType) +} diff --git a/config/serverinit/business_init.go b/config/serverinit/business_init.go new file mode 100644 index 0000000..87ebca1 --- /dev/null +++ b/config/serverinit/business_init.go @@ -0,0 +1,134 @@ +package serverinit + +import ( + "fmt" + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/common/const/rediskey" + "go-admin/common/helper" + "go-admin/config" + "go-admin/pkg/httputils" + "go-admin/services/binanceservice" + "go-admin/services/futureservice" + "go-admin/services/scriptservice" + "os" + + "github.com/bytedance/sonic" + "github.com/go-admin-team/go-admin-core/logger" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/robfig/cron/v3" + "gorm.io/gorm" +) + +// 业务初始化 +func BusinessInit(db *gorm.DB) { + httputils.InitProxy(config.ExtConfig.ProxyUrl) + + if err := loadApiUser(db); err != nil { //加载api用户 + os.Exit(-1) + } + + //初始化订单配置 + binanceservice.ResetSystemSetting(db) + + //币安 现货交易对 + SpotCurrencyInit() + + //币安 合约交易对 + FuturesInit() + + //第一次初始化交易对价格 + spotApi := binanceservice.SpotRestApi{} + futApi := binanceservice.FutRestApi{} + spotApi.Ticker() + futApi.Ticker() + + //现货 订阅 + SpotSubscribeInit("normal") + //合约订阅 + futureservice.StartBinanceProWs("normal") + //定时任务 + RunSpotJobs(db) +} + +// 开启现货ws监听 +func SpotWsInit() { + //启动websocket服务 + //go func() { + // err := wsservice.StartServer(":3333") + // if err != nil { + // log.Info("wsservice.StartServer", zap.Error(err)) + // + // _, cancel := context.WithTimeout(context.Background(), 5*time.Second) + // cancel() + // } + //}() +} + +// 定义任务 +func RunSpotJobs(db *gorm.DB) { + cronJob := cron.New() + //添加任务 同步交易对行情 + // spotApi := spotservice.SpotRestApi{} + // futApi := futureservice.FutRestApi{} + // _, err := cronJob.AddFunc("@every 10s", func() { + // //BuySellOrderNew(db) + // spotApi.Ticker() + // futApi.Ticker() + // }) + // if err != nil { + // return + // } + + //定时执行脚本任务 + _, err := cronJob.AddFunc("@every 3s", func() { + order := scriptservice.PreOrder{} + order.AddOrder(db) + }) + if err != nil { + return + } + + //启动 + cronJob.Start() +} + +/* +加载api user +*/ +func loadApiUser(db *gorm.DB) error { + users := make([]models.LineApiUser, 0) + + if err := db.Model(&models.LineApiUser{}).Where("open_status =1").Find(&users).Error; err != nil { + log.Error("loadApiUser:", err) + return err + } + + for _, user := range users { + key := fmt.Sprintf(rediskey.API_USER, user.Id) + val, _ := sonic.MarshalString(&user) + + if val != "" { + if err := helper.DefaultRedis.SetString(key, val); err != nil { + log.Error("loadApiUser 保存redis:", err) + } + } + } + + groups := make([]models.LineApiGroup, 0) + + if err := db.Model(&models.LineApiGroup{}).Find(&groups).Error; err != nil { + log.Error("loadApiGroup:", err) + return err + } + + apiGroupService := service.LineApiGroup{} + apiGroupService.Log = logger.NewHelper(sdk.Runtime.GetLogger()).WithFields(map[string]interface{}{}) + + for _, group := range groups { + service.SetApiGroupCache(group) + } + + return nil +} diff --git a/config/serverinit/closeposition_init.go b/config/serverinit/closeposition_init.go new file mode 100644 index 0000000..308ba20 --- /dev/null +++ b/config/serverinit/closeposition_init.go @@ -0,0 +1,15 @@ +package serverinit + +import ( + "go-admin/config" + "go-admin/pkg/httputils" + "go-admin/services/futureservice" +) + +func ClosePositionInit() { + httputils.InitProxy(config.ExtConfig.ProxyUrl) + //现货 订阅 + SpotSubscribeInit("trigger") + //合约订阅 + futureservice.StartBinanceProWs("trigger") +} diff --git a/config/serverinit/usersubscribeinit.go b/config/serverinit/usersubscribeinit.go new file mode 100644 index 0000000..b4f6df0 --- /dev/null +++ b/config/serverinit/usersubscribeinit.go @@ -0,0 +1,85 @@ +package serverinit + +import ( + "context" + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/common/const/rediskey" + "go-admin/common/helper" + "go-admin/pkg/utility" + "go-admin/services/excservice" + "os" + "time" + + "github.com/bytedance/sonic" + log "github.com/go-admin-team/go-admin-core/logger" + "gorm.io/gorm" +) + +// 初始化用户订阅ws连接 +func UserSubscribeInit(orm *gorm.DB, ctx context.Context) { + var list []models.LineApiUser + + err := orm.Model(&models.LineApiUser{}).Where("open_status=1 ").Find(&list).Error + + if err != nil { + log.Error("获取用户api失败", err) + os.Exit(1) + } + + for _, item := range list { + service.OpenUserBinanceWebsocket(item) + } + + utility.SafeGo(func() { + ticker := time.NewTicker(time.Second * 5) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + log.Info("ctx.Done() 被触发,原因:", ctx.Err()) // 打印上下文错误信息 + return + case <-ticker.C: + deleteKeys, _ := helper.DefaultRedis.GetAllList(rediskey.ApiUserDeleteList) + activeApis, _ := helper.DefaultRedis.GetAllList(rediskey.ApiUserActiveList) + + //移除连接 + for _, item := range deleteKeys { + if wm, ok := excservice.SpotSockets[item]; ok { + wm.Stop() + + delete(excservice.FutureSockets, item) + } + + if wm, ok := excservice.SpotSockets[item]; ok { + wm.Stop() + + delete(excservice.SpotSockets, item) + } + + if _, err := helper.DefaultRedis.LRem(rediskey.ApiUserDeleteList, item); err != nil { + log.Error("移除 待关闭websocket 失败:", err) + } + } + + //连接websocket + var apiUser models.LineApiUser + for _, item := range activeApis { + if item == "" { + continue + } + + sonic.Unmarshal([]byte(item), &apiUser) + + if apiUser.Id > 0 { + service.OpenUserBinanceWebsocket(apiUser) + if _, err := helper.DefaultRedis.LRem(rediskey.ApiUserActiveList, item); err != nil { + log.Error("删除 待触发的apiUser失败", err) + } + } + } + } + } + }) +} diff --git a/config/settings.demo.yml b/config/settings.demo.yml new file mode 100644 index 0000000..9b297e6 --- /dev/null +++ b/config/settings.demo.yml @@ -0,0 +1,34 @@ +settings: + application: + demomsg: "谢谢您的参与,但为了大家更好的体验,所以本次提交就算了吧!\U0001F600\U0001F600\U0001F600" + enabledp: true + host: 0.0.0.0 + mode: demo + name: testApp + port: 8000 + readtimeout: 10000 + writertimeout: 20000 + database: + driver: sqlite3 + source: ./go-admin-db.db + gen: + dbname: testhhh + frontpath: ../go-admin-ui/src + jwt: + secret: go-admin + timeout: 3600 + logger: + # 日志存放路径 + path: temp/logs + # 日志输出,file:文件,default:命令行,其他:命令行 + stdout: '' #控制台日志,启用后,不输出到文件 + # 日志等级, trace, debug, info, warn, error, fatal + level: trace + # 数据库日志开关 + enableddb: true + queue: + memory: + poolSize: 100 + extend: + amap: + key: de7a062c984bf828d5d1b3a631a517e4 \ No newline at end of file diff --git a/config/settings.full.yml b/config/settings.full.yml new file mode 100644 index 0000000..bcca7e2 --- /dev/null +++ b/config/settings.full.yml @@ -0,0 +1,58 @@ +settings: + application: + # dev开发环境 test测试环境 prod线上环境 + mode: dev + # 服务器ip,默认使用 0.0.0.0 + host: 0.0.0.0 + # 服务名称 + name: testApp + # 端口号 + port: 8000 # 服务端口号 + readtimeout: 1 + writertimeout: 2 + # 数据权限功能开关 + enabledp: false + ssl: + # https对应的域名 + domain: localhost:8000 + # https开关 + enable: false + # ssl 证书key + key: keystring + # ssl 证书路径 + pem: temp/pem.pem + logger: + # 日志存放路径 + path: temp/logs + # 日志输出,file:文件,default:命令行,其他:命令行 + stdout: '' #控制台日志,启用后,不输出到文件 + # 日志等级, trace, debug, info, warn, error, fatal + level: trace + # 数据库日志开关 + enableddb: false + jwt: + # token 密钥,生产环境时及的修改 + secret: go-admin + # token 过期时间 单位:秒 + timeout: 3600 + database: + # 数据库类型 mysql,sqlite3, postgres + driver: mysql + # 数据库连接字符串 mysql 缺省信息 charset=utf8&parseTime=True&loc=Local&timeout=1000ms + source: user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8&parseTime=True&loc=Local&timeout=1000ms + # source: sqlite3.db + # source: host=myhost port=myport user=gorm dbname=gorm password=mypassword + registers: + - sources: + - user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8&parseTime=True&loc=Local&timeout=1000ms + gen: + # 代码生成读取的数据库名称 + dbname: dbname + # 代码生成是使用前端代码存放位置,需要指定到src文件夹,相对路径 + frontpath: ../go-admin-ui/src + queue: + memory: + poolSize: 100 + extend: # 扩展项使用说明 + demo: + name: data diff --git a/config/settings.sqlite.yml b/config/settings.sqlite.yml new file mode 100644 index 0000000..90e98e2 --- /dev/null +++ b/config/settings.sqlite.yml @@ -0,0 +1,38 @@ +settings: + application: + # dev开发环境 test测试环境 prod线上环境 + mode: dev + # 服务器ip,默认使用 0.0.0.0 + host: 0.0.0.0 + # 服务名称 + name: testApp + # 端口号 + port: 8000 # 服务端口号 + readtimeout: 3000 + writertimeout: 2000 + # 数据权限功能开关 + enabledp: false + logger: + # 日志存放路径 + path: temp/logs + # 日志输出,file:文件,default:命令行,其他:命令行 + stdout: '' #控制台日志,启用后,不输出到文件 + # 日志等级, trace, debug, info, warn, error, fatal + level: trace + # 数据库日志开关 + enableddb: false + jwt: + # token 密钥,生产环境时及的修改 + secret: go-admin + # token 过期时间 单位:秒 + timeout: 3600 + database: + # 数据库类型 mysql,sqlite3, postgres + driver: sqlite3 + # 数据库连接sqlite3数据文件的路径 + source: go-admin-db.db + gen: + # 代码生成读取的数据库名称 + dbname: dbname + # 代码生成是使用前端代码存放位置,需要指定到src文件夹,相对路径 + frontpath: ../go-admin-ui/src diff --git a/config/settings.yml b/config/settings.yml new file mode 100644 index 0000000..f02ad69 --- /dev/null +++ b/config/settings.yml @@ -0,0 +1,121 @@ +settings: + application: + # dev开发环境 test测试环境 prod线上环境 + mode: dev + # 服务器ip,默认使用 0.0.0.0 + host: 0.0.0.0 + # 服务名称 + name: testApp + # 端口号 + port: 6789 # 服务端口号 + readtimeout: 1 + writertimeout: 2 + # 数据权限功能开关 + enabledp: false + logger: + # 日志存放路径 + path: temp/logs + # 日志输出,file:文件,default:命令行,其他:命令行 + stdout: '' #控制台日志,启用后,不输出到文件 + # 日志等级, trace, debug, info, warn, error, fatal + level: trace + # 数据库日志开关 + enableddb: false + jwt: + # token 密钥,生产环境时及的修改 + secret: go-admin + # token 过期时间 单位:秒 + timeout: 259200 + database: + # 数据库类型 mysql, sqlite3, postgres, sqlserver + # sqlserver: sqlserver://用户名:密码@地址?database=数据库名 + driver: mysql + # 数据库连接字符串 mysql 缺省信息 charset=utf8&parseTime=True&loc=Local&timeout=1000ms + source: root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms +# databases: +# 'locaohost:8000': +# driver: mysql +# # 数据库连接字符串 mysql 缺省信息 charset=utf8&parseTime=True&loc=Local&timeout=1000ms +# source: user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8&parseTime=True&loc=Local&timeout=1000ms +# registers: +# - sources: +# - user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8&parseTime=True&loc=Local&timeout=1000ms + gen: + # 代码生成读取的数据库名称 + dbname: gp-bian + # 代码生成是使用前端代码存放位置,需要指定到src文件夹,相对路径 + frontpath: ../go-admin-ui/src + extend: # 扩展项使用说明 + demo: + name: data + + # redis 配置 + redis: + addr: "192.168.1.12:6379" + password: "" + db: 0 + # 雪花算法设备id + serviceId: 1 + + #代理 + proxyUrl: "http://127.0.0.1:7890" + + #币安配置 + binanceSet: + spotRestURL: "https://api.binance.com" + futRestURL: "https://fapi.binance.com" + + # 邮箱发送配置 + emailConfig: + mail_smtp_host: "smtp.163.com" + mail_smtp_port: "465" + mail_smtp_user: "daichaodsy@163.com" + mail_smtp_pass: "QCKTZWTREARMGDZN" + mail_verify_type: "ssl" + mail_from: "daichaodsy@163.com" + + # 网站域名 + domain: "https://192.168.123.242" + + #GoToneSms 短信配置 + GoToneSmsConfig: + sender_id: "GoTone SMS" + api_endpoint: "https://gosms.one/api/v3/sms/send" + authorization: "9460|2Vv9ghXT7AynQNG6Ojt4ytEUXH7qiDinclrOBhMZ4ef2be43" + + #UDun 配置 + UDunConfig: + UDunUrl: "https://sig10.udun.io" + UDunMerchantID: "318322" + UDunKey: "e5b52a5da3f112d457b35387f2bf1001" + CurrServerIp: "" + + #coingate 配置 + coingate: + auth: "8hX6L1weBP9jxja5sKaEzyqB7UY2SQUC_JLAWgyM" + endPoint: "https://api-sandbox.coingate.com" + + cache: +# redis: +# addr: 127.0.0.1:6379 +# password: xxxxxx +# db: 2 + # key存在即可 + memory: '' + queue: + memory: + poolSize: 100 +# redis: +# addr: 127.0.0.1:6379 +# password: xxxxxx +# producer: +# streamMaxLength: 100 +# approximateMaxLength: true +# consumer: +# visibilityTimeout: 60 +# bufferSize: 100 +# concurrency: 10 +# blockingTimeout: 5 +# reclaimInterval: 1 + locker: + redis: \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..41d94de --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,19 @@ +version: '3.8' +services: + go-admin-api: + container_name: go-admin + image: go-admin:latest + privileged: true + restart: always + ports: + - 8000:8000 + volumes: + - ./config/:/go-admin-api/config/ + - ./static/:/go-admin-api/static/ + - ./temp/:/go-admin-api/temp/ + networks: + - myweb +networks: + myweb: + driver: bridge + diff --git a/docs/admin/admin_docs.go b/docs/admin/admin_docs.go new file mode 100644 index 0000000..31c596a --- /dev/null +++ b/docs/admin/admin_docs.go @@ -0,0 +1,4373 @@ +// Package admin GENERATED BY SWAG; DO NOT EDIT +// This file was generated by swaggo/swag +package admin + +import "github.com/swaggo/swag" + +const docTemplateadmin = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "license": { + "name": "MIT", + "url": "https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/api/v1/app-config": { + "get": { + "description": "获取系统配置信息,主要注意这里不在验证权限", + "tags": [ + "配置管理" + ], + "summary": "获取系统前台配置信息,主要注意这里不在验证权限", + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + ] + } + } + } + } + }, + "/api/v1/captcha": { + "get": { + "description": "获取验证码", + "tags": [ + "登陆" + ], + "summary": "获取验证码", + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + }, + "id": { + "type": "string" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/v1/db/columns/page": { + "get": { + "description": "数据库表列分页列表 / database table column page list", + "tags": [ + "工具 / 生成工具" + ], + "summary": "分页列表数据 / page list data", + "parameters": [ + { + "type": "string", + "description": "tableName / 数据表名称", + "name": "tableName", + "in": "query" + }, + { + "type": "integer", + "description": "pageSize / 页条数", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "pageIndex / 页码", + "name": "pageIndex", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/db/tables/page": { + "get": { + "description": "数据库表分页列表 / database table page list", + "tags": [ + "工具 / 生成工具" + ], + "summary": "分页列表数据 / page list data", + "parameters": [ + { + "type": "string", + "description": "tableName / 数据表名称", + "name": "tableName", + "in": "query" + }, + { + "type": "integer", + "description": "pageSize / 页条数", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "pageIndex / 页码", + "name": "pageIndex", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/dept": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "分页列表", + "tags": [ + "部门" + ], + "summary": "分页部门列表数据", + "parameters": [ + { + "type": "string", + "description": "deptName", + "name": "deptName", + "in": "query" + }, + { + "type": "string", + "description": "deptId", + "name": "deptId", + "in": "query" + }, + { + "type": "string", + "description": "position", + "name": "position", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "部门" + ], + "summary": "添加部门", + "parameters": [ + { + "description": "data", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysDeptInsertReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": -1, \"message\": \"添加失败\"}", + "schema": { + "type": "string" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除数据", + "tags": [ + "部门" + ], + "summary": "删除部门", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysDeptDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": -1, \"message\": \"删除失败\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/dept/{deptId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "部门" + ], + "summary": "获取部门数据", + "parameters": [ + { + "type": "string", + "description": "deptId", + "name": "deptId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "部门" + ], + "summary": "修改部门", + "parameters": [ + { + "type": "integer", + "description": "id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysDeptUpdateReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": -1, \"message\": \"添加失败\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/dict-data/option-select": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "数据字典根据key获取", + "tags": [ + "字典数据" + ], + "summary": "数据字典根据key获取", + "parameters": [ + { + "type": "integer", + "description": "dictType", + "name": "dictType", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.SysDictDataGetAllResp" + } + } + } + } + ] + } + } + } + } + }, + "/api/v1/dict/data": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "字典数据" + ], + "summary": "字典数据列表", + "parameters": [ + { + "type": "string", + "description": "status", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "dictCode", + "name": "dictCode", + "in": "query" + }, + { + "type": "string", + "description": "dictType", + "name": "dictType", + "in": "query" + }, + { + "type": "integer", + "description": "页条数", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "pageIndex", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "字典数据" + ], + "summary": "添加字典数据", + "parameters": [ + { + "description": "data", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysDictDataInsertReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"添加成功\"}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除数据", + "tags": [ + "字典数据" + ], + "summary": "删除字典数据", + "parameters": [ + { + "description": "body", + "name": "dictCode", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysDictDataDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"删除成功\"}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/dict/data/{dictCode}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "字典数据" + ], + "summary": "通过编码获取字典数据", + "parameters": [ + { + "type": "integer", + "description": "字典编码", + "name": "dictCode", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "字典数据" + ], + "summary": "修改字典数据", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysDictDataUpdateReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"修改成功\"}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/dict/type": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "字典类型" + ], + "summary": "字典类型列表数据", + "parameters": [ + { + "type": "string", + "description": "dictName", + "name": "dictName", + "in": "query" + }, + { + "type": "string", + "description": "dictId", + "name": "dictId", + "in": "query" + }, + { + "type": "string", + "description": "dictType", + "name": "dictType", + "in": "query" + }, + { + "type": "integer", + "description": "页条数", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "pageIndex", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "字典类型" + ], + "summary": "添加字典类型", + "parameters": [ + { + "description": "data", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysDictTypeInsertReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除数据", + "tags": [ + "字典类型" + ], + "summary": "删除字典类型", + "parameters": [ + { + "description": "body", + "name": "dictCode", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysDictTypeDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/dict/type-option-select": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "字典类型" + ], + "summary": "字典类型全部数据 代码生成使用接口", + "parameters": [ + { + "type": "string", + "description": "dictName", + "name": "dictName", + "in": "query" + }, + { + "type": "string", + "description": "dictId", + "name": "dictId", + "in": "query" + }, + { + "type": "string", + "description": "dictType", + "name": "dictType", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/dict/type/{dictId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "字典类型" + ], + "summary": "字典类型通过字典id获取", + "parameters": [ + { + "type": "integer", + "description": "字典类型编码", + "name": "dictId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "字典类型" + ], + "summary": "修改字典类型", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysDictTypeUpdateReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/getinfo": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "个人中心" + ], + "summary": "获取个人信息", + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/login": { + "post": { + "description": "获取token\nLoginHandler can be used by clients to get a jwt token.\nPayload needs to be json in the form of {\"username\": \"USERNAME\", \"password\": \"PASSWORD\"}.\nReply will be of the form {\"token\": \"TOKEN\"}.\ndev mode:It should be noted that all fields cannot be empty, and a value of 0 can be passed in addition to the account password\n注意:开发模式:需要注意全部字段不能为空,账号密码外可以传入0值", + "consumes": [ + "application/json" + ], + "tags": [ + "登陆" + ], + "summary": "登陆", + "parameters": [ + { + "description": "account", + "name": "account", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.Login" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"expire\": \"2019-08-07T12:45:48+08:00\", \"token\": \".eyJleHAiOjE1NjUxNTMxNDgsImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTU2NTE0OTU0OH0.-zvzHvbg0A\" }", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/menu": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "菜单" + ], + "summary": "Menu列表数据", + "parameters": [ + { + "type": "string", + "description": "menuName", + "name": "menuName", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "菜单" + ], + "summary": "创建菜单", + "parameters": [ + { + "description": "data", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysMenuInsertReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除数据", + "tags": [ + "菜单" + ], + "summary": "删除菜单", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysMenuDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/menu/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "菜单" + ], + "summary": "Menu详情数据", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "菜单" + ], + "summary": "修改菜单", + "parameters": [ + { + "type": "integer", + "description": "id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysMenuUpdateReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/menuTreeselect/{roleId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "菜单" + ], + "summary": "角色修改使用的菜单列表", + "parameters": [ + { + "type": "integer", + "description": "roleId", + "name": "roleId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/menurole": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "菜单" + ], + "summary": "根据登录角色名称获取菜单列表数据(左菜单使用)", + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/post": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "岗位" + ], + "summary": "岗位列表数据", + "parameters": [ + { + "type": "string", + "description": "postName", + "name": "postName", + "in": "query" + }, + { + "type": "string", + "description": "postCode", + "name": "postCode", + "in": "query" + }, + { + "type": "string", + "description": "postId", + "name": "postId", + "in": "query" + }, + { + "type": "string", + "description": "status", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "岗位" + ], + "summary": "添加岗位", + "parameters": [ + { + "description": "data", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysPostInsertReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除数据", + "tags": [ + "岗位" + ], + "summary": "删除岗位", + "parameters": [ + { + "description": "请求参数", + "name": "id", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysPostDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/post/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "岗位" + ], + "summary": "修改岗位", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysPostUpdateReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/post/{postId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "岗位" + ], + "summary": "获取岗位信息", + "parameters": [ + { + "type": "integer", + "description": "编码", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/public/uploadFile": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "multipart/form-data" + ], + "tags": [ + "公共接口" + ], + "summary": "上传图片", + "parameters": [ + { + "type": "string", + "description": "type", + "name": "type", + "in": "query", + "required": true + }, + { + "type": "file", + "description": "file", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": -1, \"message\": \"添加失败\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/role": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Get JSON", + "tags": [ + "角色/Role" + ], + "summary": "角色列表数据", + "parameters": [ + { + "type": "string", + "description": "roleName", + "name": "roleName", + "in": "query" + }, + { + "type": "string", + "description": "status", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "roleKey", + "name": "roleKey", + "in": "query" + }, + { + "type": "integer", + "description": "页条数", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "pageIndex", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "角色/Role" + ], + "summary": "创建角色", + "parameters": [ + { + "description": "data", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysRoleInsertReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除数据", + "tags": [ + "角色/Role" + ], + "summary": "删除用户角色", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysRoleDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/role-status/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "角色/Role" + ], + "summary": "更新角色数据权限", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.RoleDataScopeReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/role/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "角色/Role" + ], + "summary": "获取Role数据", + "parameters": [ + { + "type": "string", + "description": "roleId", + "name": "roleId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "角色/Role" + ], + "summary": "修改用户角色", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysRoleUpdateReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/server-monitor": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "系统信息" + ], + "summary": "系统信息", + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/set-config": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "界面操作设置配置值的获取", + "consumes": [ + "application/json" + ], + "tags": [ + "配置管理" + ], + "summary": "获取配置", + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"修改成功\"}", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "界面操作设置配置值", + "consumes": [ + "application/json" + ], + "tags": [ + "配置管理" + ], + "summary": "设置配置", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.GetSetSysConfigReq" + } + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"修改成功\"}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-api": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取接口管理列表", + "tags": [ + "接口管理" + ], + "summary": "获取接口管理列表", + "parameters": [ + { + "type": "string", + "description": "名称", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "标题", + "name": "title", + "in": "query" + }, + { + "type": "string", + "description": "地址", + "name": "path", + "in": "query" + }, + { + "type": "string", + "description": "类型", + "name": "action", + "in": "query" + }, + { + "type": "integer", + "description": "页条数", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "pageIndex", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/response.Page" + }, + { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysApi" + } + } + } + } + ] + } + } + } + ] + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除接口管理", + "tags": [ + "接口管理" + ], + "summary": "删除接口管理", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysApiDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"删除成功\"}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-api/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取接口管理", + "tags": [ + "接口管理" + ], + "summary": "获取接口管理", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.SysApi" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "修改接口管理", + "consumes": [ + "application/json" + ], + "tags": [ + "接口管理" + ], + "summary": "修改接口管理", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysApiUpdateReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"修改成功\"}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-config": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取配置管理列表", + "tags": [ + "配置管理" + ], + "summary": "获取配置管理列表", + "parameters": [ + { + "type": "string", + "description": "名称", + "name": "configName", + "in": "query" + }, + { + "type": "string", + "description": "key", + "name": "configKey", + "in": "query" + }, + { + "type": "string", + "description": "类型", + "name": "configType", + "in": "query" + }, + { + "type": "integer", + "description": "是否前端", + "name": "isFrontend", + "in": "query" + }, + { + "type": "integer", + "description": "页条数", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "pageIndex", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/response.Page" + }, + { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysApi" + } + } + } + } + ] + } + } + } + ] + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "创建配置管理", + "consumes": [ + "application/json" + ], + "tags": [ + "配置管理" + ], + "summary": "创建配置管理", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysConfigControl" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"创建成功\"}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除配置管理", + "tags": [ + "配置管理" + ], + "summary": "删除配置管理", + "parameters": [ + { + "description": "ids", + "name": "ids", + "in": "body", + "schema": { + "type": "array", + "items": { + "type": "integer" + } + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"删除成功\"}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-config/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "根据Key获取SysConfig的Service", + "tags": [ + "配置管理" + ], + "summary": "根据Key获取SysConfig的Service", + "parameters": [ + { + "type": "string", + "description": "configKey", + "name": "configKey", + "in": "path" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/dto.SysConfigByKeyReq" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "修改配置管理", + "consumes": [ + "application/json" + ], + "tags": [ + "配置管理" + ], + "summary": "修改配置管理", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysConfigControl" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"修改成功\"}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-login-log": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "登录日志" + ], + "summary": "登录日志列表", + "parameters": [ + { + "type": "string", + "description": "用户名", + "name": "username", + "in": "query" + }, + { + "type": "string", + "description": "ip地址", + "name": "ipaddr", + "in": "query" + }, + { + "type": "string", + "description": "归属地", + "name": "loginLocation", + "in": "query" + }, + { + "type": "string", + "description": "状态", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "开始时间", + "name": "beginTime", + "in": "query" + }, + { + "type": "string", + "description": "结束时间", + "name": "endTime", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "登录日志删除", + "tags": [ + "登录日志" + ], + "summary": "登录日志删除", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysLoginLogDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-login-log/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "登录日志" + ], + "summary": "登录日志通过id获取", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-opera-log": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "操作日志" + ], + "summary": "操作日志列表", + "parameters": [ + { + "type": "string", + "description": "title", + "name": "title", + "in": "query" + }, + { + "type": "string", + "description": "method", + "name": "method", + "in": "query" + }, + { + "type": "string", + "description": "requestMethod", + "name": "requestMethod", + "in": "query" + }, + { + "type": "string", + "description": "operUrl", + "name": "operUrl", + "in": "query" + }, + { + "type": "string", + "description": "operIp", + "name": "operIp", + "in": "query" + }, + { + "type": "string", + "description": "status", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "beginTime", + "name": "beginTime", + "in": "query" + }, + { + "type": "string", + "description": "endTime", + "name": "endTime", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除数据", + "tags": [ + "操作日志" + ], + "summary": "删除操作日志", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysOperaLogDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-opera-log/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "操作日志" + ], + "summary": "操作日志通过id获取", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-user": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "用户" + ], + "summary": "列表用户信息数据", + "parameters": [ + { + "type": "string", + "description": "username", + "name": "username", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "创建用户", + "parameters": [ + { + "description": "用户数据", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysUserInsertReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-user/{userId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "用户" + ], + "summary": "获取用户", + "parameters": [ + { + "type": "integer", + "description": "用户编码", + "name": "userId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "修改用户数据", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysUserUpdateReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除数据", + "tags": [ + "用户" + ], + "summary": "删除用户数据", + "parameters": [ + { + "type": "integer", + "description": "userId", + "name": "userId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys/tables/info": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "修改表结构", + "consumes": [ + "application/json" + ], + "tags": [ + "工具 / 生成工具" + ], + "summary": "修改表结构", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/tools.SysTables" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": -1, \"message\": \"添加失败\"}", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "添加表结构", + "consumes": [ + "application/json" + ], + "tags": [ + "工具 / 生成工具" + ], + "summary": "添加表结构", + "parameters": [ + { + "type": "string", + "description": "tableName / 数据表名称", + "name": "tables", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": -1, \"message\": \"添加失败\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/sys/tables/info/{tableId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "工具 / 生成工具" + ], + "summary": "获取配置", + "parameters": [ + { + "type": "integer", + "description": "configKey", + "name": "configKey", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "description": "删除表结构", + "tags": [ + "工具 / 生成工具" + ], + "summary": "删除表结构", + "parameters": [ + { + "type": "integer", + "description": "tableId", + "name": "tableId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": -1, \"message\": \"删除失败\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/sys/tables/page": { + "get": { + "description": "生成表分页列表", + "tags": [ + "工具 / 生成工具" + ], + "summary": "分页列表数据", + "parameters": [ + { + "type": "string", + "description": "tableName / 数据表名称", + "name": "tableName", + "in": "query" + }, + { + "type": "integer", + "description": "pageSize / 页条数", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "pageIndex / 页码", + "name": "pageIndex", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/user/avatar": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "multipart/form-data" + ], + "tags": [ + "个人中心" + ], + "summary": "修改头像", + "parameters": [ + { + "type": "file", + "description": "file", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/user/profile": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "个人中心" + ], + "summary": "获取个人中心用户", + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/user/pwd/reset": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "重置用户密码", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ResetSysUserPwdReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/user/pwd/set": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "修改密码", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PassWord" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/user/status": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "修改用户状态", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateSysUserStatusReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/logout": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取token", + "consumes": [ + "application/json" + ], + "summary": "退出登录", + "responses": { + "200": { + "description": "{\"code\": 200, \"msg\": \"成功退出系统\" }", + "schema": { + "type": "string" + } + } + } + } + } + }, + "definitions": { + "dto.GetSetSysConfigReq": { + "type": "object", + "properties": { + "configKey": { + "type": "string" + }, + "configValue": { + "type": "string" + } + } + }, + "dto.PassWord": { + "type": "object", + "properties": { + "newPassword": { + "type": "string" + }, + "oldPassword": { + "type": "string" + } + } + }, + "dto.ResetSysUserPwdReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "password": { + "type": "string" + }, + "updateBy": { + "type": "integer" + }, + "userId": { + "description": "用户ID", + "type": "integer" + } + } + }, + "dto.RoleDataScopeReq": { + "type": "object", + "required": [ + "dataScope", + "roleId" + ], + "properties": { + "dataScope": { + "type": "string" + }, + "deptIds": { + "type": "array", + "items": { + "type": "integer" + } + }, + "roleId": { + "type": "integer" + } + } + }, + "dto.SysApiDeleteReq": { + "type": "object", + "properties": { + "ids": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "dto.SysApiUpdateReq": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "handle": { + "type": "string" + }, + "id": { + "description": "编码", + "type": "integer" + }, + "path": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysConfigByKeyReq": { + "type": "object", + "properties": { + "configKey": { + "type": "string" + } + } + }, + "dto.SysConfigControl": { + "type": "object", + "properties": { + "configKey": { + "type": "string" + }, + "configName": { + "type": "string" + }, + "configType": { + "type": "string" + }, + "configValue": { + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "id": { + "description": "编码", + "type": "integer" + }, + "isFrontend": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysDeptDeleteReq": { + "type": "object", + "properties": { + "ids": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "dto.SysDeptInsertReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "deptId": { + "description": "编码", + "type": "integer" + }, + "deptName": { + "description": "部门名称", + "type": "string" + }, + "deptPath": { + "description": "路径", + "type": "string" + }, + "email": { + "description": "邮箱", + "type": "string" + }, + "leader": { + "description": "负责人", + "type": "string" + }, + "parentId": { + "description": "上级部门", + "type": "integer" + }, + "phone": { + "description": "手机", + "type": "string" + }, + "sort": { + "description": "排序", + "type": "integer" + }, + "status": { + "description": "状态", + "type": "integer" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysDeptUpdateReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "deptId": { + "description": "编码", + "type": "integer" + }, + "deptName": { + "description": "部门名称", + "type": "string" + }, + "deptPath": { + "description": "路径", + "type": "string" + }, + "email": { + "description": "邮箱", + "type": "string" + }, + "leader": { + "description": "负责人", + "type": "string" + }, + "parentId": { + "description": "上级部门", + "type": "integer" + }, + "phone": { + "description": "手机", + "type": "string" + }, + "sort": { + "description": "排序", + "type": "integer" + }, + "status": { + "description": "状态", + "type": "integer" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysDictDataDeleteReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysDictDataGetAllResp": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "dto.SysDictDataInsertReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "cssClass": { + "type": "string" + }, + "default": { + "type": "string" + }, + "dictLabel": { + "type": "string" + }, + "dictSort": { + "type": "integer" + }, + "dictType": { + "type": "string" + }, + "dictValue": { + "type": "string" + }, + "isDefault": { + "type": "string" + }, + "listClass": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "status": { + "type": "integer" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysDictDataUpdateReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "cssClass": { + "type": "string" + }, + "default": { + "type": "string" + }, + "dictLabel": { + "type": "string" + }, + "dictSort": { + "type": "integer" + }, + "dictType": { + "type": "string" + }, + "dictValue": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isDefault": { + "type": "string" + }, + "listClass": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "status": { + "type": "integer" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysDictTypeDeleteReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysDictTypeInsertReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "dictName": { + "type": "string" + }, + "dictType": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "remark": { + "type": "string" + }, + "status": { + "type": "integer" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysDictTypeUpdateReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "dictName": { + "type": "string" + }, + "dictType": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "remark": { + "type": "string" + }, + "status": { + "type": "integer" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysLoginLogDeleteReq": { + "type": "object", + "properties": { + "ids": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "dto.SysMenuDeleteReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysMenuInsertReq": { + "type": "object", + "properties": { + "action": { + "description": "请求方式", + "type": "string" + }, + "apis": { + "type": "array", + "items": { + "type": "integer" + } + }, + "breadcrumb": { + "description": "是否面包屑", + "type": "string" + }, + "component": { + "description": "组件", + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "icon": { + "description": "图标", + "type": "string" + }, + "isFrame": { + "description": "是否frame", + "type": "string" + }, + "menuId": { + "description": "编码", + "type": "integer" + }, + "menuName": { + "description": "菜单name", + "type": "string" + }, + "menuType": { + "description": "菜单类型", + "type": "string" + }, + "noCache": { + "description": "是否缓存", + "type": "boolean" + }, + "parentId": { + "description": "上级菜单", + "type": "integer" + }, + "path": { + "description": "路径", + "type": "string" + }, + "paths": { + "description": "id路径", + "type": "string" + }, + "permission": { + "description": "权限编码", + "type": "string" + }, + "sort": { + "description": "排序", + "type": "integer" + }, + "sysApi": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysApi" + } + }, + "title": { + "description": "显示名称", + "type": "string" + }, + "updateBy": { + "type": "integer" + }, + "visible": { + "description": "是否显示", + "type": "string" + } + } + }, + "dto.SysMenuUpdateReq": { + "type": "object", + "properties": { + "action": { + "description": "请求方式", + "type": "string" + }, + "apis": { + "type": "array", + "items": { + "type": "integer" + } + }, + "breadcrumb": { + "description": "是否面包屑", + "type": "string" + }, + "component": { + "description": "组件", + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "icon": { + "description": "图标", + "type": "string" + }, + "isFrame": { + "description": "是否frame", + "type": "string" + }, + "menuId": { + "description": "编码", + "type": "integer" + }, + "menuName": { + "description": "菜单name", + "type": "string" + }, + "menuType": { + "description": "菜单类型", + "type": "string" + }, + "noCache": { + "description": "是否缓存", + "type": "boolean" + }, + "parentId": { + "description": "上级菜单", + "type": "integer" + }, + "path": { + "description": "路径", + "type": "string" + }, + "paths": { + "description": "id路径", + "type": "string" + }, + "permission": { + "description": "权限编码", + "type": "string" + }, + "sort": { + "description": "排序", + "type": "integer" + }, + "sysApi": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysApi" + } + }, + "title": { + "description": "显示名称", + "type": "string" + }, + "updateBy": { + "type": "integer" + }, + "visible": { + "description": "是否显示", + "type": "string" + } + } + }, + "dto.SysOperaLogDeleteReq": { + "type": "object", + "properties": { + "ids": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "dto.SysPostDeleteReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysPostInsertReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "postCode": { + "type": "string" + }, + "postId": { + "type": "integer" + }, + "postName": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "sort": { + "type": "integer" + }, + "status": { + "type": "integer" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysPostUpdateReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "postCode": { + "type": "string" + }, + "postId": { + "type": "integer" + }, + "postName": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "sort": { + "type": "integer" + }, + "status": { + "type": "integer" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysRoleDeleteReq": { + "type": "object", + "properties": { + "ids": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "dto.SysRoleInsertReq": { + "type": "object", + "properties": { + "admin": { + "type": "boolean" + }, + "createBy": { + "type": "integer" + }, + "dataScope": { + "type": "string" + }, + "deptIds": { + "type": "array", + "items": { + "type": "integer" + } + }, + "flag": { + "description": "标记", + "type": "string" + }, + "menuIds": { + "type": "array", + "items": { + "type": "integer" + } + }, + "remark": { + "description": "备注", + "type": "string" + }, + "roleId": { + "description": "角色编码", + "type": "integer" + }, + "roleKey": { + "description": "角色代码", + "type": "string" + }, + "roleName": { + "description": "角色名称", + "type": "string" + }, + "roleSort": { + "description": "角色排序", + "type": "integer" + }, + "status": { + "description": "状态 1禁用 2正常", + "type": "string" + }, + "sysDept": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysDept" + } + }, + "sysMenu": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysMenu" + } + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysRoleUpdateReq": { + "type": "object", + "properties": { + "admin": { + "type": "boolean" + }, + "createBy": { + "type": "integer" + }, + "dataScope": { + "type": "string" + }, + "deptIds": { + "type": "array", + "items": { + "type": "integer" + } + }, + "flag": { + "description": "标记", + "type": "string" + }, + "menuIds": { + "type": "array", + "items": { + "type": "integer" + } + }, + "remark": { + "description": "备注", + "type": "string" + }, + "roleId": { + "description": "角色编码", + "type": "integer" + }, + "roleKey": { + "description": "角色代码", + "type": "string" + }, + "roleName": { + "description": "角色名称", + "type": "string" + }, + "roleSort": { + "description": "角色排序", + "type": "integer" + }, + "status": { + "description": "状态", + "type": "string" + }, + "sysDept": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysDept" + } + }, + "sysMenu": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysMenu" + } + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysUserInsertReq": { + "type": "object", + "properties": { + "avatar": { + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "deptId": { + "type": "integer" + }, + "email": { + "type": "string" + }, + "nickName": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "postId": { + "type": "integer" + }, + "remark": { + "type": "string" + }, + "roleId": { + "type": "integer" + }, + "sex": { + "type": "string" + }, + "status": { + "type": "string", + "default": "1" + }, + "updateBy": { + "type": "integer" + }, + "userId": { + "description": "用户ID", + "type": "integer" + }, + "username": { + "type": "string" + } + } + }, + "dto.SysUserUpdateReq": { + "type": "object", + "properties": { + "avatar": { + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "deptId": { + "type": "integer" + }, + "email": { + "type": "string" + }, + "nickName": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "postId": { + "type": "integer" + }, + "remark": { + "type": "string" + }, + "roleId": { + "type": "integer" + }, + "sex": { + "type": "string" + }, + "status": { + "type": "string", + "default": "1" + }, + "updateBy": { + "type": "integer" + }, + "userId": { + "description": "用户ID", + "type": "integer" + }, + "username": { + "type": "string" + } + } + }, + "dto.UpdateStatusReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "roleId": { + "description": "角色编码", + "type": "integer" + }, + "status": { + "description": "状态", + "type": "string" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.UpdateSysUserStatusReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updateBy": { + "type": "integer" + }, + "userId": { + "description": "用户ID", + "type": "integer" + } + } + }, + "handler.Login": { + "type": "object", + "required": [ + "code", + "password", + "username", + "uuid" + ], + "properties": { + "code": { + "type": "string" + }, + "password": { + "type": "string" + }, + "username": { + "type": "string" + }, + "uuid": { + "type": "string" + } + } + }, + "models.SysApi": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "handle": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "path": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updateBy": { + "type": "integer" + }, + "updatedAt": { + "type": "string" + } + } + }, + "models.SysConfig": { + "type": "object", + "properties": { + "configKey": { + "type": "string" + }, + "configName": { + "type": "string" + }, + "configType": { + "type": "string" + }, + "configValue": { + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isFrontend": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "updateBy": { + "type": "integer" + }, + "updatedAt": { + "type": "string" + } + } + }, + "models.SysDept": { + "type": "object", + "properties": { + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysDept" + } + }, + "createBy": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "dataScope": { + "type": "string" + }, + "deptId": { + "description": "部门编码", + "type": "integer" + }, + "deptName": { + "description": "部门名称", + "type": "string" + }, + "deptPath": { + "type": "string" + }, + "email": { + "description": "邮箱", + "type": "string" + }, + "leader": { + "description": "负责人", + "type": "string" + }, + "params": { + "type": "string" + }, + "parentId": { + "description": "上级部门", + "type": "integer" + }, + "phone": { + "description": "手机", + "type": "string" + }, + "sort": { + "description": "排序", + "type": "integer" + }, + "status": { + "description": "状态", + "type": "integer" + }, + "updateBy": { + "type": "integer" + }, + "updatedAt": { + "type": "string" + } + } + }, + "models.SysMenu": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "apis": { + "type": "array", + "items": { + "type": "integer" + } + }, + "breadcrumb": { + "type": "string" + }, + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysMenu" + } + }, + "component": { + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "dataScope": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "isFrame": { + "type": "string" + }, + "is_select": { + "type": "boolean" + }, + "menuId": { + "type": "integer" + }, + "menuName": { + "type": "string" + }, + "menuType": { + "type": "string" + }, + "noCache": { + "type": "boolean" + }, + "params": { + "type": "string" + }, + "parentId": { + "type": "integer" + }, + "path": { + "type": "string" + }, + "paths": { + "type": "string" + }, + "permission": { + "type": "string" + }, + "roleId": { + "type": "integer" + }, + "sort": { + "type": "integer" + }, + "sysApi": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysApi" + } + }, + "title": { + "type": "string" + }, + "updateBy": { + "type": "integer" + }, + "updatedAt": { + "type": "string" + }, + "visible": { + "type": "string" + } + } + }, + "response.Page": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "pageIndex": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + } + }, + "response.Response": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "msg": { + "type": "string" + }, + "requestId": { + "description": "数据集", + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "tools.Params": { + "type": "object", + "properties": { + "treeCode": { + "type": "string" + }, + "treeName": { + "type": "string" + }, + "treeParentCode": { + "type": "string" + } + } + }, + "tools.SysColumns": { + "type": "object", + "properties": { + "columnComment": { + "type": "string" + }, + "columnId": { + "type": "integer" + }, + "columnName": { + "type": "string" + }, + "columnType": { + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "deletedAt": { + "type": "string" + }, + "dictType": { + "type": "string" + }, + "edit": { + "type": "boolean" + }, + "fkCol": { + "type": "array", + "items": { + "$ref": "#/definitions/tools.SysColumns" + } + }, + "fkLabelId": { + "type": "string" + }, + "fkLabelName": { + "type": "string" + }, + "fkTableName": { + "type": "string" + }, + "fkTableNameClass": { + "type": "string" + }, + "fkTableNamePackage": { + "type": "string" + }, + "goField": { + "type": "string" + }, + "goType": { + "type": "string" + }, + "htmlType": { + "type": "string" + }, + "increment": { + "type": "boolean" + }, + "insert": { + "type": "boolean" + }, + "isEdit": { + "type": "string" + }, + "isIncrement": { + "type": "string" + }, + "isInsert": { + "type": "string" + }, + "isList": { + "type": "string" + }, + "isPk": { + "type": "string" + }, + "isQuery": { + "type": "string" + }, + "isRequired": { + "type": "string" + }, + "jsonField": { + "type": "string" + }, + "list": { + "type": "string" + }, + "pk": { + "type": "boolean" + }, + "query": { + "type": "boolean" + }, + "queryType": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "required": { + "type": "boolean" + }, + "sort": { + "type": "integer" + }, + "superColumn": { + "type": "boolean" + }, + "tableId": { + "type": "integer" + }, + "updateBy": { + "type": "integer" + }, + "updatedAt": { + "type": "string" + }, + "usableColumn": { + "type": "boolean" + } + } + }, + "tools.SysTables": { + "type": "object", + "properties": { + "businessName": { + "type": "string" + }, + "className": { + "description": "类名", + "type": "string" + }, + "columns": { + "type": "array", + "items": { + "$ref": "#/definitions/tools.SysColumns" + } + }, + "createBy": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "crud": { + "type": "boolean" + }, + "dataScope": { + "type": "string" + }, + "deletedAt": { + "type": "string" + }, + "functionAuthor": { + "description": "功能作者", + "type": "string" + }, + "functionName": { + "description": "功能名称", + "type": "string" + }, + "isActions": { + "type": "integer" + }, + "isAuth": { + "type": "integer" + }, + "isDataScope": { + "type": "integer" + }, + "isLogicalDelete": { + "type": "string" + }, + "logicalDelete": { + "type": "boolean" + }, + "logicalDeleteColumn": { + "type": "string" + }, + "moduleFrontName": { + "description": "前端文件名", + "type": "string" + }, + "moduleName": { + "description": "go文件名", + "type": "string" + }, + "options": { + "type": "string" + }, + "packageName": { + "description": "包名", + "type": "string" + }, + "params": { + "$ref": "#/definitions/tools.Params" + }, + "pkColumn": { + "type": "string" + }, + "pkGoField": { + "type": "string" + }, + "pkJsonField": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "tableComment": { + "description": "表备注", + "type": "string" + }, + "tableId": { + "description": "表编码", + "type": "integer" + }, + "tableName": { + "description": "表名称", + "type": "string" + }, + "tplCategory": { + "type": "string" + }, + "tree": { + "type": "boolean" + }, + "treeCode": { + "type": "string" + }, + "treeName": { + "type": "string" + }, + "treeParentCode": { + "type": "string" + }, + "updateBy": { + "type": "integer" + }, + "updatedAt": { + "type": "string" + } + } + } + }, + "securityDefinitions": { + "Bearer": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +}` + +// SwaggerInfoadmin holds exported Swagger Info so clients can modify it +var SwaggerInfoadmin = &swag.Spec{ + Version: "2.0.0", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "go-admin API", + Description: "基于Gin + Vue + Element UI的前后端分离权限管理系统的接口文档\n添加qq群: 521386980 进入技术交流群 请先star,谢谢!", + InfoInstanceName: "admin", + SwaggerTemplate: docTemplateadmin, +} + +func init() { + swag.Register(SwaggerInfoadmin.InstanceName(), SwaggerInfoadmin) +} diff --git a/docs/admin/admin_swagger.json b/docs/admin/admin_swagger.json new file mode 100644 index 0000000..d3c32a1 --- /dev/null +++ b/docs/admin/admin_swagger.json @@ -0,0 +1,4348 @@ +{ + "swagger": "2.0", + "info": { + "description": "基于Gin + Vue + Element UI的前后端分离权限管理系统的接口文档\n添加qq群: 521386980 进入技术交流群 请先star,谢谢!", + "title": "go-admin API", + "contact": {}, + "license": { + "name": "MIT", + "url": "https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md" + }, + "version": "2.0.0" + }, + "paths": { + "/api/v1/app-config": { + "get": { + "description": "获取系统配置信息,主要注意这里不在验证权限", + "tags": [ + "配置管理" + ], + "summary": "获取系统前台配置信息,主要注意这里不在验证权限", + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + ] + } + } + } + } + }, + "/api/v1/captcha": { + "get": { + "description": "获取验证码", + "tags": [ + "登陆" + ], + "summary": "获取验证码", + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + }, + "id": { + "type": "string" + }, + "msg": { + "type": "string" + } + } + } + ] + } + } + } + } + }, + "/api/v1/db/columns/page": { + "get": { + "description": "数据库表列分页列表 / database table column page list", + "tags": [ + "工具 / 生成工具" + ], + "summary": "分页列表数据 / page list data", + "parameters": [ + { + "type": "string", + "description": "tableName / 数据表名称", + "name": "tableName", + "in": "query" + }, + { + "type": "integer", + "description": "pageSize / 页条数", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "pageIndex / 页码", + "name": "pageIndex", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/db/tables/page": { + "get": { + "description": "数据库表分页列表 / database table page list", + "tags": [ + "工具 / 生成工具" + ], + "summary": "分页列表数据 / page list data", + "parameters": [ + { + "type": "string", + "description": "tableName / 数据表名称", + "name": "tableName", + "in": "query" + }, + { + "type": "integer", + "description": "pageSize / 页条数", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "pageIndex / 页码", + "name": "pageIndex", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/dept": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "分页列表", + "tags": [ + "部门" + ], + "summary": "分页部门列表数据", + "parameters": [ + { + "type": "string", + "description": "deptName", + "name": "deptName", + "in": "query" + }, + { + "type": "string", + "description": "deptId", + "name": "deptId", + "in": "query" + }, + { + "type": "string", + "description": "position", + "name": "position", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "部门" + ], + "summary": "添加部门", + "parameters": [ + { + "description": "data", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysDeptInsertReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": -1, \"message\": \"添加失败\"}", + "schema": { + "type": "string" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除数据", + "tags": [ + "部门" + ], + "summary": "删除部门", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysDeptDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": -1, \"message\": \"删除失败\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/dept/{deptId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "部门" + ], + "summary": "获取部门数据", + "parameters": [ + { + "type": "string", + "description": "deptId", + "name": "deptId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "部门" + ], + "summary": "修改部门", + "parameters": [ + { + "type": "integer", + "description": "id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysDeptUpdateReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": -1, \"message\": \"添加失败\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/dict-data/option-select": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "数据字典根据key获取", + "tags": [ + "字典数据" + ], + "summary": "数据字典根据key获取", + "parameters": [ + { + "type": "integer", + "description": "dictType", + "name": "dictType", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.SysDictDataGetAllResp" + } + } + } + } + ] + } + } + } + } + }, + "/api/v1/dict/data": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "字典数据" + ], + "summary": "字典数据列表", + "parameters": [ + { + "type": "string", + "description": "status", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "dictCode", + "name": "dictCode", + "in": "query" + }, + { + "type": "string", + "description": "dictType", + "name": "dictType", + "in": "query" + }, + { + "type": "integer", + "description": "页条数", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "pageIndex", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "字典数据" + ], + "summary": "添加字典数据", + "parameters": [ + { + "description": "data", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysDictDataInsertReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"添加成功\"}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除数据", + "tags": [ + "字典数据" + ], + "summary": "删除字典数据", + "parameters": [ + { + "description": "body", + "name": "dictCode", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysDictDataDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"删除成功\"}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/dict/data/{dictCode}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "字典数据" + ], + "summary": "通过编码获取字典数据", + "parameters": [ + { + "type": "integer", + "description": "字典编码", + "name": "dictCode", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "字典数据" + ], + "summary": "修改字典数据", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysDictDataUpdateReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"修改成功\"}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/dict/type": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "字典类型" + ], + "summary": "字典类型列表数据", + "parameters": [ + { + "type": "string", + "description": "dictName", + "name": "dictName", + "in": "query" + }, + { + "type": "string", + "description": "dictId", + "name": "dictId", + "in": "query" + }, + { + "type": "string", + "description": "dictType", + "name": "dictType", + "in": "query" + }, + { + "type": "integer", + "description": "页条数", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "pageIndex", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "字典类型" + ], + "summary": "添加字典类型", + "parameters": [ + { + "description": "data", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysDictTypeInsertReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除数据", + "tags": [ + "字典类型" + ], + "summary": "删除字典类型", + "parameters": [ + { + "description": "body", + "name": "dictCode", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysDictTypeDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/dict/type-option-select": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "字典类型" + ], + "summary": "字典类型全部数据 代码生成使用接口", + "parameters": [ + { + "type": "string", + "description": "dictName", + "name": "dictName", + "in": "query" + }, + { + "type": "string", + "description": "dictId", + "name": "dictId", + "in": "query" + }, + { + "type": "string", + "description": "dictType", + "name": "dictType", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/dict/type/{dictId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "字典类型" + ], + "summary": "字典类型通过字典id获取", + "parameters": [ + { + "type": "integer", + "description": "字典类型编码", + "name": "dictId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "字典类型" + ], + "summary": "修改字典类型", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysDictTypeUpdateReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/getinfo": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "个人中心" + ], + "summary": "获取个人信息", + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/login": { + "post": { + "description": "获取token\nLoginHandler can be used by clients to get a jwt token.\nPayload needs to be json in the form of {\"username\": \"USERNAME\", \"password\": \"PASSWORD\"}.\nReply will be of the form {\"token\": \"TOKEN\"}.\ndev mode:It should be noted that all fields cannot be empty, and a value of 0 can be passed in addition to the account password\n注意:开发模式:需要注意全部字段不能为空,账号密码外可以传入0值", + "consumes": [ + "application/json" + ], + "tags": [ + "登陆" + ], + "summary": "登陆", + "parameters": [ + { + "description": "account", + "name": "account", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.Login" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"expire\": \"2019-08-07T12:45:48+08:00\", \"token\": \".eyJleHAiOjE1NjUxNTMxNDgsImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTU2NTE0OTU0OH0.-zvzHvbg0A\" }", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/menu": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "菜单" + ], + "summary": "Menu列表数据", + "parameters": [ + { + "type": "string", + "description": "menuName", + "name": "menuName", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "菜单" + ], + "summary": "创建菜单", + "parameters": [ + { + "description": "data", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysMenuInsertReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除数据", + "tags": [ + "菜单" + ], + "summary": "删除菜单", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysMenuDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/menu/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "菜单" + ], + "summary": "Menu详情数据", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "菜单" + ], + "summary": "修改菜单", + "parameters": [ + { + "type": "integer", + "description": "id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysMenuUpdateReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/menuTreeselect/{roleId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "菜单" + ], + "summary": "角色修改使用的菜单列表", + "parameters": [ + { + "type": "integer", + "description": "roleId", + "name": "roleId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/menurole": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "菜单" + ], + "summary": "根据登录角色名称获取菜单列表数据(左菜单使用)", + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/post": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "岗位" + ], + "summary": "岗位列表数据", + "parameters": [ + { + "type": "string", + "description": "postName", + "name": "postName", + "in": "query" + }, + { + "type": "string", + "description": "postCode", + "name": "postCode", + "in": "query" + }, + { + "type": "string", + "description": "postId", + "name": "postId", + "in": "query" + }, + { + "type": "string", + "description": "status", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "岗位" + ], + "summary": "添加岗位", + "parameters": [ + { + "description": "data", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysPostInsertReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除数据", + "tags": [ + "岗位" + ], + "summary": "删除岗位", + "parameters": [ + { + "description": "请求参数", + "name": "id", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysPostDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/post/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "岗位" + ], + "summary": "修改岗位", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysPostUpdateReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/post/{postId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "岗位" + ], + "summary": "获取岗位信息", + "parameters": [ + { + "type": "integer", + "description": "编码", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/public/uploadFile": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "multipart/form-data" + ], + "tags": [ + "公共接口" + ], + "summary": "上传图片", + "parameters": [ + { + "type": "string", + "description": "type", + "name": "type", + "in": "query", + "required": true + }, + { + "type": "file", + "description": "file", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": -1, \"message\": \"添加失败\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/role": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Get JSON", + "tags": [ + "角色/Role" + ], + "summary": "角色列表数据", + "parameters": [ + { + "type": "string", + "description": "roleName", + "name": "roleName", + "in": "query" + }, + { + "type": "string", + "description": "status", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "roleKey", + "name": "roleKey", + "in": "query" + }, + { + "type": "integer", + "description": "页条数", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "pageIndex", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "角色/Role" + ], + "summary": "创建角色", + "parameters": [ + { + "description": "data", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysRoleInsertReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除数据", + "tags": [ + "角色/Role" + ], + "summary": "删除用户角色", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysRoleDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/role-status/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "角色/Role" + ], + "summary": "更新角色数据权限", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.RoleDataScopeReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/role/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "角色/Role" + ], + "summary": "获取Role数据", + "parameters": [ + { + "type": "string", + "description": "roleId", + "name": "roleId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "角色/Role" + ], + "summary": "修改用户角色", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysRoleUpdateReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/server-monitor": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "系统信息" + ], + "summary": "系统信息", + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/set-config": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "界面操作设置配置值的获取", + "consumes": [ + "application/json" + ], + "tags": [ + "配置管理" + ], + "summary": "获取配置", + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"修改成功\"}", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "additionalProperties": true + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "界面操作设置配置值", + "consumes": [ + "application/json" + ], + "tags": [ + "配置管理" + ], + "summary": "设置配置", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.GetSetSysConfigReq" + } + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"修改成功\"}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-api": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取接口管理列表", + "tags": [ + "接口管理" + ], + "summary": "获取接口管理列表", + "parameters": [ + { + "type": "string", + "description": "名称", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "标题", + "name": "title", + "in": "query" + }, + { + "type": "string", + "description": "地址", + "name": "path", + "in": "query" + }, + { + "type": "string", + "description": "类型", + "name": "action", + "in": "query" + }, + { + "type": "integer", + "description": "页条数", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "pageIndex", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/response.Page" + }, + { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysApi" + } + } + } + } + ] + } + } + } + ] + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除接口管理", + "tags": [ + "接口管理" + ], + "summary": "删除接口管理", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysApiDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"删除成功\"}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-api/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取接口管理", + "tags": [ + "接口管理" + ], + "summary": "获取接口管理", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.SysApi" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "修改接口管理", + "consumes": [ + "application/json" + ], + "tags": [ + "接口管理" + ], + "summary": "修改接口管理", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysApiUpdateReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"修改成功\"}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-config": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取配置管理列表", + "tags": [ + "配置管理" + ], + "summary": "获取配置管理列表", + "parameters": [ + { + "type": "string", + "description": "名称", + "name": "configName", + "in": "query" + }, + { + "type": "string", + "description": "key", + "name": "configKey", + "in": "query" + }, + { + "type": "string", + "description": "类型", + "name": "configType", + "in": "query" + }, + { + "type": "integer", + "description": "是否前端", + "name": "isFrontend", + "in": "query" + }, + { + "type": "integer", + "description": "页条数", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "页码", + "name": "pageIndex", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/definitions/response.Page" + }, + { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysApi" + } + } + } + } + ] + } + } + } + ] + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "创建配置管理", + "consumes": [ + "application/json" + ], + "tags": [ + "配置管理" + ], + "summary": "创建配置管理", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysConfigControl" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"创建成功\"}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除配置管理", + "tags": [ + "配置管理" + ], + "summary": "删除配置管理", + "parameters": [ + { + "description": "ids", + "name": "ids", + "in": "body", + "schema": { + "type": "array", + "items": { + "type": "integer" + } + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"删除成功\"}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-config/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "根据Key获取SysConfig的Service", + "tags": [ + "配置管理" + ], + "summary": "根据Key获取SysConfig的Service", + "parameters": [ + { + "type": "string", + "description": "configKey", + "name": "configKey", + "in": "path" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/dto.SysConfigByKeyReq" + } + } + } + ] + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "修改配置管理", + "consumes": [ + "application/json" + ], + "tags": [ + "配置管理" + ], + "summary": "修改配置管理", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysConfigControl" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"message\": \"修改成功\"}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-login-log": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "登录日志" + ], + "summary": "登录日志列表", + "parameters": [ + { + "type": "string", + "description": "用户名", + "name": "username", + "in": "query" + }, + { + "type": "string", + "description": "ip地址", + "name": "ipaddr", + "in": "query" + }, + { + "type": "string", + "description": "归属地", + "name": "loginLocation", + "in": "query" + }, + { + "type": "string", + "description": "状态", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "开始时间", + "name": "beginTime", + "in": "query" + }, + { + "type": "string", + "description": "结束时间", + "name": "endTime", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "登录日志删除", + "tags": [ + "登录日志" + ], + "summary": "登录日志删除", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysLoginLogDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-login-log/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "登录日志" + ], + "summary": "登录日志通过id获取", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-opera-log": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "操作日志" + ], + "summary": "操作日志列表", + "parameters": [ + { + "type": "string", + "description": "title", + "name": "title", + "in": "query" + }, + { + "type": "string", + "description": "method", + "name": "method", + "in": "query" + }, + { + "type": "string", + "description": "requestMethod", + "name": "requestMethod", + "in": "query" + }, + { + "type": "string", + "description": "operUrl", + "name": "operUrl", + "in": "query" + }, + { + "type": "string", + "description": "operIp", + "name": "operIp", + "in": "query" + }, + { + "type": "string", + "description": "status", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "beginTime", + "name": "beginTime", + "in": "query" + }, + { + "type": "string", + "description": "endTime", + "name": "endTime", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除数据", + "tags": [ + "操作日志" + ], + "summary": "删除操作日志", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysOperaLogDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-opera-log/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "操作日志" + ], + "summary": "操作日志通过id获取", + "parameters": [ + { + "type": "string", + "description": "id", + "name": "id", + "in": "path" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-user": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "用户" + ], + "summary": "列表用户信息数据", + "parameters": [ + { + "type": "string", + "description": "username", + "name": "username", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "创建用户", + "parameters": [ + { + "description": "用户数据", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysUserInsertReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys-user/{userId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "用户" + ], + "summary": "获取用户", + "parameters": [ + { + "type": "integer", + "description": "用户编码", + "name": "userId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "修改用户数据", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SysUserUpdateReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "删除数据", + "tags": [ + "用户" + ], + "summary": "删除用户数据", + "parameters": [ + { + "type": "integer", + "description": "userId", + "name": "userId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/sys/tables/info": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "修改表结构", + "consumes": [ + "application/json" + ], + "tags": [ + "工具 / 生成工具" + ], + "summary": "修改表结构", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/tools.SysTables" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": -1, \"message\": \"添加失败\"}", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "添加表结构", + "consumes": [ + "application/json" + ], + "tags": [ + "工具 / 生成工具" + ], + "summary": "添加表结构", + "parameters": [ + { + "type": "string", + "description": "tableName / 数据表名称", + "name": "tables", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": -1, \"message\": \"添加失败\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/sys/tables/info/{tableId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "工具 / 生成工具" + ], + "summary": "获取配置", + "parameters": [ + { + "type": "integer", + "description": "configKey", + "name": "configKey", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "description": "删除表结构", + "tags": [ + "工具 / 生成工具" + ], + "summary": "删除表结构", + "parameters": [ + { + "type": "integer", + "description": "tableId", + "name": "tableId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": -1, \"message\": \"删除失败\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/sys/tables/page": { + "get": { + "description": "生成表分页列表", + "tags": [ + "工具 / 生成工具" + ], + "summary": "分页列表数据", + "parameters": [ + { + "type": "string", + "description": "tableName / 数据表名称", + "name": "tableName", + "in": "query" + }, + { + "type": "integer", + "description": "pageSize / 页条数", + "name": "pageSize", + "in": "query" + }, + { + "type": "integer", + "description": "pageIndex / 页码", + "name": "pageIndex", + "in": "query" + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/user/avatar": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "multipart/form-data" + ], + "tags": [ + "个人中心" + ], + "summary": "修改头像", + "parameters": [ + { + "type": "file", + "description": "file", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/user/profile": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "tags": [ + "个人中心" + ], + "summary": "获取个人中心用户", + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/user/pwd/reset": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "重置用户密码", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ResetSysUserPwdReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/user/pwd/set": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "修改密码", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PassWord" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/api/v1/user/status": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取JSON", + "consumes": [ + "application/json" + ], + "tags": [ + "用户" + ], + "summary": "修改用户状态", + "parameters": [ + { + "description": "body", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateSysUserStatusReq" + } + } + ], + "responses": { + "200": { + "description": "{\"code\": 200, \"data\": [...]}", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/logout": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "获取token", + "consumes": [ + "application/json" + ], + "summary": "退出登录", + "responses": { + "200": { + "description": "{\"code\": 200, \"msg\": \"成功退出系统\" }", + "schema": { + "type": "string" + } + } + } + } + } + }, + "definitions": { + "dto.GetSetSysConfigReq": { + "type": "object", + "properties": { + "configKey": { + "type": "string" + }, + "configValue": { + "type": "string" + } + } + }, + "dto.PassWord": { + "type": "object", + "properties": { + "newPassword": { + "type": "string" + }, + "oldPassword": { + "type": "string" + } + } + }, + "dto.ResetSysUserPwdReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "password": { + "type": "string" + }, + "updateBy": { + "type": "integer" + }, + "userId": { + "description": "用户ID", + "type": "integer" + } + } + }, + "dto.RoleDataScopeReq": { + "type": "object", + "required": [ + "dataScope", + "roleId" + ], + "properties": { + "dataScope": { + "type": "string" + }, + "deptIds": { + "type": "array", + "items": { + "type": "integer" + } + }, + "roleId": { + "type": "integer" + } + } + }, + "dto.SysApiDeleteReq": { + "type": "object", + "properties": { + "ids": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "dto.SysApiUpdateReq": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "handle": { + "type": "string" + }, + "id": { + "description": "编码", + "type": "integer" + }, + "path": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysConfigByKeyReq": { + "type": "object", + "properties": { + "configKey": { + "type": "string" + } + } + }, + "dto.SysConfigControl": { + "type": "object", + "properties": { + "configKey": { + "type": "string" + }, + "configName": { + "type": "string" + }, + "configType": { + "type": "string" + }, + "configValue": { + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "id": { + "description": "编码", + "type": "integer" + }, + "isFrontend": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysDeptDeleteReq": { + "type": "object", + "properties": { + "ids": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "dto.SysDeptInsertReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "deptId": { + "description": "编码", + "type": "integer" + }, + "deptName": { + "description": "部门名称", + "type": "string" + }, + "deptPath": { + "description": "路径", + "type": "string" + }, + "email": { + "description": "邮箱", + "type": "string" + }, + "leader": { + "description": "负责人", + "type": "string" + }, + "parentId": { + "description": "上级部门", + "type": "integer" + }, + "phone": { + "description": "手机", + "type": "string" + }, + "sort": { + "description": "排序", + "type": "integer" + }, + "status": { + "description": "状态", + "type": "integer" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysDeptUpdateReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "deptId": { + "description": "编码", + "type": "integer" + }, + "deptName": { + "description": "部门名称", + "type": "string" + }, + "deptPath": { + "description": "路径", + "type": "string" + }, + "email": { + "description": "邮箱", + "type": "string" + }, + "leader": { + "description": "负责人", + "type": "string" + }, + "parentId": { + "description": "上级部门", + "type": "integer" + }, + "phone": { + "description": "手机", + "type": "string" + }, + "sort": { + "description": "排序", + "type": "integer" + }, + "status": { + "description": "状态", + "type": "integer" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysDictDataDeleteReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysDictDataGetAllResp": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "dto.SysDictDataInsertReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "cssClass": { + "type": "string" + }, + "default": { + "type": "string" + }, + "dictLabel": { + "type": "string" + }, + "dictSort": { + "type": "integer" + }, + "dictType": { + "type": "string" + }, + "dictValue": { + "type": "string" + }, + "isDefault": { + "type": "string" + }, + "listClass": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "status": { + "type": "integer" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysDictDataUpdateReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "cssClass": { + "type": "string" + }, + "default": { + "type": "string" + }, + "dictLabel": { + "type": "string" + }, + "dictSort": { + "type": "integer" + }, + "dictType": { + "type": "string" + }, + "dictValue": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isDefault": { + "type": "string" + }, + "listClass": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "status": { + "type": "integer" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysDictTypeDeleteReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysDictTypeInsertReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "dictName": { + "type": "string" + }, + "dictType": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "remark": { + "type": "string" + }, + "status": { + "type": "integer" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysDictTypeUpdateReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "dictName": { + "type": "string" + }, + "dictType": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "remark": { + "type": "string" + }, + "status": { + "type": "integer" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysLoginLogDeleteReq": { + "type": "object", + "properties": { + "ids": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "dto.SysMenuDeleteReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysMenuInsertReq": { + "type": "object", + "properties": { + "action": { + "description": "请求方式", + "type": "string" + }, + "apis": { + "type": "array", + "items": { + "type": "integer" + } + }, + "breadcrumb": { + "description": "是否面包屑", + "type": "string" + }, + "component": { + "description": "组件", + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "icon": { + "description": "图标", + "type": "string" + }, + "isFrame": { + "description": "是否frame", + "type": "string" + }, + "menuId": { + "description": "编码", + "type": "integer" + }, + "menuName": { + "description": "菜单name", + "type": "string" + }, + "menuType": { + "description": "菜单类型", + "type": "string" + }, + "noCache": { + "description": "是否缓存", + "type": "boolean" + }, + "parentId": { + "description": "上级菜单", + "type": "integer" + }, + "path": { + "description": "路径", + "type": "string" + }, + "paths": { + "description": "id路径", + "type": "string" + }, + "permission": { + "description": "权限编码", + "type": "string" + }, + "sort": { + "description": "排序", + "type": "integer" + }, + "sysApi": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysApi" + } + }, + "title": { + "description": "显示名称", + "type": "string" + }, + "updateBy": { + "type": "integer" + }, + "visible": { + "description": "是否显示", + "type": "string" + } + } + }, + "dto.SysMenuUpdateReq": { + "type": "object", + "properties": { + "action": { + "description": "请求方式", + "type": "string" + }, + "apis": { + "type": "array", + "items": { + "type": "integer" + } + }, + "breadcrumb": { + "description": "是否面包屑", + "type": "string" + }, + "component": { + "description": "组件", + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "icon": { + "description": "图标", + "type": "string" + }, + "isFrame": { + "description": "是否frame", + "type": "string" + }, + "menuId": { + "description": "编码", + "type": "integer" + }, + "menuName": { + "description": "菜单name", + "type": "string" + }, + "menuType": { + "description": "菜单类型", + "type": "string" + }, + "noCache": { + "description": "是否缓存", + "type": "boolean" + }, + "parentId": { + "description": "上级菜单", + "type": "integer" + }, + "path": { + "description": "路径", + "type": "string" + }, + "paths": { + "description": "id路径", + "type": "string" + }, + "permission": { + "description": "权限编码", + "type": "string" + }, + "sort": { + "description": "排序", + "type": "integer" + }, + "sysApi": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysApi" + } + }, + "title": { + "description": "显示名称", + "type": "string" + }, + "updateBy": { + "type": "integer" + }, + "visible": { + "description": "是否显示", + "type": "string" + } + } + }, + "dto.SysOperaLogDeleteReq": { + "type": "object", + "properties": { + "ids": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "dto.SysPostDeleteReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysPostInsertReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "postCode": { + "type": "string" + }, + "postId": { + "type": "integer" + }, + "postName": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "sort": { + "type": "integer" + }, + "status": { + "type": "integer" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysPostUpdateReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "postCode": { + "type": "string" + }, + "postId": { + "type": "integer" + }, + "postName": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "sort": { + "type": "integer" + }, + "status": { + "type": "integer" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysRoleDeleteReq": { + "type": "object", + "properties": { + "ids": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "dto.SysRoleInsertReq": { + "type": "object", + "properties": { + "admin": { + "type": "boolean" + }, + "createBy": { + "type": "integer" + }, + "dataScope": { + "type": "string" + }, + "deptIds": { + "type": "array", + "items": { + "type": "integer" + } + }, + "flag": { + "description": "标记", + "type": "string" + }, + "menuIds": { + "type": "array", + "items": { + "type": "integer" + } + }, + "remark": { + "description": "备注", + "type": "string" + }, + "roleId": { + "description": "角色编码", + "type": "integer" + }, + "roleKey": { + "description": "角色代码", + "type": "string" + }, + "roleName": { + "description": "角色名称", + "type": "string" + }, + "roleSort": { + "description": "角色排序", + "type": "integer" + }, + "status": { + "description": "状态 1禁用 2正常", + "type": "string" + }, + "sysDept": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysDept" + } + }, + "sysMenu": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysMenu" + } + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysRoleUpdateReq": { + "type": "object", + "properties": { + "admin": { + "type": "boolean" + }, + "createBy": { + "type": "integer" + }, + "dataScope": { + "type": "string" + }, + "deptIds": { + "type": "array", + "items": { + "type": "integer" + } + }, + "flag": { + "description": "标记", + "type": "string" + }, + "menuIds": { + "type": "array", + "items": { + "type": "integer" + } + }, + "remark": { + "description": "备注", + "type": "string" + }, + "roleId": { + "description": "角色编码", + "type": "integer" + }, + "roleKey": { + "description": "角色代码", + "type": "string" + }, + "roleName": { + "description": "角色名称", + "type": "string" + }, + "roleSort": { + "description": "角色排序", + "type": "integer" + }, + "status": { + "description": "状态", + "type": "string" + }, + "sysDept": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysDept" + } + }, + "sysMenu": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysMenu" + } + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.SysUserInsertReq": { + "type": "object", + "properties": { + "avatar": { + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "deptId": { + "type": "integer" + }, + "email": { + "type": "string" + }, + "nickName": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "postId": { + "type": "integer" + }, + "remark": { + "type": "string" + }, + "roleId": { + "type": "integer" + }, + "sex": { + "type": "string" + }, + "status": { + "type": "string", + "default": "1" + }, + "updateBy": { + "type": "integer" + }, + "userId": { + "description": "用户ID", + "type": "integer" + }, + "username": { + "type": "string" + } + } + }, + "dto.SysUserUpdateReq": { + "type": "object", + "properties": { + "avatar": { + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "deptId": { + "type": "integer" + }, + "email": { + "type": "string" + }, + "nickName": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "postId": { + "type": "integer" + }, + "remark": { + "type": "string" + }, + "roleId": { + "type": "integer" + }, + "sex": { + "type": "string" + }, + "status": { + "type": "string", + "default": "1" + }, + "updateBy": { + "type": "integer" + }, + "userId": { + "description": "用户ID", + "type": "integer" + }, + "username": { + "type": "string" + } + } + }, + "dto.UpdateStatusReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "roleId": { + "description": "角色编码", + "type": "integer" + }, + "status": { + "description": "状态", + "type": "string" + }, + "updateBy": { + "type": "integer" + } + } + }, + "dto.UpdateSysUserStatusReq": { + "type": "object", + "properties": { + "createBy": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "updateBy": { + "type": "integer" + }, + "userId": { + "description": "用户ID", + "type": "integer" + } + } + }, + "handler.Login": { + "type": "object", + "required": [ + "code", + "password", + "username", + "uuid" + ], + "properties": { + "code": { + "type": "string" + }, + "password": { + "type": "string" + }, + "username": { + "type": "string" + }, + "uuid": { + "type": "string" + } + } + }, + "models.SysApi": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "handle": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "path": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updateBy": { + "type": "integer" + }, + "updatedAt": { + "type": "string" + } + } + }, + "models.SysConfig": { + "type": "object", + "properties": { + "configKey": { + "type": "string" + }, + "configName": { + "type": "string" + }, + "configType": { + "type": "string" + }, + "configValue": { + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isFrontend": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "updateBy": { + "type": "integer" + }, + "updatedAt": { + "type": "string" + } + } + }, + "models.SysDept": { + "type": "object", + "properties": { + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysDept" + } + }, + "createBy": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "dataScope": { + "type": "string" + }, + "deptId": { + "description": "部门编码", + "type": "integer" + }, + "deptName": { + "description": "部门名称", + "type": "string" + }, + "deptPath": { + "type": "string" + }, + "email": { + "description": "邮箱", + "type": "string" + }, + "leader": { + "description": "负责人", + "type": "string" + }, + "params": { + "type": "string" + }, + "parentId": { + "description": "上级部门", + "type": "integer" + }, + "phone": { + "description": "手机", + "type": "string" + }, + "sort": { + "description": "排序", + "type": "integer" + }, + "status": { + "description": "状态", + "type": "integer" + }, + "updateBy": { + "type": "integer" + }, + "updatedAt": { + "type": "string" + } + } + }, + "models.SysMenu": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "apis": { + "type": "array", + "items": { + "type": "integer" + } + }, + "breadcrumb": { + "type": "string" + }, + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysMenu" + } + }, + "component": { + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "dataScope": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "isFrame": { + "type": "string" + }, + "is_select": { + "type": "boolean" + }, + "menuId": { + "type": "integer" + }, + "menuName": { + "type": "string" + }, + "menuType": { + "type": "string" + }, + "noCache": { + "type": "boolean" + }, + "params": { + "type": "string" + }, + "parentId": { + "type": "integer" + }, + "path": { + "type": "string" + }, + "paths": { + "type": "string" + }, + "permission": { + "type": "string" + }, + "roleId": { + "type": "integer" + }, + "sort": { + "type": "integer" + }, + "sysApi": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SysApi" + } + }, + "title": { + "type": "string" + }, + "updateBy": { + "type": "integer" + }, + "updatedAt": { + "type": "string" + }, + "visible": { + "type": "string" + } + } + }, + "response.Page": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "pageIndex": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + } + }, + "response.Response": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "msg": { + "type": "string" + }, + "requestId": { + "description": "数据集", + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "tools.Params": { + "type": "object", + "properties": { + "treeCode": { + "type": "string" + }, + "treeName": { + "type": "string" + }, + "treeParentCode": { + "type": "string" + } + } + }, + "tools.SysColumns": { + "type": "object", + "properties": { + "columnComment": { + "type": "string" + }, + "columnId": { + "type": "integer" + }, + "columnName": { + "type": "string" + }, + "columnType": { + "type": "string" + }, + "createBy": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "deletedAt": { + "type": "string" + }, + "dictType": { + "type": "string" + }, + "edit": { + "type": "boolean" + }, + "fkCol": { + "type": "array", + "items": { + "$ref": "#/definitions/tools.SysColumns" + } + }, + "fkLabelId": { + "type": "string" + }, + "fkLabelName": { + "type": "string" + }, + "fkTableName": { + "type": "string" + }, + "fkTableNameClass": { + "type": "string" + }, + "fkTableNamePackage": { + "type": "string" + }, + "goField": { + "type": "string" + }, + "goType": { + "type": "string" + }, + "htmlType": { + "type": "string" + }, + "increment": { + "type": "boolean" + }, + "insert": { + "type": "boolean" + }, + "isEdit": { + "type": "string" + }, + "isIncrement": { + "type": "string" + }, + "isInsert": { + "type": "string" + }, + "isList": { + "type": "string" + }, + "isPk": { + "type": "string" + }, + "isQuery": { + "type": "string" + }, + "isRequired": { + "type": "string" + }, + "jsonField": { + "type": "string" + }, + "list": { + "type": "string" + }, + "pk": { + "type": "boolean" + }, + "query": { + "type": "boolean" + }, + "queryType": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "required": { + "type": "boolean" + }, + "sort": { + "type": "integer" + }, + "superColumn": { + "type": "boolean" + }, + "tableId": { + "type": "integer" + }, + "updateBy": { + "type": "integer" + }, + "updatedAt": { + "type": "string" + }, + "usableColumn": { + "type": "boolean" + } + } + }, + "tools.SysTables": { + "type": "object", + "properties": { + "businessName": { + "type": "string" + }, + "className": { + "description": "类名", + "type": "string" + }, + "columns": { + "type": "array", + "items": { + "$ref": "#/definitions/tools.SysColumns" + } + }, + "createBy": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "crud": { + "type": "boolean" + }, + "dataScope": { + "type": "string" + }, + "deletedAt": { + "type": "string" + }, + "functionAuthor": { + "description": "功能作者", + "type": "string" + }, + "functionName": { + "description": "功能名称", + "type": "string" + }, + "isActions": { + "type": "integer" + }, + "isAuth": { + "type": "integer" + }, + "isDataScope": { + "type": "integer" + }, + "isLogicalDelete": { + "type": "string" + }, + "logicalDelete": { + "type": "boolean" + }, + "logicalDeleteColumn": { + "type": "string" + }, + "moduleFrontName": { + "description": "前端文件名", + "type": "string" + }, + "moduleName": { + "description": "go文件名", + "type": "string" + }, + "options": { + "type": "string" + }, + "packageName": { + "description": "包名", + "type": "string" + }, + "params": { + "$ref": "#/definitions/tools.Params" + }, + "pkColumn": { + "type": "string" + }, + "pkGoField": { + "type": "string" + }, + "pkJsonField": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "tableComment": { + "description": "表备注", + "type": "string" + }, + "tableId": { + "description": "表编码", + "type": "integer" + }, + "tableName": { + "description": "表名称", + "type": "string" + }, + "tplCategory": { + "type": "string" + }, + "tree": { + "type": "boolean" + }, + "treeCode": { + "type": "string" + }, + "treeName": { + "type": "string" + }, + "treeParentCode": { + "type": "string" + }, + "updateBy": { + "type": "integer" + }, + "updatedAt": { + "type": "string" + } + } + } + }, + "securityDefinitions": { + "Bearer": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +} \ No newline at end of file diff --git a/docs/admin/admin_swagger.yaml b/docs/admin/admin_swagger.yaml new file mode 100644 index 0000000..412817e --- /dev/null +++ b/docs/admin/admin_swagger.yaml @@ -0,0 +1,2771 @@ +definitions: + dto.GetSetSysConfigReq: + properties: + configKey: + type: string + configValue: + type: string + type: object + dto.PassWord: + properties: + newPassword: + type: string + oldPassword: + type: string + type: object + dto.ResetSysUserPwdReq: + properties: + createBy: + type: integer + password: + type: string + updateBy: + type: integer + userId: + description: 用户ID + type: integer + type: object + dto.RoleDataScopeReq: + properties: + dataScope: + type: string + deptIds: + items: + type: integer + type: array + roleId: + type: integer + required: + - dataScope + - roleId + type: object + dto.SysApiDeleteReq: + properties: + ids: + items: + type: integer + type: array + type: object + dto.SysApiUpdateReq: + properties: + action: + type: string + createBy: + type: integer + handle: + type: string + id: + description: 编码 + type: integer + path: + type: string + title: + type: string + type: + type: string + updateBy: + type: integer + type: object + dto.SysConfigByKeyReq: + properties: + configKey: + type: string + type: object + dto.SysConfigControl: + properties: + configKey: + type: string + configName: + type: string + configType: + type: string + configValue: + type: string + createBy: + type: integer + id: + description: 编码 + type: integer + isFrontend: + type: string + remark: + type: string + updateBy: + type: integer + type: object + dto.SysDeptDeleteReq: + properties: + ids: + items: + type: integer + type: array + type: object + dto.SysDeptInsertReq: + properties: + createBy: + type: integer + deptId: + description: 编码 + type: integer + deptName: + description: 部门名称 + type: string + deptPath: + description: 路径 + type: string + email: + description: 邮箱 + type: string + leader: + description: 负责人 + type: string + parentId: + description: 上级部门 + type: integer + phone: + description: 手机 + type: string + sort: + description: 排序 + type: integer + status: + description: 状态 + type: integer + updateBy: + type: integer + type: object + dto.SysDeptUpdateReq: + properties: + createBy: + type: integer + deptId: + description: 编码 + type: integer + deptName: + description: 部门名称 + type: string + deptPath: + description: 路径 + type: string + email: + description: 邮箱 + type: string + leader: + description: 负责人 + type: string + parentId: + description: 上级部门 + type: integer + phone: + description: 手机 + type: string + sort: + description: 排序 + type: integer + status: + description: 状态 + type: integer + updateBy: + type: integer + type: object + dto.SysDictDataDeleteReq: + properties: + createBy: + type: integer + ids: + items: + type: integer + type: array + updateBy: + type: integer + type: object + dto.SysDictDataGetAllResp: + properties: + label: + type: string + value: + type: string + type: object + dto.SysDictDataInsertReq: + properties: + createBy: + type: integer + cssClass: + type: string + default: + type: string + dictLabel: + type: string + dictSort: + type: integer + dictType: + type: string + dictValue: + type: string + isDefault: + type: string + listClass: + type: string + remark: + type: string + status: + type: integer + updateBy: + type: integer + type: object + dto.SysDictDataUpdateReq: + properties: + createBy: + type: integer + cssClass: + type: string + default: + type: string + dictLabel: + type: string + dictSort: + type: integer + dictType: + type: string + dictValue: + type: string + id: + type: integer + isDefault: + type: string + listClass: + type: string + remark: + type: string + status: + type: integer + updateBy: + type: integer + type: object + dto.SysDictTypeDeleteReq: + properties: + createBy: + type: integer + ids: + items: + type: integer + type: array + updateBy: + type: integer + type: object + dto.SysDictTypeInsertReq: + properties: + createBy: + type: integer + dictName: + type: string + dictType: + type: string + id: + type: integer + remark: + type: string + status: + type: integer + updateBy: + type: integer + type: object + dto.SysDictTypeUpdateReq: + properties: + createBy: + type: integer + dictName: + type: string + dictType: + type: string + id: + type: integer + remark: + type: string + status: + type: integer + updateBy: + type: integer + type: object + dto.SysLoginLogDeleteReq: + properties: + ids: + items: + type: integer + type: array + type: object + dto.SysMenuDeleteReq: + properties: + createBy: + type: integer + ids: + items: + type: integer + type: array + updateBy: + type: integer + type: object + dto.SysMenuInsertReq: + properties: + action: + description: 请求方式 + type: string + apis: + items: + type: integer + type: array + breadcrumb: + description: 是否面包屑 + type: string + component: + description: 组件 + type: string + createBy: + type: integer + icon: + description: 图标 + type: string + isFrame: + description: 是否frame + type: string + menuId: + description: 编码 + type: integer + menuName: + description: 菜单name + type: string + menuType: + description: 菜单类型 + type: string + noCache: + description: 是否缓存 + type: boolean + parentId: + description: 上级菜单 + type: integer + path: + description: 路径 + type: string + paths: + description: id路径 + type: string + permission: + description: 权限编码 + type: string + sort: + description: 排序 + type: integer + sysApi: + items: + $ref: '#/definitions/models.SysApi' + type: array + title: + description: 显示名称 + type: string + updateBy: + type: integer + visible: + description: 是否显示 + type: string + type: object + dto.SysMenuUpdateReq: + properties: + action: + description: 请求方式 + type: string + apis: + items: + type: integer + type: array + breadcrumb: + description: 是否面包屑 + type: string + component: + description: 组件 + type: string + createBy: + type: integer + icon: + description: 图标 + type: string + isFrame: + description: 是否frame + type: string + menuId: + description: 编码 + type: integer + menuName: + description: 菜单name + type: string + menuType: + description: 菜单类型 + type: string + noCache: + description: 是否缓存 + type: boolean + parentId: + description: 上级菜单 + type: integer + path: + description: 路径 + type: string + paths: + description: id路径 + type: string + permission: + description: 权限编码 + type: string + sort: + description: 排序 + type: integer + sysApi: + items: + $ref: '#/definitions/models.SysApi' + type: array + title: + description: 显示名称 + type: string + updateBy: + type: integer + visible: + description: 是否显示 + type: string + type: object + dto.SysOperaLogDeleteReq: + properties: + ids: + items: + type: integer + type: array + type: object + dto.SysPostDeleteReq: + properties: + createBy: + type: integer + ids: + items: + type: integer + type: array + updateBy: + type: integer + type: object + dto.SysPostInsertReq: + properties: + createBy: + type: integer + postCode: + type: string + postId: + type: integer + postName: + type: string + remark: + type: string + sort: + type: integer + status: + type: integer + updateBy: + type: integer + type: object + dto.SysPostUpdateReq: + properties: + createBy: + type: integer + postCode: + type: string + postId: + type: integer + postName: + type: string + remark: + type: string + sort: + type: integer + status: + type: integer + updateBy: + type: integer + type: object + dto.SysRoleDeleteReq: + properties: + ids: + items: + type: integer + type: array + type: object + dto.SysRoleInsertReq: + properties: + admin: + type: boolean + createBy: + type: integer + dataScope: + type: string + deptIds: + items: + type: integer + type: array + flag: + description: 标记 + type: string + menuIds: + items: + type: integer + type: array + remark: + description: 备注 + type: string + roleId: + description: 角色编码 + type: integer + roleKey: + description: 角色代码 + type: string + roleName: + description: 角色名称 + type: string + roleSort: + description: 角色排序 + type: integer + status: + description: 状态 1禁用 2正常 + type: string + sysDept: + items: + $ref: '#/definitions/models.SysDept' + type: array + sysMenu: + items: + $ref: '#/definitions/models.SysMenu' + type: array + updateBy: + type: integer + type: object + dto.SysRoleUpdateReq: + properties: + admin: + type: boolean + createBy: + type: integer + dataScope: + type: string + deptIds: + items: + type: integer + type: array + flag: + description: 标记 + type: string + menuIds: + items: + type: integer + type: array + remark: + description: 备注 + type: string + roleId: + description: 角色编码 + type: integer + roleKey: + description: 角色代码 + type: string + roleName: + description: 角色名称 + type: string + roleSort: + description: 角色排序 + type: integer + status: + description: 状态 + type: string + sysDept: + items: + $ref: '#/definitions/models.SysDept' + type: array + sysMenu: + items: + $ref: '#/definitions/models.SysMenu' + type: array + updateBy: + type: integer + type: object + dto.SysUserInsertReq: + properties: + avatar: + type: string + createBy: + type: integer + deptId: + type: integer + email: + type: string + nickName: + type: string + password: + type: string + phone: + type: string + postId: + type: integer + remark: + type: string + roleId: + type: integer + sex: + type: string + status: + default: "1" + type: string + updateBy: + type: integer + userId: + description: 用户ID + type: integer + username: + type: string + type: object + dto.SysUserUpdateReq: + properties: + avatar: + type: string + createBy: + type: integer + deptId: + type: integer + email: + type: string + nickName: + type: string + phone: + type: string + postId: + type: integer + remark: + type: string + roleId: + type: integer + sex: + type: string + status: + default: "1" + type: string + updateBy: + type: integer + userId: + description: 用户ID + type: integer + username: + type: string + type: object + dto.UpdateStatusReq: + properties: + createBy: + type: integer + roleId: + description: 角色编码 + type: integer + status: + description: 状态 + type: string + updateBy: + type: integer + type: object + dto.UpdateSysUserStatusReq: + properties: + createBy: + type: integer + status: + type: string + updateBy: + type: integer + userId: + description: 用户ID + type: integer + type: object + handler.Login: + properties: + code: + type: string + password: + type: string + username: + type: string + uuid: + type: string + required: + - code + - password + - username + - uuid + type: object + models.SysApi: + properties: + action: + type: string + createBy: + type: integer + createdAt: + type: string + handle: + type: string + id: + type: integer + path: + type: string + title: + type: string + type: + type: string + updateBy: + type: integer + updatedAt: + type: string + type: object + models.SysConfig: + properties: + configKey: + type: string + configName: + type: string + configType: + type: string + configValue: + type: string + createBy: + type: integer + createdAt: + type: string + id: + type: integer + isFrontend: + type: string + remark: + type: string + updateBy: + type: integer + updatedAt: + type: string + type: object + models.SysDept: + properties: + children: + items: + $ref: '#/definitions/models.SysDept' + type: array + createBy: + type: integer + createdAt: + type: string + dataScope: + type: string + deptId: + description: 部门编码 + type: integer + deptName: + description: 部门名称 + type: string + deptPath: + type: string + email: + description: 邮箱 + type: string + leader: + description: 负责人 + type: string + params: + type: string + parentId: + description: 上级部门 + type: integer + phone: + description: 手机 + type: string + sort: + description: 排序 + type: integer + status: + description: 状态 + type: integer + updateBy: + type: integer + updatedAt: + type: string + type: object + models.SysMenu: + properties: + action: + type: string + apis: + items: + type: integer + type: array + breadcrumb: + type: string + children: + items: + $ref: '#/definitions/models.SysMenu' + type: array + component: + type: string + createBy: + type: integer + createdAt: + type: string + dataScope: + type: string + icon: + type: string + is_select: + type: boolean + isFrame: + type: string + menuId: + type: integer + menuName: + type: string + menuType: + type: string + noCache: + type: boolean + params: + type: string + parentId: + type: integer + path: + type: string + paths: + type: string + permission: + type: string + roleId: + type: integer + sort: + type: integer + sysApi: + items: + $ref: '#/definitions/models.SysApi' + type: array + title: + type: string + updateBy: + type: integer + updatedAt: + type: string + visible: + type: string + type: object + response.Page: + properties: + count: + type: integer + pageIndex: + type: integer + pageSize: + type: integer + type: object + response.Response: + properties: + code: + type: integer + msg: + type: string + requestId: + description: 数据集 + type: string + status: + type: string + type: object + tools.Params: + properties: + treeCode: + type: string + treeName: + type: string + treeParentCode: + type: string + type: object + tools.SysColumns: + properties: + columnComment: + type: string + columnId: + type: integer + columnName: + type: string + columnType: + type: string + createBy: + type: integer + createdAt: + type: string + deletedAt: + type: string + dictType: + type: string + edit: + type: boolean + fkCol: + items: + $ref: '#/definitions/tools.SysColumns' + type: array + fkLabelId: + type: string + fkLabelName: + type: string + fkTableName: + type: string + fkTableNameClass: + type: string + fkTableNamePackage: + type: string + goField: + type: string + goType: + type: string + htmlType: + type: string + increment: + type: boolean + insert: + type: boolean + isEdit: + type: string + isIncrement: + type: string + isInsert: + type: string + isList: + type: string + isPk: + type: string + isQuery: + type: string + isRequired: + type: string + jsonField: + type: string + list: + type: string + pk: + type: boolean + query: + type: boolean + queryType: + type: string + remark: + type: string + required: + type: boolean + sort: + type: integer + superColumn: + type: boolean + tableId: + type: integer + updateBy: + type: integer + updatedAt: + type: string + usableColumn: + type: boolean + type: object + tools.SysTables: + properties: + businessName: + type: string + className: + description: 类名 + type: string + columns: + items: + $ref: '#/definitions/tools.SysColumns' + type: array + createBy: + type: integer + createdAt: + type: string + crud: + type: boolean + dataScope: + type: string + deletedAt: + type: string + functionAuthor: + description: 功能作者 + type: string + functionName: + description: 功能名称 + type: string + isActions: + type: integer + isAuth: + type: integer + isDataScope: + type: integer + isLogicalDelete: + type: string + logicalDelete: + type: boolean + logicalDeleteColumn: + type: string + moduleFrontName: + description: 前端文件名 + type: string + moduleName: + description: go文件名 + type: string + options: + type: string + packageName: + description: 包名 + type: string + params: + $ref: '#/definitions/tools.Params' + pkColumn: + type: string + pkGoField: + type: string + pkJsonField: + type: string + remark: + type: string + tableComment: + description: 表备注 + type: string + tableId: + description: 表编码 + type: integer + tableName: + description: 表名称 + type: string + tplCategory: + type: string + tree: + type: boolean + treeCode: + type: string + treeName: + type: string + treeParentCode: + type: string + updateBy: + type: integer + updatedAt: + type: string + type: object +info: + contact: {} + description: |- + 基于Gin + Vue + Element UI的前后端分离权限管理系统的接口文档 + 添加qq群: 521386980 进入技术交流群 请先star,谢谢! + license: + name: MIT + url: https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md + title: go-admin API + version: 2.0.0 +paths: + /api/v1/app-config: + get: + description: 获取系统配置信息,主要注意这里不在验证权限 + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: + type: string + type: object + type: object + summary: 获取系统前台配置信息,主要注意这里不在验证权限 + tags: + - 配置管理 + /api/v1/captcha: + get: + description: 获取验证码 + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + type: string + id: + type: string + msg: + type: string + type: object + summary: 获取验证码 + tags: + - 登陆 + /api/v1/db/columns/page: + get: + description: 数据库表列分页列表 / database table column page list + parameters: + - description: tableName / 数据表名称 + in: query + name: tableName + type: string + - description: pageSize / 页条数 + in: query + name: pageSize + type: integer + - description: pageIndex / 页码 + in: query + name: pageIndex + type: integer + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + summary: 分页列表数据 / page list data + tags: + - 工具 / 生成工具 + /api/v1/db/tables/page: + get: + description: 数据库表分页列表 / database table page list + parameters: + - description: tableName / 数据表名称 + in: query + name: tableName + type: string + - description: pageSize / 页条数 + in: query + name: pageSize + type: integer + - description: pageIndex / 页码 + in: query + name: pageIndex + type: integer + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + summary: 分页列表数据 / page list data + tags: + - 工具 / 生成工具 + /api/v1/dept: + delete: + description: 删除数据 + parameters: + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysDeptDeleteReq' + responses: + "200": + description: '{"code": -1, "message": "删除失败"}' + schema: + type: string + security: + - Bearer: [] + summary: 删除部门 + tags: + - 部门 + get: + description: 分页列表 + parameters: + - description: deptName + in: query + name: deptName + type: string + - description: deptId + in: query + name: deptId + type: string + - description: position + in: query + name: position + type: string + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 分页部门列表数据 + tags: + - 部门 + post: + consumes: + - application/json + description: 获取JSON + parameters: + - description: data + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysDeptInsertReq' + responses: + "200": + description: '{"code": -1, "message": "添加失败"}' + schema: + type: string + security: + - Bearer: [] + summary: 添加部门 + tags: + - 部门 + /api/v1/dept/{deptId}: + get: + description: 获取JSON + parameters: + - description: deptId + in: path + name: deptId + type: string + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 获取部门数据 + tags: + - 部门 + put: + consumes: + - application/json + description: 获取JSON + parameters: + - description: id + in: path + name: id + required: true + type: integer + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysDeptUpdateReq' + responses: + "200": + description: '{"code": -1, "message": "添加失败"}' + schema: + type: string + security: + - Bearer: [] + summary: 修改部门 + tags: + - 部门 + /api/v1/dict-data/option-select: + get: + description: 数据字典根据key获取 + parameters: + - description: dictType + in: query + name: dictType + required: true + type: integer + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + items: + $ref: '#/definitions/dto.SysDictDataGetAllResp' + type: array + type: object + security: + - Bearer: [] + summary: 数据字典根据key获取 + tags: + - 字典数据 + /api/v1/dict/data: + delete: + description: 删除数据 + parameters: + - description: body + in: body + name: dictCode + required: true + schema: + $ref: '#/definitions/dto.SysDictDataDeleteReq' + responses: + "200": + description: '{"code": 200, "message": "删除成功"}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 删除字典数据 + tags: + - 字典数据 + get: + description: 获取JSON + parameters: + - description: status + in: query + name: status + type: string + - description: dictCode + in: query + name: dictCode + type: string + - description: dictType + in: query + name: dictType + type: string + - description: 页条数 + in: query + name: pageSize + type: integer + - description: 页码 + in: query + name: pageIndex + type: integer + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 字典数据列表 + tags: + - 字典数据 + post: + consumes: + - application/json + description: 获取JSON + parameters: + - description: data + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysDictDataInsertReq' + responses: + "200": + description: '{"code": 200, "message": "添加成功"}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 添加字典数据 + tags: + - 字典数据 + /api/v1/dict/data/{dictCode}: + get: + description: 获取JSON + parameters: + - description: 字典编码 + in: path + name: dictCode + required: true + type: integer + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 通过编码获取字典数据 + tags: + - 字典数据 + put: + consumes: + - application/json + description: 获取JSON + parameters: + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysDictDataUpdateReq' + responses: + "200": + description: '{"code": 200, "message": "修改成功"}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 修改字典数据 + tags: + - 字典数据 + /api/v1/dict/type: + delete: + description: 删除数据 + parameters: + - description: body + in: body + name: dictCode + required: true + schema: + $ref: '#/definitions/dto.SysDictTypeDeleteReq' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 删除字典类型 + tags: + - 字典类型 + get: + description: 获取JSON + parameters: + - description: dictName + in: query + name: dictName + type: string + - description: dictId + in: query + name: dictId + type: string + - description: dictType + in: query + name: dictType + type: string + - description: 页条数 + in: query + name: pageSize + type: integer + - description: 页码 + in: query + name: pageIndex + type: integer + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 字典类型列表数据 + tags: + - 字典类型 + post: + consumes: + - application/json + description: 获取JSON + parameters: + - description: data + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysDictTypeInsertReq' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 添加字典类型 + tags: + - 字典类型 + /api/v1/dict/type-option-select: + get: + description: 获取JSON + parameters: + - description: dictName + in: query + name: dictName + type: string + - description: dictId + in: query + name: dictId + type: string + - description: dictType + in: query + name: dictType + type: string + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 字典类型全部数据 代码生成使用接口 + tags: + - 字典类型 + /api/v1/dict/type/{dictId}: + get: + description: 获取JSON + parameters: + - description: 字典类型编码 + in: path + name: dictId + required: true + type: integer + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 字典类型通过字典id获取 + tags: + - 字典类型 + put: + consumes: + - application/json + description: 获取JSON + parameters: + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysDictTypeUpdateReq' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 修改字典类型 + tags: + - 字典类型 + /api/v1/getinfo: + get: + description: 获取JSON + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 获取个人信息 + tags: + - 个人中心 + /api/v1/login: + post: + consumes: + - application/json + description: |- + 获取token + LoginHandler can be used by clients to get a jwt token. + Payload needs to be json in the form of {"username": "USERNAME", "password": "PASSWORD"}. + Reply will be of the form {"token": "TOKEN"}. + dev mode:It should be noted that all fields cannot be empty, and a value of 0 can be passed in addition to the account password + 注意:开发模式:需要注意全部字段不能为空,账号密码外可以传入0值 + parameters: + - description: account + in: body + name: account + required: true + schema: + $ref: '#/definitions/handler.Login' + responses: + "200": + description: '{"code": 200, "expire": "2019-08-07T12:45:48+08:00", "token": + ".eyJleHAiOjE1NjUxNTMxNDgsImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTU2NTE0OTU0OH0.-zvzHvbg0A" + }' + schema: + type: string + summary: 登陆 + tags: + - 登陆 + /api/v1/menu: + delete: + description: 删除数据 + parameters: + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysMenuDeleteReq' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 删除菜单 + tags: + - 菜单 + get: + description: 获取JSON + parameters: + - description: menuName + in: query + name: menuName + type: string + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Menu列表数据 + tags: + - 菜单 + post: + consumes: + - application/json + description: 获取JSON + parameters: + - description: data + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysMenuInsertReq' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 创建菜单 + tags: + - 菜单 + /api/v1/menu/{id}: + get: + description: 获取JSON + parameters: + - description: id + in: path + name: id + type: string + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Menu详情数据 + tags: + - 菜单 + put: + consumes: + - application/json + description: 获取JSON + parameters: + - description: id + in: path + name: id + required: true + type: integer + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysMenuUpdateReq' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 修改菜单 + tags: + - 菜单 + /api/v1/menuTreeselect/{roleId}: + get: + consumes: + - application/json + description: 获取JSON + parameters: + - description: roleId + in: path + name: roleId + required: true + type: integer + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 角色修改使用的菜单列表 + tags: + - 菜单 + /api/v1/menurole: + get: + description: 获取JSON + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 根据登录角色名称获取菜单列表数据(左菜单使用) + tags: + - 菜单 + /api/v1/post: + delete: + description: 删除数据 + parameters: + - description: 请求参数 + in: body + name: id + required: true + schema: + $ref: '#/definitions/dto.SysPostDeleteReq' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 删除岗位 + tags: + - 岗位 + get: + description: 获取JSON + parameters: + - description: postName + in: query + name: postName + type: string + - description: postCode + in: query + name: postCode + type: string + - description: postId + in: query + name: postId + type: string + - description: status + in: query + name: status + type: string + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 岗位列表数据 + tags: + - 岗位 + post: + consumes: + - application/json + description: 获取JSON + parameters: + - description: data + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysPostInsertReq' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 添加岗位 + tags: + - 岗位 + /api/v1/post/{id}: + put: + consumes: + - application/json + description: 获取JSON + parameters: + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysPostUpdateReq' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 修改岗位 + tags: + - 岗位 + /api/v1/post/{postId}: + get: + description: 获取JSON + parameters: + - description: 编码 + in: path + name: id + required: true + type: integer + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 获取岗位信息 + tags: + - 岗位 + /api/v1/public/uploadFile: + post: + consumes: + - multipart/form-data + description: 获取JSON + parameters: + - description: type + in: query + name: type + required: true + type: string + - description: file + in: formData + name: file + required: true + type: file + responses: + "200": + description: '{"code": -1, "message": "添加失败"}' + schema: + type: string + security: + - Bearer: [] + summary: 上传图片 + tags: + - 公共接口 + /api/v1/role: + delete: + description: 删除数据 + parameters: + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysRoleDeleteReq' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 删除用户角色 + tags: + - 角色/Role + get: + description: Get JSON + parameters: + - description: roleName + in: query + name: roleName + type: string + - description: status + in: query + name: status + type: string + - description: roleKey + in: query + name: roleKey + type: string + - description: 页条数 + in: query + name: pageSize + type: integer + - description: 页码 + in: query + name: pageIndex + type: integer + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 角色列表数据 + tags: + - 角色/Role + post: + consumes: + - application/json + description: 获取JSON + parameters: + - description: data + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysRoleInsertReq' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 创建角色 + tags: + - 角色/Role + /api/v1/role-status/{id}: + put: + consumes: + - application/json + description: 获取JSON + parameters: + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.RoleDataScopeReq' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 更新角色数据权限 + tags: + - 角色/Role + /api/v1/role/{id}: + get: + description: 获取JSON + parameters: + - description: roleId + in: path + name: roleId + type: string + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 获取Role数据 + tags: + - 角色/Role + put: + consumes: + - application/json + description: 获取JSON + parameters: + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysRoleUpdateReq' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 修改用户角色 + tags: + - 角色/Role + /api/v1/server-monitor: + get: + description: 获取JSON + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 系统信息 + tags: + - 系统信息 + /api/v1/set-config: + get: + consumes: + - application/json + description: 界面操作设置配置值的获取 + responses: + "200": + description: '{"code": 200, "message": "修改成功"}' + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + additionalProperties: true + type: object + type: object + security: + - Bearer: [] + summary: 获取配置 + tags: + - 配置管理 + put: + consumes: + - application/json + description: 界面操作设置配置值 + parameters: + - description: body + in: body + name: data + required: true + schema: + items: + $ref: '#/definitions/dto.GetSetSysConfigReq' + type: array + responses: + "200": + description: '{"code": 200, "message": "修改成功"}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 设置配置 + tags: + - 配置管理 + /api/v1/sys-api: + delete: + description: 删除接口管理 + parameters: + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysApiDeleteReq' + responses: + "200": + description: '{"code": 200, "message": "删除成功"}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 删除接口管理 + tags: + - 接口管理 + get: + description: 获取接口管理列表 + parameters: + - description: 名称 + in: query + name: name + type: string + - description: 标题 + in: query + name: title + type: string + - description: 地址 + in: query + name: path + type: string + - description: 类型 + in: query + name: action + type: string + - description: 页条数 + in: query + name: pageSize + type: integer + - description: 页码 + in: query + name: pageIndex + type: integer + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + allOf: + - $ref: '#/definitions/response.Page' + - properties: + list: + items: + $ref: '#/definitions/models.SysApi' + type: array + type: object + type: object + security: + - Bearer: [] + summary: 获取接口管理列表 + tags: + - 接口管理 + /api/v1/sys-api/{id}: + get: + description: 获取接口管理 + parameters: + - description: id + in: path + name: id + type: string + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/models.SysApi' + type: object + security: + - Bearer: [] + summary: 获取接口管理 + tags: + - 接口管理 + put: + consumes: + - application/json + description: 修改接口管理 + parameters: + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysApiUpdateReq' + responses: + "200": + description: '{"code": 200, "message": "修改成功"}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 修改接口管理 + tags: + - 接口管理 + /api/v1/sys-config: + delete: + description: 删除配置管理 + parameters: + - description: ids + in: body + name: ids + schema: + items: + type: integer + type: array + responses: + "200": + description: '{"code": 200, "message": "删除成功"}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 删除配置管理 + tags: + - 配置管理 + get: + description: 获取配置管理列表 + parameters: + - description: 名称 + in: query + name: configName + type: string + - description: key + in: query + name: configKey + type: string + - description: 类型 + in: query + name: configType + type: string + - description: 是否前端 + in: query + name: isFrontend + type: integer + - description: 页条数 + in: query + name: pageSize + type: integer + - description: 页码 + in: query + name: pageIndex + type: integer + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + allOf: + - $ref: '#/definitions/response.Page' + - properties: + list: + items: + $ref: '#/definitions/models.SysApi' + type: array + type: object + type: object + security: + - Bearer: [] + summary: 获取配置管理列表 + tags: + - 配置管理 + post: + consumes: + - application/json + description: 创建配置管理 + parameters: + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysConfigControl' + responses: + "200": + description: '{"code": 200, "message": "创建成功"}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 创建配置管理 + tags: + - 配置管理 + /api/v1/sys-config/{id}: + get: + description: 根据Key获取SysConfig的Service + parameters: + - description: configKey + in: path + name: configKey + type: string + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + allOf: + - $ref: '#/definitions/response.Response' + - properties: + data: + $ref: '#/definitions/dto.SysConfigByKeyReq' + type: object + security: + - Bearer: [] + summary: 根据Key获取SysConfig的Service + tags: + - 配置管理 + put: + consumes: + - application/json + description: 修改配置管理 + parameters: + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysConfigControl' + responses: + "200": + description: '{"code": 200, "message": "修改成功"}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 修改配置管理 + tags: + - 配置管理 + /api/v1/sys-login-log: + delete: + description: 登录日志删除 + parameters: + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysLoginLogDeleteReq' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 登录日志删除 + tags: + - 登录日志 + get: + description: 获取JSON + parameters: + - description: 用户名 + in: query + name: username + type: string + - description: ip地址 + in: query + name: ipaddr + type: string + - description: 归属地 + in: query + name: loginLocation + type: string + - description: 状态 + in: query + name: status + type: string + - description: 开始时间 + in: query + name: beginTime + type: string + - description: 结束时间 + in: query + name: endTime + type: string + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 登录日志列表 + tags: + - 登录日志 + /api/v1/sys-login-log/{id}: + get: + description: 获取JSON + parameters: + - description: id + in: path + name: id + type: string + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 登录日志通过id获取 + tags: + - 登录日志 + /api/v1/sys-opera-log: + delete: + description: 删除数据 + parameters: + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysOperaLogDeleteReq' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 删除操作日志 + tags: + - 操作日志 + get: + description: 获取JSON + parameters: + - description: title + in: query + name: title + type: string + - description: method + in: query + name: method + type: string + - description: requestMethod + in: query + name: requestMethod + type: string + - description: operUrl + in: query + name: operUrl + type: string + - description: operIp + in: query + name: operIp + type: string + - description: status + in: query + name: status + type: string + - description: beginTime + in: query + name: beginTime + type: string + - description: endTime + in: query + name: endTime + type: string + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 操作日志列表 + tags: + - 操作日志 + /api/v1/sys-opera-log/{id}: + get: + description: 获取JSON + parameters: + - description: id + in: path + name: id + type: string + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 操作日志通过id获取 + tags: + - 操作日志 + /api/v1/sys-user: + get: + description: 获取JSON + parameters: + - description: username + in: query + name: username + type: string + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + type: string + security: + - Bearer: [] + summary: 列表用户信息数据 + tags: + - 用户 + post: + consumes: + - application/json + description: 获取JSON + parameters: + - description: 用户数据 + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysUserInsertReq' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 创建用户 + tags: + - 用户 + /api/v1/sys-user/{userId}: + delete: + description: 删除数据 + parameters: + - description: userId + in: path + name: userId + required: true + type: integer + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 删除用户数据 + tags: + - 用户 + get: + description: 获取JSON + parameters: + - description: 用户编码 + in: path + name: userId + required: true + type: integer + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 获取用户 + tags: + - 用户 + put: + consumes: + - application/json + description: 获取JSON + parameters: + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.SysUserUpdateReq' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 修改用户数据 + tags: + - 用户 + /api/v1/sys/tables/info: + post: + consumes: + - application/json + description: 添加表结构 + parameters: + - description: tableName / 数据表名称 + in: query + name: tables + type: string + responses: + "200": + description: '{"code": -1, "message": "添加失败"}' + schema: + type: string + security: + - Bearer: [] + summary: 添加表结构 + tags: + - 工具 / 生成工具 + put: + consumes: + - application/json + description: 修改表结构 + parameters: + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/tools.SysTables' + responses: + "200": + description: '{"code": -1, "message": "添加失败"}' + schema: + type: string + security: + - Bearer: [] + summary: 修改表结构 + tags: + - 工具 / 生成工具 + /api/v1/sys/tables/info/{tableId}: + delete: + description: 删除表结构 + parameters: + - description: tableId + in: path + name: tableId + required: true + type: integer + responses: + "200": + description: '{"code": -1, "message": "删除失败"}' + schema: + type: string + summary: 删除表结构 + tags: + - 工具 / 生成工具 + get: + description: 获取JSON + parameters: + - description: configKey + in: path + name: configKey + required: true + type: integer + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 获取配置 + tags: + - 工具 / 生成工具 + /api/v1/sys/tables/page: + get: + description: 生成表分页列表 + parameters: + - description: tableName / 数据表名称 + in: query + name: tableName + type: string + - description: pageSize / 页条数 + in: query + name: pageSize + type: integer + - description: pageIndex / 页码 + in: query + name: pageIndex + type: integer + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + summary: 分页列表数据 + tags: + - 工具 / 生成工具 + /api/v1/user/avatar: + post: + consumes: + - multipart/form-data + description: 获取JSON + parameters: + - description: file + in: formData + name: file + required: true + type: file + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 修改头像 + tags: + - 个人中心 + /api/v1/user/profile: + get: + description: 获取JSON + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 获取个人中心用户 + tags: + - 个人中心 + /api/v1/user/pwd/reset: + put: + consumes: + - application/json + description: 获取JSON + parameters: + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.ResetSysUserPwdReq' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 重置用户密码 + tags: + - 用户 + /api/v1/user/pwd/set: + put: + consumes: + - application/json + description: 获取JSON + parameters: + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.PassWord' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 修改密码 + tags: + - 用户 + /api/v1/user/status: + put: + consumes: + - application/json + description: 获取JSON + parameters: + - description: body + in: body + name: data + required: true + schema: + $ref: '#/definitions/dto.UpdateSysUserStatusReq' + responses: + "200": + description: '{"code": 200, "data": [...]}' + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: 修改用户状态 + tags: + - 用户 + /logout: + post: + consumes: + - application/json + description: 获取token + responses: + "200": + description: '{"code": 200, "msg": "成功退出系统" }' + schema: + type: string + security: + - Bearer: [] + summary: 退出登录 +securityDefinitions: + Bearer: + in: header + name: Authorization + type: apiKey +swagger: "2.0" diff --git a/examples/run.go b/examples/run.go new file mode 100644 index 0000000..2bb61e2 --- /dev/null +++ b/examples/run.go @@ -0,0 +1,28 @@ +// +build examples + +package main + +import ( + "github.com/go-admin-team/go-admin-core/sdk" + "log" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" + + myCasbin "github.com/go-admin-team/go-admin-core/sdk/pkg/casbin" + "gorm.io/driver/mysql" +) + +func main() { + db, err := gorm.Open(mysql.Open("root:123456@tcp/inmg?charset=utf8&parseTime=True&loc=Local"), &gorm.Config{}) + if err != nil { + panic(err) + } + syncEnforce := myCasbin.Setup(db, "sys_") + sdk.Runtime.SetDb("*", db) + sdk.Runtime.SetCasbin("*", syncEnforce) + + e := gin.Default() + sdk.Runtime.SetEngine(e) + log.Fatal(e.Run(":8000")) +} diff --git a/go-admin-db.db b/go-admin-db.db new file mode 100644 index 0000000..fe73e03 Binary files /dev/null and b/go-admin-db.db differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5e7b47b --- /dev/null +++ b/go.mod @@ -0,0 +1,185 @@ +module go-admin + +go 1.21 + +require ( + github.com/alibaba/sentinel-golang v1.0.4 + github.com/alibaba/sentinel-golang/pkg/adapters/gin v0.0.0-20230626085943-08071855bc67 + github.com/aliyun/aliyun-oss-go-sdk v3.0.1+incompatible + github.com/bitly/go-simplejson v0.5.1 + github.com/bwmarrin/snowflake v0.3.0 + github.com/bytedance/go-tagexpr/v2 v2.9.11 + github.com/bytedance/sonic v1.12.6 + github.com/casbin/casbin/v2 v2.77.2 + github.com/forgoer/openssl v1.6.0 + github.com/gin-gonic/gin v1.9.1 + github.com/go-admin-team/go-admin-core v1.5.2-0.20231103105356-84418ed9252c + github.com/go-admin-team/go-admin-core/sdk v1.5.2-0.20231103105356-84418ed9252c + github.com/go-redis/redis/v8 v8.11.5 + github.com/golang-jwt/jwt/v5 v5.0.0 + github.com/google/uuid v1.4.0 + github.com/gorilla/websocket v1.5.0 + github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.9+incompatible + github.com/jinzhu/copier v0.4.0 + github.com/json-iterator/go v1.1.12 + github.com/juju/ratelimit v1.0.2 + github.com/mailjet/mailjet-apiv3-go v0.0.0-20201009050126-c24bc15a9394 + github.com/mitchellh/mapstructure v1.1.2 + github.com/mssola/user_agent v0.6.0 + github.com/opentracing/opentracing-go v1.2.0 + github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/pkg/errors v0.9.1 + github.com/prometheus/client_golang v1.17.0 + github.com/qiniu/go-sdk/v7 v7.18.2 + github.com/redis/go-redis/v9 v9.3.0 + github.com/robfig/cron/v3 v3.0.1 + github.com/rs/xid v1.2.1 + github.com/shirou/gopsutil/v3 v3.23.10 + github.com/shopspring/decimal v1.2.0 + github.com/spf13/cobra v1.7.0 + github.com/swaggo/files v1.0.1 + github.com/swaggo/gin-swagger v1.6.0 + github.com/swaggo/swag v1.16.2 + github.com/unrolled/secure v1.13.0 + github.com/valyala/fasthttp v1.58.0 + github.com/vmihailenco/msgpack/v5 v5.4.1 + go.uber.org/zap v1.26.0 + golang.org/x/crypto v0.29.0 + golang.org/x/net v0.31.0 + gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df + gorm.io/driver/mysql v1.5.2 + gorm.io/driver/postgres v1.5.4 + gorm.io/driver/sqlite v1.5.4 + gorm.io/driver/sqlserver v1.5.2 + gorm.io/gorm v1.25.5 +) + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/AlecAivazis/survey/v2 v2.3.6 // indirect + github.com/BurntSushi/toml v1.3.2 // indirect + github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/andeya/ameda v1.5.3 // indirect + github.com/andeya/goutil v1.0.1 // indirect + github.com/andybalholm/brotli v1.1.1 // indirect + github.com/andygrunwald/go-jira v1.16.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bsm/redislock v0.9.4 // indirect + github.com/bytedance/sonic/loader v0.2.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chanxuehong/rand v0.0.0-20211009035549-2f07823e8e99 // indirect + github.com/chanxuehong/wechat v0.0.0-20230222024006-36f0325263cd // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/fatih/color v1.15.0 // indirect + github.com/fatih/structs v1.1.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/git-chglog/git-chglog v0.15.4 // indirect + github.com/go-admin-team/go-admin-core/plugins/logger/zap v1.3.5-rc.0.0.20231103105142-2d9e40ec6f71 // indirect + github.com/go-admin-team/gorm-adapter/v3 v3.7.8-0.20220809100335-eaf9f67b3d21 // indirect + github.com/go-admin-team/redis-watcher/v2 v2.0.0-20231102130416-bfe327cac940 // indirect + github.com/go-admin-team/redisqueue/v2 v2.0.1-0.20231102124201-508101cc789a // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.15.5 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/huandu/xstrings v1.3.3 // indirect + github.com/imdario/mergo v0.3.13 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.4.3 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/kyokomi/emoji/v2 v2.2.11 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.17 // indirect + github.com/mattn/goveralls v0.0.12 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect + github.com/microsoft/go-mssqldb v1.6.0 // indirect + github.com/mitchellh/copystructure v1.0.0 // indirect + github.com/mitchellh/reflectwalk v1.0.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mojocn/base64Captcha v1.3.5 // indirect + github.com/nsqio/go-nsq v1.1.0 // indirect + github.com/nyaruka/phonenumbers v1.0.55 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shamsher31/goimgext v1.0.0 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/tidwall/gjson v1.14.4 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/trivago/tgo v1.0.7 // indirect + github.com/tsuyoshiwada/go-gitcmd v0.0.0-20180205145712-5f1f5f9475df // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + github.com/urfave/cli/v2 v2.24.3 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/image v0.13.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/term v0.26.0 // indirect + golang.org/x/text v0.20.0 // indirect + golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/plugin/dbresolver v1.4.7 // indirect +) + +//replace ( +// github.com/go-admin-team/go-admin-core v1.4.0 => ../../go-admin-core +// github.com/go-admin-team/go-admin-core/sdk v1.4.0 => ../../go-admin-core/sdk +//) diff --git a/main.go b/main.go new file mode 100644 index 0000000..e25407e --- /dev/null +++ b/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "go-admin/cmd" +) + +//go:generate swag init --parseDependency --parseDepth=6 --instanceName admin -o ./docs/admin + +// @title go-admin API +// @version 2.0.0 +// @description 基于Gin + Vue + Element UI的前后端分离权限管理系统的接口文档 +// @description 添加qq群: 521386980 进入技术交流群 请先star,谢谢! +// @license.name MIT +// @license.url https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md + +// @securityDefinitions.apikey Bearer +// @in header +// @name Authorization +func main() { + cmd.Execute() +} diff --git a/models/binancedto/binance.go b/models/binancedto/binance.go new file mode 100644 index 0000000..da9423c --- /dev/null +++ b/models/binancedto/binance.go @@ -0,0 +1,24 @@ +package binancedto + +import ( + "time" + + "github.com/shopspring/decimal" +) + +type UserSubscribeState struct { + SpotLastTime *time.Time `json:"spotLastTime" comment:"现货websocket最后通信时间"` + FuturesLastTime *time.Time `json:"futuresLastTime" comment:"合约websocket最后通信时间"` +} + +type OpenOrder struct { + Price decimal.Decimal `json:"price"` //均价 + Quantity decimal.Decimal `json:"quantity"` //数量 + Status string `json:"status"` //币安原始状态 "NEW", "PARTIALLY_FILLED", "FILLED", "CANCELED", "PENDING_CANCEL", "REJECTED", "EXPIRED" +} + +type StoplossMarket struct { + Pid int `json:"pid"` + Symbol string `json:"symbol"` + Type int `json:"type" comment:"对冲类型 1-现货 2-合约"` +} diff --git a/models/coin.go b/models/coin.go new file mode 100644 index 0000000..4ac17f4 --- /dev/null +++ b/models/coin.go @@ -0,0 +1,44 @@ +package models + +// Coin vts_coin 币种表信息 +type Coin struct { + ID int `db:"id"` + IsOpenTran int `db:"isopentran"` //是否开启提币:1开启,2关闭 + IsTrade int `db:"istrade"` //是否开启合约交易,1开启,0未开启 + CoinName string `db:"coinname"` //币种名称 + CoinCode string `db:"coincode"` //币种代号 + Url string `db:"url"` //钱包接口url + MinChargeNum float64 `db:"minchargenum"` //最小充值数量 + MinNum float64 `db:"minnum"` //最小提币数量 + MaxNum float64 `db:"maxnum"` //最大提币转量 +} + +// TradeSet vts_tradeset 交易配置 +type TradeSet struct { + ID int `db:"id" json:"id"` + AmountDigit int `db:"amountdigit"` //基础币种计数精度 + PriceDigit int `db:"pricecdigit" json:"pricecdigit"` //价格小数点位数 + Currency string `db:"currency" json:"currency"` //法币 + Coin string `db:"coin" json:"coin"` //币种 + PriceChange float64 `db:"pricechange" json:"pricechange"` //价格波动价位 + MinBuyVal float64 `db:"minbuyval"` //最小下单金额 + OpenPrice float64 `db:"openprice"` //开盘价格 + LastPrice string `json:"last"` //最新价格 + HighPrice string `json:"high"` //24小时最高价 + LowPrice string `json:"low"` //24小时最低价 + Volume string `json:"volume"` //24小时成数量 + QuoteVolume string `json:"quote"` //24小时成交金额 + MinQty float64 `json:"minQty"` //最小交易数量 + MaxQty float64 `json:"maxQty"` //最大交易数量 + MaxNotional string `json:"MaxNotional` //最大名义价值 + MinNotional string `json:"MinNotional` //最大名义价值 + E int64 `json:"-"` //推送时间 +} + +//CommissionType int `db:"commissiontype"` //手续费:1买,2卖,3双向 +//DepositNum float64 `db:"depositnum" json:"depositnum"` //保证金规模(手) +//ForceRate float64 `db:"forcerate" json:"forcerate"` //维持保证金率1% +//OverNighRate float64 `db:"overnighrate" json:"overnighrate"` //隔夜费用比率 +//MinBuyNum int `db:"minbuynum" json:"minbuynum"` //最小购买多少手 +// MaxBuyNum int `db:"maxbuynum" json:"maxbuynum"` //最大购买多少手 +//HandNum int `db:"handnum" json:"handnum"` //1手多少数量usdt diff --git a/models/coingatedto/coingatedto.go b/models/coingatedto/coingatedto.go new file mode 100644 index 0000000..6199fa4 --- /dev/null +++ b/models/coingatedto/coingatedto.go @@ -0,0 +1,76 @@ +package coingatedto + +import "github.com/shopspring/decimal" + +type OrderRequest struct { + OrderID string `json:"order_id"` // 订单ID + PriceAmount decimal.Decimal `json:"price_amount"` // 价格金额 + PriceCurrency string `json:"price_currency"` // 价格货币 + ReceiveCurrency string `json:"receive_currency"` // 接收货币 + Title string `json:"title"` // 标题 + Description string `json:"description"` // 描述 + CallbackURL string `json:"callback_url"` // 回调URL + CancelUrl string `json:"cancel_url"` //取消跳转url + SuccessUrl string `json:"success_url"` //成功跳转地址 + // OrderID:PriceAmount:PriceCurrency 然后在rsa加密 + Token string `json:"token"` //回调token + PurchaserEmail string `json:"purchaser_email"` //付款方邮箱 + +} + +type OrderResponse struct { + ID int `json:"id"` // 订单ID + Status string `json:"status"` // 订单状态 + Title string `json:"title"` // 标题 + DoNotConvert bool `json:"do_not_convert"` // 不转换 + OrderableType string `json:"orderable_type"` // 可订购类型 + OrderableID int `json:"orderable_id"` // 可订购ID + UUID string `json:"uuid"` // UUID + PaymentGateway *string `json:"payment_gateway"` // 支付网关 (可为 nil) + PriceCurrency string `json:"price_currency"` // 价格货币 + PriceAmount string `json:"price_amount"` // 价格金额 + LightningNetwork bool `json:"lightning_network"` // 闪电网络 + ReceiveCurrency string `json:"receive_currency"` // 接收货币 + ReceiveAmount string `json:"receive_amount"` // 接收金额 + CreatedAt string `json:"created_at"` // 创建时间 + OrderID string `json:"order_id"` // 订单ID + PaymentURL string `json:"payment_url"` // 支付URL + UnderpaidAmount string `json:"underpaid_amount"` // 欠款金额 + OverpaidAmount string `json:"overpaid_amount"` // 超额支付金额 + IsRefundable bool `json:"is_refundable"` // 是否可退款 + PaymentRequestURI *string `json:"payment_request_uri"` // 支付请求URI (可为 nil) + Refunds []interface{} `json:"refunds"` // 退款信息 + Voids []interface{} `json:"voids"` // 作废信息 + Fees []interface{} `json:"fees"` // 费用信息 + BlockchainTransactions []interface{} `json:"blockchain_transactions"` // 区块链交易信息 + Token string `json:"token"` // 令牌 +} + +type OrderCallBackResponse struct { + ID int `json:"id" form:"id"` // 订单ID + OrderID string `json:"order_id" form:"order_id"` // 订单号 + Status string `json:"status" form:"status"` // 订单状态 + PayAmount string `json:"pay_amount" form:"pay_amount"` // 支付金额 + PayCurrency string `json:"pay_currency" form:"pay_currency"` // 支付货币 + PriceAmount string `json:"price_amount" form:"price_amount"` // 价格金额 + PriceCurrency string `json:"price_currency" form:"price_currency"` // 价格货币 + ReceiveCurrency string `json:"receive_currency" form:"receive_currency"` // 接收货币 + ReceiveAmount string `json:"receive_amount" form:"receive_amount"` // 接收金额 + CreatedAt string `json:"created_at" form:"create_at"` // 创建时间 + Token string `json:"token" form:"token"` // 令牌 + UnderpaidAmount string `json:"underpaid_amount" form:"underpaid_amount"` // 欠款金额 + OverpaidAmount string `json:"overpaid_amount" form:"overpaid_amount"` // 超额支付金额 + IsRefundable bool `json:"is_refundable" form:"is_refundable"` // 是否可退款 + Fees []OrderCallBackFee `json:"fees" form:"fees"` // 费用信息 +} + +type OrderCallBackFee struct { + Type string `json:"type"` // 费用类型 + Amount string `json:"amount"` // 费用金额 + Currency OrderCallCurrency `json:"currency"` // 货币信息 {"id":1,"symbol":"BTC"} +} + +type OrderCallCurrency struct { + ID int `json:"id"` // 货币ID + Symbol string `json:"symbol"` // 货币符号 +} diff --git a/models/commondto/websocket.go b/models/commondto/websocket.go new file mode 100644 index 0000000..6cf824c --- /dev/null +++ b/models/commondto/websocket.go @@ -0,0 +1,9 @@ +package commondto + +import "time" + +type WebSocketErr struct { + Count int `json:"count"` + ErrorMessage string `json:"message"` + Time time.Time `json:"time"` +} diff --git a/models/deal.go b/models/deal.go new file mode 100644 index 0000000..a024d70 --- /dev/null +++ b/models/deal.go @@ -0,0 +1,121 @@ +package models + +import ( + "time" +) + +// Deal vts_deal 成交记录表 +type Deal struct { + ID int `db:"id"` + UserID int `db:"userid"` + CurrencyID int `db:"currencyid"` // + CoinID int `db:"coinid"` // + OrderType int `db:"ordertype"` //订单类型:1限价,2限价止盈止损,3市价 + BuyType int `db:"buytype"` //买卖类型:1买,2卖 + IsTrigger int `db:"istrigger"` //是否触发撮合-是:1,否:2 + MatchID int64 `db:"matchid"` //撮合id + OrderNO int64 `db:"orderno"` //订单号 + CreateDateInt int64 `db:"-"` //成交日期 + EntrustDateInt int64 `db:"-"` //委托时间 + DealNO string `db:"dealno"` //成交单号 + Price float64 `db:"price"` //成交价格 + Num float64 `db:"num"` //成交数量 + Poundage float64 `db:"poundage"` //手续费 + TotalAmt float64 `db:"totalamt"` //总金额 + SurAmt float64 `db:"suramt"` //剩余金额 + BuyInPrice float64 `db:"buyinprice"` //买卖方原始价格 + FreezeAmt float64 `db:"freezeamt"` // + CreateDate time.Time `db:"createdate"` //成交日期 + EntrustDate time.Time `db:"entrustdate"` //委托时间 +} + +// DealStatistics vts_dealstatistics 成交量和最新最大最小价格统计 +type DealStatistics struct { + ID int `db:"id"` + CurrencyID int `db:"currencyid"` + CoinID int `db:"coinid"` + NewPrice float64 `db:"newprice"` //最新价格 + HighPrice float64 `db:"highprice"` //24小时最高价 + LowPrice float64 `db:"lowprice"` //24小时最小价格 + DealNum float64 `db:"dealnum"` //24小时成交量 + DealAmt float64 `db:"dealamt"` //24小时成交金额 + OpenPrice float64 `db:"openprice"` //开盘价 + Rate float64 `db:"rate"` //涨幅--未乘百分比 + Change float64 `db:"-"` + DealDate int64 `db:"-"` +} + +// DealKLine 发送k线消息队列实体 +type DealKLine struct { + CurrencyID int + CoinID int + IsTrigger int //是否触发撮合-是:1,否:2 + CreateDateInt int64 //成交日期 + Price float64 + Num float64 //成交数量 + TotalAmt float64 //总金额 + Poundage float64 + CreateDate time.Time //成交日期 +} + +// KLine vts_kline model +type KLine struct { + CurrencyID int `db:"currencyid"` + CoinID int `db:"coinid"` + KlineType int `db:"klinetype"` //kline类型:对应 KlineTypeMap的值 + Opened float64 `db:"opened"` //开盘 + Highest float64 `db:"highest"` //最高 + Lowest float64 `db:"lowest"` //最低 + Closed float64 `db:"closed"` //收盘 + DNum float64 `db:"dnum"` //成交 + DAmt float64 `db:"damt"` //金额 + DealDate time.Time `db:"dealdate"` //时间 + IsUpdate bool `db:"-"` +} + +//KlineType int `db:"klinetype"` //kline类型:1--分时线,2--1分线,3--5分线,4--15分线,5--30分线,6--60分线,7--日线,8--周线,9--月线线,10--交易行情 +//KlineType string `db:"klinetype"` //kline类型:1--分时线,2--1分线,3--5分线,4--15分线,5--30分线,6--60分线,7--日线,8--周线,9--月线线,10--交易行情 + +type DbDealDataSend struct { + Deals []Deal +} + +type BalanceUpdate struct { + Event string `json:"e"` // + Data int `json:"data"` // +} + +type ErrCode struct { + Event string `json:"e"` //错误的订阅 + Data string `json:"data"` // +} + +// BuySellFive set five item +type BuySellFive struct { + Price float64 + Num float64 +} + +// DealDay 行情成交记录表 vts_dealday +type DealDay struct { + ID int `db:"id"` + CurrencyID int `db:"currencyid"` // + CoinID int `db:"coinid"` // + IsTrigger int `db:"istrigger"` //是否触发撮合-是:1,否:2 + Price float64 `db:"price"` //成交价格 + Num float64 `db:"num"` //成交数量 + CreateDate time.Time `db:"createdate"` //成交日期 +} + +// PushNewDealNew Push new deal to redis +type PushNewDealNew struct { + Type int `json:"type"` + Symbol string `json:"symbol"` //交易对,比如BTC-USDT + Data [][]string `json:"data"` +} + +type PushNewDealNewStr struct { + Type int `json:"type"` + Symbol string `json:"symbol"` //交易对,比如BTC-USDT + Data []string `json:"data"` +} diff --git a/models/enums/klineconst.go b/models/enums/klineconst.go new file mode 100644 index 0000000..aa23861 --- /dev/null +++ b/models/enums/klineconst.go @@ -0,0 +1,67 @@ +package enums + +//k线周期 +const ( + KLINE_1MIN = 1 + iota + KLINE_3MIN + KLINE_5MIN + KLINE_15MIN + KLINE_30MIN + KLINE_1H + KLINE_2H + KLINE_4H + KLINE_6H + KLINE_8H + KLINE_12H + KLINE_1DAY + KLINE_3DAY + KLINE_1WEEK + KLINE_1MONTH + KLINE_1YEAR +) + +var ( + ExchangeBinanceId = 2 //币安交易所id + ExchangeHuoBiId = 1 //火币交易所id + + //KlineTypeMap k类型对应整型数值 + KlineTypeMap = map[string]int{ + "1m": KLINE_1MIN, + "3m": KLINE_3MIN, + "5m": KLINE_5MIN, + "15m": KLINE_15MIN, + "30m": KLINE_30MIN, + "1h": KLINE_1H, + "2h": KLINE_2H, + "4h": KLINE_4H, + "6h": KLINE_6H, + "8h": KLINE_8H, + "12h": KLINE_12H, + "1d": KLINE_1DAY, + "3d": KLINE_3DAY, + "1w": KLINE_1WEEK, + "1M": KLINE_1MONTH, + } + + //KlineTypeStringMap k类型对应字符串数值 + KlineTypeStringMap = map[int]string{ + KLINE_1MIN: "1m", + KLINE_3MIN: "3m", + KLINE_5MIN: "5m", + KLINE_15MIN: "15m", + KLINE_30MIN: "30m", + KLINE_1H: "1h", + KLINE_2H: "2h", + KLINE_4H: "4h", + KLINE_6H: "6h", + KLINE_8H: "8h", + KLINE_12H: "12h", + KLINE_1DAY: "1d", + KLINE_3DAY: "3d", + KLINE_1WEEK: "1w", + KLINE_1MONTH: "1M", + } +) + +//KLINE_PERIOD_1H +//KLINE_PERIOD_3H diff --git a/models/futuresdto/futuresdto.go b/models/futuresdto/futuresdto.go new file mode 100644 index 0000000..fca4e93 --- /dev/null +++ b/models/futuresdto/futuresdto.go @@ -0,0 +1,172 @@ +package futuresdto + +type FutureTicker24h struct { + Symbol string `json:"symbol"` // 交易对符号 (e.g., BTCUSDT) + PriceChange string `json:"priceChange"` // 24小时价格变动 + PriceChangePercent string `json:"priceChangePercent"` // 24小时价格变动百分比 + WeightedAvgPrice string `json:"weightedAvgPrice"` // 加权平均价 + LastPrice string `json:"lastPrice"` // 最近一次成交价 + LastQty string `json:"lastQty"` // 最近一次成交额 + OpenPrice string `json:"openPrice"` // 24小时内第一次成交的价格 + HighPrice string `json:"highPrice"` // 24小时最高价 + LowPrice string `json:"lowPrice"` // 24小时最低价 + Volume string `json:"volume"` // 24小时成交量 + QuoteVolume string `json:"quoteVolume"` // 24小时成交额 + OpenTime int64 `json:"openTime"` // 24小时内,第一笔交易的发生时间 (Unix timestamp) + CloseTime int64 `json:"closeTime"` // 24小时内,最后一笔交易的发生时间 (Unix timestamp) + FirstId int `json:"firstId"` // 首笔成交id + LastId int `json:"lastId"` // 末笔成交id + Count int `json:"count"` // 成交笔数 +} + +type FundingInfo struct { + Symbol string `json:"symbol"` // 交易对符号 (e.g., ORBSUSDT) + MarkPrice string `json:"markPrice"` // 标记价格 + IndexPrice string `json:"indexPrice"` // 指数价格 + EstimatedSettlePrice string `json:"estimatedSettlePrice"` // 预计结算价格 + LastFundingRate string `json:"lastFundingRate"` // 最后一次资金费率 + InterestRate string `json:"interestRate"` // 利率 + NextFundingTime int64 `json:"nextFundingTime"` // 下次资金费用时间 (Unix timestamp) + // Time int64 `json:"time"` // 当前时间 (Unix timestamp) +} + +type RateLimit struct { + Interval string `json:"interval"` // 限制时间间隔 (e.g., MINUTE) + IntervalNum int `json:"intervalNum"` // 间隔数量 (e.g., 1) + Limit int `json:"limit"` // 限制次数 + RateLimitType string `json:"rateLimitType"` // 限制类型 (e.g., REQUEST_WEIGHT) +} + +type Asset struct { + Asset string `json:"asset"` // 资产名称 (e.g., BUSD) + MarginAvailable bool `json:"marginAvailable"` // 是否可用作保证金 + AutoAssetExchange *string `json:"autoAssetExchange"` // 自动兑换阈值 (可选) +} + +type Filter struct { + FilterType string `json:"filterType"` // 过滤器类型 (e.g., PRICE_FILTER) + MaxPrice *string `json:"maxPrice,omitempty"` // 最大价格 (可选) + MinPrice *string `json:"minPrice,omitempty"` // 最小价格 (可选) + TickSize *string `json:"tickSize,omitempty"` // 最小价格间隔 (可选) + MaxQty *string `json:"maxQty,omitempty"` // 最大数量 (可选) + MinQty *string `json:"minQty,omitempty"` // 最小数量 (可选) + StepSize *string `json:"stepSize,omitempty"` // 最小数量间隔 (可选) + Limit *int `json:"limit,omitempty"` // 限制 (可选) + Notional *string `json:"notional,omitempty"` // 最小名义价值 (可选) + MultiplierUp *string `json:"multiplierUp,omitempty"` // 价格上限百分比 (可选) + MultiplierDown *string `json:"multiplierDown,omitempty"` // 价格下限百分比 (可选) + // MultiplierDecimal *int `json:"multiplierDecimal,omitempty"` // 小数位数 (可选) +} + +type FutSymbol struct { + Symbol string `json:"symbol"` // 交易对 (e.g., BLZUSDT) + Pair string `json:"pair"` // 标的交易对 + ContractType string `json:"contractType"` // 合约类型 + DeliveryDate int64 `json:"deliveryDate"` // 交割日期 (Unix timestamp) + OnboardDate int64 `json:"onboardDate"` // 上线日期 (Unix timestamp) + Status string `json:"status"` // 交易对状态 (e.g., TRADING) + BaseAsset string `json:"baseAsset"` // 标的资产 + QuoteAsset string `json:"quoteAsset"` // 报价资产 + MarginAsset string `json:"marginAsset"` // 保证金资产 + PricePrecision int `json:"pricePrecision"` // 价格小数点位数 + QuantityPrecision int `json:"quantityPrecision"` // 数量小数点位数 + BaseAssetPrecision int `json:"baseAssetPrecision"` // 标的资产精度 + QuotePrecision int `json:"quotePrecision"` // 报价资产精度 + UnderlyingType string `json:"underlyingType"` // 标的类型 + // UnderlyingSubType []string `json:"underlyingSubType"` // 子类型 + // SettlePlan int `json:"settlePlan"` // 结算计划 + TriggerProtect string `json:"triggerProtect"` // 触发保护阈值 + Filters []Filter `json:"filters"` // 过滤器列表 + // OrderType []string `json:"OrderType"` // 订单类型 + // TimeInForce []string `json:"timeInForce"` // 有效方式 + LiquidationFee string `json:"liquidationFee"` // 强平费率 + MarketTakeBound string `json:"marketTakeBound"` // 市价吃单最大偏离比例 +} + +type FutExchangeInfo struct { + // ExchangeFilters []interface{} `json:"exchangeFilters"` // 交易所过滤器 + RateLimits []RateLimit `json:"rateLimits"` // API访问限制 + ServerTime int64 `json:"serverTime"` // 服务器时间 (Unix timestamp) + Assets []Asset `json:"assets"` // 资产信息 + Symbols []FutSymbol `json:"symbols"` // 交易对信息 + Timezone string `json:"timezone"` // 服务器时区 +} + +// AllTickersResp 获取所有合约币--app +type AllTickersResp struct { + SymbolId int `json:"symbol_id"` // 交易对id + CoinId int `json:"coin_id"` // 交易币id + Coin string `json:"coin"` // 交易币 + PriceDigit int `json:"price_digit"` // 价格小数点位数 + AmountDigit int32 `json:"amount_digit"` // 基础币种计数精度 + Multiplier int `json:"multiplier"` // 合约乘数 + NewPrice string `json:"new_price"` // 最新价格 + DealAmt string `json:"deal_amt"` // 24小时成交总额-usd + DealNum string `json:"deal_num"` // 24小时成交总额-张 + DealCoin string `json:"deal_coin"` // 24小时成交量-币 + Ratio string `json:"ratio"` // 涨跌幅 + MaxLever int `json:"max_lever"` //最大杠杆倍数 + UserLike int `json:"user_like"` // 是否自选 + FutType int `json:"fut_type"` // 数据类型 1==U本位数据 2==币本位数据 +} + +// AllTickersRespPc 获取所有合约币--pc +type AllTickersRespPc struct { + SymbolId int `json:"symbol_id"` // 交易对id + CoinId int `json:"coin_id"` // 交易币id + Coin string `json:"coin"` // 交易币 + PriceDigit int `json:"price_digit"` // 价格小数点位数 + UserLike int `json:"user_like"` // 是否自选 + AmountDigit int32 `json:"amount_digit"` // 基础币种计数精度 + NewPrice string `json:"new_price"` // 最新价格 + DealAmt string `json:"deal_amt"` // 24小时成交总额-usd + DealNum string `json:"deal_num"` // 24小时成交总额-张 + DealCoin string `json:"deal_coin"` // 24小时成交量-币 + Ratio string `json:"ratio"` // 涨跌幅 + HighPrice string `json:"high_price"` // 24小时最高价 + LowPrice string `json:"low_price"` // 24小时最小价格 + DisplaySort int `json:"display_sort"` // 排序号 + FutType int `json:"fut_type"` // 数据类型 1==U本位数据 2==币本位数据 +} + +// MarketResp 获取单个合约币返回 +type MarketResp struct { + SymbolId int `json:"symbol_id"` // 交易对id + CoinId int `json:"coin_id"` // 交易币id + Coin string `json:"coin"` // 交易币 + PriceDigit int `json:"price_digit"` // 价格小数点位数 + AmountDigit int `json:"amount_digit"` // 基础币种计数精度 + USDTDigit int `json:"usdt_digit"` // 基础币种计数精度 + Multiplier int `json:"multiplier"` //合约乘数 + NewPrice string `json:"new_price"` // 最新价格 + MinBuyVal string `json:"min_buy_val"` // 最小下单金额 + Ratio string `json:"ratio"` // 涨跌幅 + ImageStr string `json:"image_str"` // 币种图片 + DetailCode string `json:"detail_code"` // 币种全称 + DealNum string `json:"deal_num"` // 24小时成交量-张 + DealAmt string `json:"deal_amt"` // 24小时成交金额-usd + DealCoin string `json:"deal_coin"` // 24小时成交量-币 + OpenPrice string `json:"open_price"` // 开盘价 + HighPrice string `json:"high_price"` // 24小时最高价 + LowPrice string `json:"low_price"` // 24小时最小价格 + MergeDepth string `json:"merge_depth"` // 合并深度 如:0.1,0.001,0.0001 + MarkPrice string `json:"mark_price"` // 标记价格 + IndexPrice string `json:"index_price"` // 指数价格 + FundRate string `json:"fund_rate"` //资金费率 + UserLike int `json:"user_like"` // 是否自选 + NextFundingSec int `json:"next_funding_sec"` // 距离下次收资金费用时间,多少秒 +} + +type WsKline struct { + Line string `json:"i"` // K线间隔,比如1m + LineTime string `json:"t"` // k线时间撮 + Open string `json:"o"` // 这根K线期间第一笔成交价 + Close string `json:"c"` // 这根K线期间末一笔成交价 + High string `json:"h"` // 这根K线期间最高成交价 + Low string `json:"l"` // 这根K线期间最低成交价 + Volume string `json:"v"` // 这根K线期间成交量 + VolCoin string `json:"vc"` // 成交量-币 + QuoteVolume string `json:"q"` // 这根K线期间成交额 + Chg string `json:"chg"` //涨幅 + Ampl string `json:"ampl"` //震幅 +} diff --git a/models/futuresdto/futuresreceivedto.go b/models/futuresdto/futuresreceivedto.go new file mode 100644 index 0000000..a21d388 --- /dev/null +++ b/models/futuresdto/futuresreceivedto.go @@ -0,0 +1,55 @@ +package futuresdto + +type OrderTriggerReject struct { + Symbol string `json:"s"` + OrderNo int `json:"i"` + Reason string `json:"r"` +} + +type ReceiveBase struct { + EventType string `json:"e"` // 事件类型 + EventTime int64 `json:"E"` // 事件时间 + MatchTime int64 `json:"T"` // 撮合时间 +} + +type OrderTradeUpdate struct { + ReceiveBase + OrderDetails map[string]interface{} `json:"o"` // 订单详情 +} + +type OrderDetails struct { + Symbol string `json:"s"` // 交易对 + ClientOrderId string `json:"c"` // 客户端自定义订单ID + Side string `json:"S"` // 订单方向 + OrderType string `json:"o"` // 订单类型 + TimeInForce string `json:"f"` // 有效方式 + OriginalQuantity string `json:"q"` // 订单原始数量 + OriginalPrice string `json:"p"` // 订单原始价格 + AveragePrice string `json:"ap"` // 订单平均价格 + StopPrice string `json:"sp"` // 条件订单触发价格,对追踪止损单无效 + ExecutionType string `json:"x"` // 本次事件的具体执行类型 + OrderStatus string `json:"X"` // 订单的当前状态 + OrderId int64 `json:"i"` // 订单ID + LastFilledQuantity string `json:"l"` // 订单末次成交量 + CumulativeQuantity string `json:"z"` // 订单累计已成交量 + LastFilledPrice string `json:"L"` // 订单末次成交价格 + CommissionAsset string `json:"N"` // 手续费资产类型 + CommissionAmount string `json:"n"` // 手续费数量 + TransactionTime int64 `json:"T"` // 成交时间 + TradeId int64 `json:"t"` // 成交ID + BidValue string `json:"b"` // 买单净值 + AskValue string `json:"a"` // 卖单净值 + IsMaker bool `json:"m"` // 该成交是作为挂单成交吗? + ReduceOnly bool `json:"R"` // 是否是只减仓单 + TriggerPriceType string `json:"wt"` // 触发价类型 + OriginalOrderType string `json:"ot"` // 原始订单类型 + PositionSide string `json:"ps"` // 持仓方向 + ClosePosition bool `json:"cp"` // 是否为触发平仓单 + ActivationPrice string `json:"AP"` // 追踪止损激活价格 + CallbackRate string `json:"cr"` // 追踪止损回调比例 + TriggerProtection bool `json:"pP"` // 是否开启条件单触发保护 + RealizedProfitLoss string `json:"rp"` // 该交易实现盈亏 + PreventSelfTrade string `json:"V"` // 自成交防止模式 + PriceMatchingMode string `json:"pm"` // 价格匹配模式 + GTDCancelTime int64 `json:"gtd"` // TIF为GTD的订单自动取消时间 +} diff --git a/models/liquidation.go b/models/liquidation.go new file mode 100644 index 0000000..a411c68 --- /dev/null +++ b/models/liquidation.go @@ -0,0 +1,60 @@ +package models + +import "github.com/shopspring/decimal" + +//Liquidation 强平计算公式字段 +type Liquidation struct { + WalletBalance decimal.Decimal //账户余额,钱包余额= 1,535,443.01 + TMM1 decimal.Decimal //(TMM1)其它合约下的全部保证金(除合约1外)= 71200.81144,维持保证金=名义价值*维持保证金率-维持保证金速算額,名义价值=价格*数量,拿标记价格计算 + UPNL1 decimal.Decimal //全部其它合约的未实现盈亏(除合约1外)= -56,249.35 + CumB decimal.Decimal //单向模式下合约1的维持保证金速算額= 135,365.00 + CumL decimal.Decimal //开多合约1下的维持保证金速算額(双向持仓模式)= 0 + CumS decimal.Decimal //开空合约1下的维持保证金速算額(双向持仓模式)= 0 + Side1BOTH int //合约1的方向(单向持仓模式);“1”代表开多持仓;“-1”代表开空持仓= 1 + Position1BOTH decimal.Decimal //合约1的持仓大小(单向持仓模式);无论开多或开空,取绝对值= 3,683.979 + EP1BOTH decimal.Decimal //合约1的头寸价格(单向持仓模式)=1,456.84 + Position1LONG decimal.Decimal //开多仓位大小(双向持仓模式);无论开多或开空,取绝对值= 0 + EP1LONG decimal.Decimal //开多持仓的头寸(双向持仓模式);无论开多或开空,取绝对值= 0 + Position1SHORT decimal.Decimal //开空仓位大小(双向持仓模式);无论开多或开空,取绝对值= 0 + EP1SHORT decimal.Decimal //开空持仓的头寸(双向持仓模式);无论开多或开空,取绝对值= 0 + MMRB decimal.Decimal //单向持仓模式合约的维持保证金费率= 10% + MMRL decimal.Decimal //开多合约的维持保证金费率(双向持仓模式)= 0 + MMRS decimal.Decimal //开空合约的维持保证金费率(双向持仓模式)= 0 +} + +//CalculateLiquidationPrice 计算强平公式 +func (lq Liquidation) CalculateLiquidationPrice() decimal.Decimal { + //d1=WB-TMM1+UPNL1+CumB+CumL+CumS-Side1BOTH*Position1BOTH*EP1BOTH-Position1LONG*EP1LONG+Position1SHORT*EP1SHORT + t1 := lq.WalletBalance + if lq.TMM1.Cmp(decimal.Zero) != 0 { + t1 = t1.Sub(lq.TMM1) + } + if lq.UPNL1.Cmp(decimal.Zero) != 0 { + t1 = t1.Add(lq.UPNL1) + } + if lq.CumB.Cmp(decimal.Zero) != 0 { + t1 = t1.Add(lq.CumB) + } + if lq.CumL.Cmp(decimal.Zero) != 0 { + t1 = t1.Add(lq.CumL) + } + if lq.CumS.Cmp(decimal.Zero) != 0 { + t1 = t1.Add(lq.CumS) + } + + position1BOTH := lq.Position1BOTH + side1BOTH := decimal.NewFromInt32(int32(lq.Side1BOTH)) + position1LONG := lq.Position1LONG + position1SHORT := lq.Position1SHORT + + d1 := t1.Sub(side1BOTH.Mul(position1BOTH).Mul(lq.EP1BOTH)). + Sub(position1LONG.Mul(lq.EP1LONG)).Add(position1SHORT.Mul(lq.EP1SHORT)) + + //d2=Position1BOTH*MMRB+Position1LONG*MMRL+Position1SHORT*MMRS-Side1BOTH*Position1BOTH-Position1LONG+Position1SHORT + t2 := position1BOTH.Mul(lq.MMRB).Add(position1LONG.Mul(lq.MMRL)).Add(position1SHORT.Mul(lq.MMRS)) + t2 = t2.Sub(side1BOTH.Mul(position1BOTH)) + d2 := t2.Sub(position1LONG).Add(position1SHORT) + //LP1= d1/d2 + + return d1.Div(d2) +} diff --git a/models/market.go b/models/market.go new file mode 100644 index 0000000..fbf46de --- /dev/null +++ b/models/market.go @@ -0,0 +1,141 @@ +package models + +import "time" + +// Ticker24 24小时统计 +type Ticker24 struct { + ChangePercent string `json:"change"` //24小时价格变动百分比 + LastPrice string `json:"last"` //最新价格 + OpenPrice string `json:"open"` //24小时开盘价格 + HighPrice string `json:"high"` //24小时最高价 + LowPrice string `json:"low"` //24小时最低价 + Volume string `json:"volume"` //24小时成数量 + QuoteVolume string `json:"quote"` //24小时成交金额 + Symbol string `json:"-"` + E int64 `json:"-"` //推送时间 +} + +type Kline struct { + Pair string + Timestamp int64 + Open string + Close string + High string + Low string + Vol string + QuoteVolume string //成交金额 +} + +type DepthRecord struct { + Price float64 + Amount float64 +} + +type DepthRecords []DepthRecord + +func (dr DepthRecords) Len() int { + return len(dr) +} + +func (dr DepthRecords) Swap(i, j int) { + dr[i], dr[j] = dr[j], dr[i] +} + +func (dr DepthRecords) Less(i, j int) bool { + return dr[i].Price < dr[j].Price +} + +type Depth struct { + ContractType string //for future + Pair string + UTime time.Time + AskList DepthRecords // Descending order 卖盘,[price(挂单价), vol(此价格挂单张数)], 按price升序 + BidList DepthRecords // Descending order 买盘,[price(挂单价), vol(此价格挂单张数)], 按price降序 +} + +// DepthBin 币安深度 +type DepthBin struct { + Symbol string `json:"s"` + Bids [][]string `json:"bids"` + Asks [][]string `json:"asks"` +} + +type UFuturesDepthBin struct { + Symbol string `json:"s"` + Bids [][]string `json:"b"` + Asks [][]string `json:"a"` +} + +// NewDealPush 最近成交记录 +type NewDealPush struct { + Price float64 `json:"price"` //成交价格 + Num float64 `json:"num"` //成交数量 + Type int `json:"type"` //1买,2卖 + DealId int64 `json:"dealid"` //交易id + CreateTime int64 `json:"createtime"` //交易时间 + //Symbol string `json:"symbol"` + //ExchangeId int `json:"exchangeid"` +} + +// ForceOrder 强平订单exc_forceorder +type ForceOrder struct { + ExchangeID int `db:"exchangeid"` + Symbol string `db:"symbol"` + Side string `db:"side"` //请买卖方向 SELL, BUY + Ordertype string `db:"ordertype"` //订单类型 LIMIT, MARKET, STOP, TAKE_PROFIT, STOP_MARKET, TAKE_PROFIT_MARKET, TRAILING_STOP_MARKET + TimeInForce string `db:"timeinforce"` //有效方式,GTC 1,IOC 2,FOK 3,GTX 4 + Price float64 `db:"price"` //价格 + Num float64 `db:"num"` //数量 + AvgPrice float64 `db:"avgprice"` //平均价格 + State string `db:"state"` //订单状态 + CreateTime time.Time `db:"createtime"` +} + +type FiveItem struct { + Buy [][]string `json:"buy"` + Sell [][]string `json:"sell"` + BuyNum float64 `json:"buynum"` + SellNum float64 `json:"sellnum"` +} + +// AlertSet vts_alertset +type AlertSet struct { + Id int `db:"id"` + UserId int `db:"userid"` + CoinId int `db:"coinid"` + CurrencyId int `db:"currencyid"` + AlertType int `db:"alerttype"` //预警类型:1价格涨到,2价格跌到,3涨幅达到,4跌幅达到,5-24小时涨幅,6-24小时跌幅 + Frequency int `db:"frequency"` //提醒频率:1仅提醒一次,2每日提醒一次,3持续提醒(每当价格达到该值,则提醒一次) + State int `db:"state"` //设置状态:1正常,2停止(比如频率是只提醒一次,提醒一次完就修改为2) + AlwaysState int `db:"alwaysstate"` //持续提醒状态:1已提醒,0可提醒 + AlertValue float64 `db:"alertvalue"` //提醒值 + SendTime time.Time `db:"sendtime"` //发送时间,每日提醒+持续提醒需要 + +} + +type PriceMarket struct { + Price float64 //最新价格 + Rate24h float64 //24小时涨幅 + Symbol string //交易对 +} + +// AlertLog vts_alertlog提醒记录 +type AlertLog struct { + Id int `db:"id"` + UserId int `db:"userid"` + AlertId int `db:"alertid"` + AlertValue float64 `db:"alertvalue"` + Coin string `db:"coin"` + Currency string `db:"currency"` + Msg string `db:"msg"` + CreateTime time.Time `db:"createtime"` +} + +type AlertMq struct { + UserId int + AlertId int + AlertValue string + Symbol string //格式:BTC/USDT + Msg string + CreateTime int64 +} diff --git a/models/ossdto/oss.go b/models/ossdto/oss.go new file mode 100644 index 0000000..a204515 --- /dev/null +++ b/models/ossdto/oss.go @@ -0,0 +1,12 @@ +package ossdto + +type OssTokenResponse struct { + AccessKeyId string `json:"accessKeyId"` + AccessKeySecret string `json:"accessKeySecret"` + SecurityToken string `json:"securityToken"` + Expiration string `json:"expiration"` + Policy string `json:"policy"` + Signature string `json:"signature"` + Host string `json:"host"` + Dir string `json:"dir"` +} diff --git a/models/pushdata.go b/models/pushdata.go new file mode 100644 index 0000000..02e493a --- /dev/null +++ b/models/pushdata.go @@ -0,0 +1,95 @@ +package models + +var ( + Pre24hrTicker = "24hrTicker" //ticker推送前缀,推送格式:@24hrTicker + PreTrade = "trade" //最新成交推送前缀 @trade + PreKline = "kline" //k线推送前缀 @kline_ + PreDepth = "depth" //深度推送前缀 @depth + PreDepth35 = "depth35" //35深度推送前缀 @depth + PreMarkPrice = "markPrice" //最新标记价 @markPrice + TickerArr = "ticker@arr" //所有24小时行情 + PreErr = "err" +) + +type PushFive struct { + Type int `json:"type"` //类型 + Num string `json:"num"` //买卖总数量 + Symbol string `json:"symbol"` //交易对,比如BTC-USDT + Data [][]string `json:"data"` //数据 +} + +type PushAll struct { + Event string `json:"e"` + Data []WsTicker `json:"data"` +} +type PushKline struct { + Type int `json:"type"` + Symbol string `json:"symbol"` //交易对,比如BTC-USDT + Data [][]string `json:"data"` +} + +type WsWsTickerBase struct { + Event string `json:"e"` + Symbol string `json:"s"` //交易对,比如BTC-USDT + Data WsTicker `json:"data"` // +} + +// WsTicker 24小时行情推送 +type WsTicker struct { + E int64 `json:"-"` //时间戳 + Symbol string `json:"s"` //symbol,BTC-USDT + Close string `json:"c"` //收盘价格,也是最新价格 + Open string `json:"o"` //开盘价格 + High string `json:"h"` //最高价格 + Low string `json:"l"` //最低价格 + Volume string `json:"v"` //成交量 + QuoteVolume string `json:"q"` //成交额 + Change string `json:"chg"` //涨幅 + RatePrice string `json:"rp"` //最新价格等于多少usdt + +} + +type WsBase struct { + Event string `json:"e"` + Symbol string `json:"s"` //交易对,比如BTC-USDT + Data interface{} `json:"data"` // +} + +type WsTrade struct { + Price string `json:"p"` //成交价格 + Quantity string `json:"q"` //成交数量 + DealTime string `json:"t"` //时间撮 + Market string `json:"m"` //1是买方主动触发交易,2卖方主动触发交易 +} + +type BaseKline struct { + Event string `json:"e"` //事件 + Symbol string `json:"s"` //交易对,比如BTC-USDT + Data WsKline `json:"data"` //k线数据 +} + +type WsKline struct { + Line string `json:"i"` // K线间隔,比如1m + LineTime string `json:"t"` // k线时间撮 + Open string `json:"o"` // 这根K线期间第一笔成交价 + Close string `json:"c"` // 这根K线期间末一笔成交价 + High string `json:"h"` // 这根K线期间最高成交价 + Low string `json:"l"` // 这根K线期间最低成交价 + Volume string `json:"v"` // 这根K线期间成交量 + QuoteVolume string `json:"q"` // 这根K线期间成交额 + Chg string `json:"chg"` // 涨幅 + Ampl string `json:"ampl"` // 震幅 +} + +type WsFive struct { + Event string `json:"e"` //事件 + Symbol string `json:"s"` //比如BTC-USDT + Bid [][]string `json:"bid"` //买盘口 + Ask [][]string `json:"ask"` //卖盘口 +} + +type WsMarkPrice struct { + Event string `json:"e"` + Symbol string `json:"s"` + Data map[string]interface{} `json:"data"` +} diff --git a/models/rabbitmqdto/ApiKeyWebSocket.go b/models/rabbitmqdto/ApiKeyWebSocket.go new file mode 100644 index 0000000..3c5771a --- /dev/null +++ b/models/rabbitmqdto/ApiKeyWebSocket.go @@ -0,0 +1,10 @@ +package rabbitmqdto + +//api key 连接 +type ApiKeyWSConnect struct { + ApiKey string `json:"apiKey"` + ApiSecret string `json:"apiSecret"` + ProxyType string `json:"proxyType"` + ProxyAddress string `json:"proxyAddress"` + Type int `json:"type" comment:"1-订阅 0-取消订阅"` +} diff --git a/models/spot/spotdto.go b/models/spot/spotdto.go new file mode 100644 index 0000000..c2bc94d --- /dev/null +++ b/models/spot/spotdto.go @@ -0,0 +1,72 @@ +package spot + +type SpotTicker24h struct { + Symbol string `json:"symbol"` // 交易对符号 (e.g., BNBBTC) + PriceChange string `json:"priceChange"` // 24小时价格变动 + PriceChangePercent string `json:"priceChangePercent"` // 24小时价格变动百分比 + WeightedAvgPrice string `json:"weightedAvgPrice"` // 加权平均价 + PrevClosePrice string `json:"prevClosePrice"` // 前一个收盘价 + LastPrice string `json:"lastPrice"` // 最近一次成交价 + LastQty string `json:"lastQty"` // 最近一次成交量 + BidPrice string `json:"bidPrice"` // 当前买单价 + BidQty string `json:"bidQty"` // 当前买单量 + AskPrice string `json:"askPrice"` // 当前卖单价 + AskQty string `json:"askQty"` // 当前卖单量 + OpenPrice string `json:"openPrice"` // 24小时内第一次成交的价格 + HighPrice string `json:"highPrice"` // 24小时最高价 + LowPrice string `json:"lowPrice"` // 24小时最低价 + Volume string `json:"volume"` // 24小时成交量 + QuoteVolume string `json:"quoteVolume"` // 24小时成交额 + OpenTime int64 `json:"openTime"` // 24小时内,第一笔交易的发生时间 (Unix timestamp) + CloseTime int64 `json:"closeTime"` // 24小时内,最后一笔交易的发生时间 (Unix timestamp) + FirstId int `json:"firstId"` // 首笔成交id + LastId int `json:"lastId"` // 末笔成交id + Count int `json:"count"` // 成交笔数 +} + +type RateLimit struct { + // 定义在 "限制种类 (rateLimitType)" 部分的限制 +} + +type ExchangeFilter struct { + // 定义在 "过滤器" 部分的过滤器 + FilterType string `json:"filterType"` //类别 + MinPrice string `json:"minPrice"` //最小金额 + MaxPrice string `json:"maxPrice"` //最大金额 + TickSize string `json:"tickSize"` //最小精度 + + MinQty string `json:"minQty"` //最小购买数量 + MaxQty string `json:"maxQty"` //最大购买数量 + StepSize string `json:"stepSize"` //数量最小精度 +} + +type Symbol struct { + Symbol string `json:"symbol"` // 交易对符号 (e.g., ETHBTC) + Status string `json:"status"` // 当前状态 (e.g., TRADING) + BaseAsset string `json:"baseAsset"` // 基础资产 (e.g., ETH) + BaseAssetPrecision int `json:"baseAssetPrecision"` // 基础资产精度 + QuoteAsset string `json:"quoteAsset"` // 报价资产 (e.g., BTC) + QuotePrecision int `json:"quotePrecision"` // 报价资产精度 + QuoteAssetPrecision int `json:"quoteAssetPrecision"` // 报价资产的精度 + OrderTypes []string `json:"orderTypes"` // 支持的订单类型 + IcebergAllowed bool `json:"icebergAllowed"` // 是否允许冰山订单 + OcoAllowed bool `json:"ocoAllowed"` // 是否允许 OCO (One Cancels the Other) 订单 + QuoteOrderQtyMarketAllowed bool `json:"quoteOrderQtyMarketAllowed"` // 是否允许市场单的报价订单数量 + AllowTrailingStop bool `json:"allowTrailingStop"` // 是否允许跟踪止损 + IsSpotTradingAllowed bool `json:"isSpotTradingAllowed"` // 是否允许现货交易 + IsMarginTradingAllowed bool `json:"isMarginTradingAllowed"` // 是否允许保证金交易 + CancelReplaceAllowed bool `json:"cancelReplaceAllowed"` // 是否允许取消替代 + Filters []ExchangeFilter `json:"filters"` // 过滤器,定义在 "过滤器" 部分 + Permissions []string `json:"permissions"` // 权限 + PermissionSets [][]string `json:"permissionSets"` // 权限集 + DefaultSelfTradePreventionMode string `json:"defaultSelfTradePreventionMode"` // 默认的自我交易防止模式 + AllowedSelfTradePreventionModes []string `json:"allowedSelfTradePreventionModes"` // 允许的自我交易防止模式 +} + +type ExchangeInfo struct { + Timezone string `json:"timezone"` // 时区 (e.g., UTC) + ServerTime int64 `json:"serverTime"` // 服务器时间 (Unix timestamp) + RateLimits []RateLimit `json:"rateLimits"` // 速率限制,定义在 "限制种类" 部分 + ExchangeFilters []ExchangeFilter `json:"exchangeFilters"` // 交易所过滤器,定义在 "过滤器" 部分 + Symbols []Symbol `json:"symbols"` // 交易对列表 +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..48e341a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3 @@ +{ + "lockfileVersion": 1 +} diff --git a/pkg/coingate/coingatehelper.go b/pkg/coingate/coingatehelper.go new file mode 100644 index 0000000..4e577c5 --- /dev/null +++ b/pkg/coingate/coingatehelper.go @@ -0,0 +1,93 @@ +package coingate + +import ( + "errors" + "fmt" + "go-admin/common/global" + "go-admin/common/helper" + "go-admin/config" + "go-admin/models/coingatedto" + "io/ioutil" + "net/http" + "strings" + + "github.com/bytedance/sonic" + + log "github.com/go-admin-team/go-admin-core/logger" +) + +/* +获取支付连接 + + - @endPoint basic url + - @token api 密钥 + - @req 请求参数 +*/ +func GetPaymentUrl(endPoint string, token string, req *coingatedto.OrderRequest) (resp coingatedto.OrderResponse, err error) { + method := "POST" + url := fmt.Sprintf("%s%s", endPoint, "/v2/orders") + payload, err := sonic.Marshal(req) + + if err != nil { + return + } + client := &http.Client{} + err = helper.CreateHtppProxy("", config.ExtConfig.ProxyUrl, client) + + if err != nil { + return + } + + request, err := http.NewRequest(method, url, strings.NewReader(string(payload))) + + if err != nil { + fmt.Println(err) + return + } + request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) + request.Header.Add("Content-Type", "application/json") + + res, err := client.Do(request) + if err != nil { + return + } + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return + } + + if res.StatusCode != http.StatusOK { + log.Error("status:", res.Status, "获取支付失败:", string(body)) + err = errors.New("获取支付连接失败,status:" + res.Status) + return + } + + err = sonic.Unmarshal(body, &resp) + + return +} + +/* +支付回调 +*/ +func CallBack(req *coingatedto.OrderCallBackResponse) error { + switch req.Status { + case global.COINGATE_STATUS_NEW: + case global.COINGATE_STATUS_PENDING: + case global.COINGATE_STATUS_CONFIRMING: + case global.COINGATE_STATUS_PAID: + case global.COINGATE_STATUS_INVALID: + case global.COINGATE_STATUS_EXPIRED: + case global.COINGATE_STATUS_CANCELED: + case global.COINGATE_STATUS_REFUNDED: + case global.COINGATE_STATUS_PARTIALLY_REFUNDED: + default: + errStr := fmt.Sprintf("支付回调未找到状态%s", req.Status) + log.Error(errStr) + return errors.New(errStr) + } + + return nil +} diff --git a/pkg/cryptohelper/aeshelper/aes256.go b/pkg/cryptohelper/aeshelper/aes256.go new file mode 100644 index 0000000..726882f --- /dev/null +++ b/pkg/cryptohelper/aeshelper/aes256.go @@ -0,0 +1,88 @@ +package aeshelper + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/md5" + "crypto/rand" + "encoding/base64" + "io" +) + +// Encrypt text with the passphrase +func Encrypt(text string, passphrase string) string { + salt := make([]byte, 8) + if _, err := io.ReadFull(rand.Reader, salt); err != nil { + panic(err.Error()) + } + + key, iv := DeriveKeyAndIv(passphrase, string(salt)) + + block, err := aes.NewCipher([]byte(key)) + if err != nil { + panic(err) + } + + pad := PKCS7Padding([]byte(text), block.BlockSize()) + ecb := cipher.NewCBCEncrypter(block, []byte(iv)) + encrypted := make([]byte, len(pad)) + ecb.CryptBlocks(encrypted, pad) + + return base64.StdEncoding.EncodeToString([]byte("Salted__" + string(salt) + string(encrypted))) +} + +// Decrypt encrypted text with the passphrase +func Decrypt(encrypted string, passphrase string) string { + ct, _ := base64.StdEncoding.DecodeString(encrypted) + if len(ct) < 16 || string(ct[:8]) != "Salted__" { + return "" + } + + salt := ct[8:16] + ct = ct[16:] + key, iv := DeriveKeyAndIv(passphrase, string(salt)) + + block, err := aes.NewCipher([]byte(key)) + if err != nil { + panic(err) + } + + cbc := cipher.NewCBCDecrypter(block, []byte(iv)) + dst := make([]byte, len(ct)) + cbc.CryptBlocks(dst, ct) + + return string(PKCS7Trimming(dst)) +} + +// PKCS7Padding PKCS7Padding +func PKCS7Padding(ciphertext []byte, blockSize int) []byte { + padding := blockSize - len(ciphertext)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(ciphertext, padtext...) +} + +// PKCS7Trimming PKCS7Trimming +func PKCS7Trimming(encrypt []byte) []byte { + padding := encrypt[len(encrypt)-1] + return encrypt[:len(encrypt)-int(padding)] +} + +// DeriveKeyAndIv DeriveKeyAndIv +func DeriveKeyAndIv(passphrase string, salt string) (string, string) { + salted := "" + dI := "" + + for len(salted) < 48 { + md := md5.New() + md.Write([]byte(dI + passphrase + salt)) + dM := md.Sum(nil) + dI = string(dM[:16]) + salted = salted + dI + } + + key := salted[0:32] + iv := salted[32:48] + + return key, iv +} diff --git a/pkg/cryptohelper/aeshelper/aeshelper.go b/pkg/cryptohelper/aeshelper/aeshelper.go new file mode 100644 index 0000000..695ea99 --- /dev/null +++ b/pkg/cryptohelper/aeshelper/aeshelper.go @@ -0,0 +1,107 @@ +package aeshelper + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "encoding/base64" + "encoding/hex" + "github.com/forgoer/openssl" +) + +const ( + sKey = "ptQJqRKxICCTeo6w" // "dde4b1f8a9e6b814" + ivParameter = "O3vZvOJSnQDP9hKT" // "dde4b1f8a9e6b814" + +) + +// PswEncrypt 加密 +func PswEncrypt(src string) (string, error) { + key := []byte(sKey) + iv := []byte(ivParameter) + result, err := Aes128Encrypt([]byte(src), key, iv) + if err != nil { + return "", err + } + return base64.RawStdEncoding.EncodeToString(result), nil +} + +// PswDecrypt 解密 +func PswDecrypt(src string) (string, error) { + key := []byte(sKey) + iv := []byte(ivParameter) + var result []byte + var err error + result, err = base64.StdEncoding.DecodeString(src) + if err != nil { + return "", err + } + origData, err := Aes128Decrypt(result, key, iv) + if err != nil { + return "", err + } + return string(origData), nil +} + +func Aes128Encrypt(origData, key []byte, IV []byte) ([]byte, error) { + if key == nil || len(key) != 16 { + return nil, nil + } + if IV != nil && len(IV) != 16 { + return nil, nil + } + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + blockSize := block.BlockSize() + origData = PKCS5Padding(origData, blockSize) + blockMode := cipher.NewCBCEncrypter(block, IV[:blockSize]) + crypted := make([]byte, len(origData)) + // 根据CryptBlocks方法的说明,如下方式初始化crypted也可以 + blockMode.CryptBlocks(crypted, origData) + return crypted, nil +} +func Aes128Decrypt(crypted, key []byte, IV []byte) ([]byte, error) { + if key == nil || len(key) != 16 { + return nil, nil + } + if IV != nil && len(IV) != 16 { + return nil, nil + } + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + blockSize := block.BlockSize() + blockMode := cipher.NewCBCDecrypter(block, IV[:blockSize]) + origData := make([]byte, len(crypted)) + blockMode.CryptBlocks(origData, crypted) + origData = PKCS5UnPadding(origData) + return origData, nil +} +func PKCS5Padding(ciphertext []byte, blockSize int) []byte { + padding := blockSize - len(ciphertext)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(ciphertext, padtext...) +} +func PKCS5UnPadding(origData []byte) []byte { + length := len(origData) + // 去掉最后一个字节 unpadding 次 + unpadding := int(origData[length-1]) + return origData[:(length - unpadding)] +} + +// 密码加密 +func AesEcbEncrypt(origData string) string { + //加密 + dst, _ := openssl.AesECBEncrypt([]byte(origData), []byte(sKey), openssl.PKCS7_PADDING) + return hex.EncodeToString(dst) +} + +// 密码解密 +func AesEcbDecrypt(origData string) string { + value, _ := hex.DecodeString(origData) + dst, _ := openssl.AesECBDecrypt(value, []byte(sKey), openssl.PKCS7_PADDING) + return string(dst) +} diff --git a/pkg/cryptohelper/aeshelper/aeshelper_test.go b/pkg/cryptohelper/aeshelper/aeshelper_test.go new file mode 100644 index 0000000..b918f4a --- /dev/null +++ b/pkg/cryptohelper/aeshelper/aeshelper_test.go @@ -0,0 +1,65 @@ +package aeshelper + +import ( + "encoding/json" + "fmt" + "testing" + "time" +) + +// 测试加密解密 +func Test_EnDe(t *testing.T) { + a := `asg` + b := Encrypt(a, `code_verify_success`) + c := Decrypt(b, `code_verify_success`) + fmt.Println(`原始为`, a) + fmt.Println(`加密后`, b) + fmt.Println(`解密后`, c) +} +func TestAesEcbEncrypt(t *testing.T) { + //aes := AesEcbEncrypt("123456") + aes := AesEcbEncrypt(fmt.Sprintf("%v_%v", time.Now().Unix(), 1332355333)) + dst := AesEcbDecrypt(aes) + fmt.Println(aes) + fmt.Println(dst) +} + +// TODO:需要加密的接口 +/** +1.合约下单接口 /api/futures/trade/order +2.合约撤单 /api/futures/trade/cancelorder +3.调整保证金 /api/futures/trade/adjustmargin +4.变换逐全仓模式 /api/futures/trade/marginType +5.更改持仓模式(方向) /api/futures/trade/positionSide/dual +6.资产划转 /api/futures/transfer +*/ +func TestAesEcbEncryptOrder(t *testing.T) { + data := addFutOrderReq{ + OrderType: 1, + BuyType: 3, + TriggerDecide: 3, + IsReduce: 2, + Coin: "asdf", + Price: "333.23", + Num: "23.20", + TriggerPrice: "1.023", + PositionSide: "long", + } + b, _ := json.Marshal(data) + aes := AesEcbEncrypt(string(b)) + dst := AesEcbDecrypt(aes) + fmt.Println(aes) + fmt.Println(dst) +} + +type addFutOrderReq struct { + OrderType int `json:"order_type"` // 订单类型:1限价,2限价止盈止损,3市价,4市价止盈止损,5强平委托(就是限价委托) + BuyType int `json:"buy_type"` // 买卖类型:1买,2卖 + TriggerDecide int `json:"trigger_decide"` // 触发条件 1按最新成交价格算,2按标记价格算 + IsReduce int `json:"is_reduce"` // 1是只减仓位(点击仓位列表中的平仓按钮),0正常 + Coin string `json:"coin"` // 交易币 + Price string `json:"price"` // 下单价格(限价+止盈止损时,该字段必填) + Num string `json:"num"` // 下单数量(市价时该字段必填) + TriggerPrice string `json:"trigger_price"` // 触发价格 + PositionSide string `json:"position_side"` // 持仓方向,单向持仓模式下可填both;在双向持仓模式下必填,且仅可选择 long 或 short +} diff --git a/pkg/cryptohelper/aeshelper/aesoldhelper/aesoldhelper.go b/pkg/cryptohelper/aeshelper/aesoldhelper/aesoldhelper.go new file mode 100644 index 0000000..c9812da --- /dev/null +++ b/pkg/cryptohelper/aeshelper/aesoldhelper/aesoldhelper.go @@ -0,0 +1,94 @@ +package aesoldhelper + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "errors" + "io" + "strings" +) + +func addBase64Padding(value string) string { + m := len(value) % 4 + if m != 0 { + value += strings.Repeat("=", 4-m) + } + + return value +} + +func removeBase64Padding(value string) string { + return strings.Replace(value, "=", "", -1) +} + +// Pad Pad +func Pad(src []byte) []byte { + padding := aes.BlockSize - len(src)%aes.BlockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(src, padtext...) +} + +// Unpad Unpad +func Unpad(src []byte) ([]byte, error) { + length := len(src) + unpadding := int(src[length-1]) + + if unpadding > length { + return nil, errors.New("unpad error. This could happen when incorrect encryption key is used") + } + + return src[:(length - unpadding)], nil +} + +// AesEncrypt AesEncrypt +func AesEncrypt(key []byte, text string) (string, error) { + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + msg := Pad([]byte(text)) + ciphertext := make([]byte, aes.BlockSize+len(msg)) + iv := ciphertext[:aes.BlockSize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return "", err + } + + cfb := cipher.NewCFBEncrypter(block, iv) + cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(msg)) + finalMsg := removeBase64Padding(base64.URLEncoding.EncodeToString(ciphertext)) + return finalMsg, nil +} + +// AesDecrypt AesDecrypt +func AesDecrypt(key []byte, text string) (string, error) { + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + decodedMsg, err := base64.URLEncoding.DecodeString(addBase64Padding(text)) + if err != nil { + return "", err + } + + if (len(decodedMsg) % aes.BlockSize) != 0 { + return "", errors.New("blocksize must be multipe of decoded message length") + } + + iv := decodedMsg[:aes.BlockSize] + msg := decodedMsg[aes.BlockSize:] + + cfb := cipher.NewCFBDecrypter(block, iv) + cfb.XORKeyStream(msg, msg) + + unpadMsg, err := Unpad(msg) + if err != nil { + return "", err + } + + return string(unpadMsg), nil +} diff --git a/pkg/cryptohelper/aeshelper/apikey.go b/pkg/cryptohelper/aeshelper/apikey.go new file mode 100644 index 0000000..006c1a5 --- /dev/null +++ b/pkg/cryptohelper/aeshelper/apikey.go @@ -0,0 +1,19 @@ +package aeshelper + +var ( + apiaeskey = "9jxFTkydwCJsmIA1TUrv" +) + +// EncryptApi 加密apikey +func EncryptApi(apikey, secretkey string) (apikeyen, secrekeyen string) { + apikeyen = Encrypt(apikey, apiaeskey) + secrekeyen = Encrypt(secretkey, apiaeskey) + return apikeyen, secrekeyen +} + +// DecryptApi 解密apikey +func DecryptApi(apikeyen, secrekeyen string) (apikey, secrekey string) { + apikey = Decrypt(apikeyen, apiaeskey) + secrekey = Decrypt(secrekeyen, apiaeskey) + return apikey, secrekey +} diff --git a/pkg/cryptohelper/inttostring/Inttoostr.go b/pkg/cryptohelper/inttostring/Inttoostr.go new file mode 100644 index 0000000..99762d1 --- /dev/null +++ b/pkg/cryptohelper/inttostring/Inttoostr.go @@ -0,0 +1,133 @@ +package inttostring + +import ( + "math" + "math/rand" + "strings" + "time" +) + +// IntToStr 用于将数字映射为字符串 例如 用户id-->邀请码 +type IntToStr struct { + SALT int // 随意取一个数值 + Len int // 邀请码长度 + PRIME2 byte // 与邀请码长度 8 互质 + AlphanumericSet []rune + PRIME1 int // 与字符集长度 36 互质 +} + +var ( + Invite = NewInvite() // 邀请码 +) + +// NewInvite 邀请码 +func NewInvite() *IntToStr { + return &IntToStr{ + SALT: 15151239, // 随意取一个数值 + Len: 8, // 邀请码长度 + PRIME2: 3, // 与邀请码长度 Len 互质 + AlphanumericSet: []rune{ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + }, // 邀请码长度 + PRIME1: 5, // 与字符集长度 36 互质 + } +} + +func (in IntToStr) DeCode(codeStr string) int { + code := make([]rune, 0, in.Len) + var key rune = 0 + // 还原k + for i := 0; i < in.Len; i++ { + for k, v := range in.AlphanumericSet { + if v == rune(codeStr[i]) { + key = rune(k) + break + } + } + code = append(code, key) + } + code2 := make([]rune, 0, in.Len) + // 还原顺序 + for i := 0; i < in.Len; i++ { + idx := i * int(in.PRIME2) % in.Len + code2 = append(code2, code[idx]) + } + + // 还原数字 + var x int + for i := 0; i < in.Len; i++ { + code2[i] = (code2[i] - rune(i)*code2[0]) % rune(len(in.AlphanumericSet)) + code2[i] = (code2[i] + rune(len(in.AlphanumericSet))) % rune(len(in.AlphanumericSet)) + place := math.Pow(float64(len(in.AlphanumericSet)), float64(i)) // 次方运算 + x += int(code2[i]) * int(place) + } + // 去盐 + 缩小 + x = (x - in.SALT) / in.PRIME1 + + return x +} + +func (in IntToStr) Encode(uid int) string { + // 放大 + 加盐 + uid = uid*in.PRIME1 + in.SALT + slIdx := make([]byte, in.Len) + + // 扩散 + for i := 0; i < in.Len; i++ { + slIdx[i] = byte(uid % len(in.AlphanumericSet)) // 转换进制 获取36进制的每一位值 + slIdx[i] = (slIdx[i] + byte(i)*slIdx[0]) % byte(len(in.AlphanumericSet)) // 其他位与个位加和再取余(让个位的变化影响到所有位) + uid = uid / len(in.AlphanumericSet) // 相当于右移一位(62进制) + } + + var code []rune + // 混淆 + for i := 0; i < in.Len; i++ { + idx := (byte(i) * in.PRIME2) % byte(in.Len) + code = append(code, in.AlphanumericSet[slIdx[idx]]) + } + return string(code) +} + +const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +const smsLetters = "0123456789" + +// GenerateRandomString 生成指定长度的随机字符串 +func GenerateRandomString(n int) string { + rand.Seed(time.Now().UnixNano()) // 初始化随机数种子 + result := make([]byte, n) + for i := range result { + result[i] = letters[rand.Intn(len(letters))] + } + return string(result) +} + +// GenerateRandomSmsString 生成指定长度的随机字符串 +func GenerateRandomSmsString(n int) string { + rand.Seed(time.Now().UnixNano()) // 初始化随机数种子 + result := make([]byte, n) + for i := range result { + result[i] = smsLetters[rand.Intn(len(smsLetters))] + } + return string(result) +} + +func EncryptString(s string, prefixLen, suffixLen int) string { + length := len(s) + + // 如果字符串长度不足,直接返回原字符串 + if length <= prefixLen+suffixLen { + return s + } + + // 保留前 prefixLen 位和后 suffixLen 位 + prefix := s[:prefixLen] + suffix := s[length-suffixLen:] + + // 中间部分用 * 替换 + middle := strings.Repeat("*", 6) + + // 拼接结果 + return prefix + middle + suffix +} diff --git a/pkg/cryptohelper/inttostring/Inttoostr_test.go b/pkg/cryptohelper/inttostring/Inttoostr_test.go new file mode 100644 index 0000000..a31f43f --- /dev/null +++ b/pkg/cryptohelper/inttostring/Inttoostr_test.go @@ -0,0 +1,33 @@ +package inttostring + +import ( + "fmt" + "testing" +) + + +func Test_a(t *testing.T) { + a:=1156 + // b:=byte(a) + fmt.Println(a%10) +} +// 加密解密 +func Test_EnDecode(t *testing.T) { + uid := 6515461646 + Invi := NewInvite() + encode := Invi.Encode(uid) + DeCode := Invi.DeCode(encode) + fmt.Println(`原始为`, uid) + fmt.Println(`加密后`, encode) + fmt.Println(`解密后`, DeCode) +} + +func Test_Invite(t *testing.T) { + for uid := 0; uid < 10000000; uid++ { + encode := Invite.Encode(uid) + DeCode := Invite.DeCode(encode) + if DeCode != uid { + t.Fatal(`加密解密错误`, DeCode, uid) + } + } +} diff --git a/pkg/cryptohelper/jwthelper/jwthelper.go b/pkg/cryptohelper/jwthelper/jwthelper.go new file mode 100644 index 0000000..bd444aa --- /dev/null +++ b/pkg/cryptohelper/jwthelper/jwthelper.go @@ -0,0 +1,295 @@ +package jwthelper + +import ( + "go-admin/common/const/rediskey" + "go-admin/common/helper" + "go-admin/pkg/utility" + + "github.com/golang-jwt/jwt/v5" + "go.uber.org/zap" + + "fmt" + "strings" + "time" + + log "github.com/go-admin-team/go-admin-core/logger" +) + +// LoginUserJwt 用户登入信息 +type LoginUserJwt struct { + UserID int + NickName string + Phone string + Email string + OsType int +} + +// LoginClaims 自定义声明结构体并内嵌jwt.StandardClaims +// jwt包自带的jwt.StandardClaims只包含了官方字段 +// 我们这里需要额外记录一个nickname字段,所以要自定义结构体 +// 如果想要保存更多信息,都可以添加到这个结构体中 +type LoginClaims struct { + UserID int `json:"userid"` + NickName string `json:"nickname"` + Phone string `json:"phone"` + Email string `json:"email"` + jwt.RegisteredClaims +} + +// JwtSigningKey Jwt signing key +var JwtSigningKey = []byte("tokexapp.com") + +var LoginTokenValidTime = 48 * time.Hour // 登录验证token有效期 +var LoginAdminTokenValidTime = 24 * time.Hour + +var TokenNotValid = -1 // Token无效 +var TokenExpire = -2 // Token过期 +var TokenSuccess = 1 // Token正常 + +// CreateJwtToken 创建用户jwt token +func CreateJwtToken(userJwt LoginUserJwt, minute int) (string, string) { + // 创建一个我们自己的声明 + expire := time.Now().Add(time.Minute * time.Duration(minute)) //.Unix() + claims := LoginClaims{ + UserID: userJwt.UserID, + Phone: userJwt.Phone, + NickName: userJwt.NickName, + Email: userJwt.Email, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(expire), + Issuer: "Tokex", // 签发人 + }, + } + + // 使用指定的签名方法创建签名对象 + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + tokenStr, err := token.SignedString(JwtSigningKey) + if err != nil { + log.Error("获取jwt token失败:", zap.Error(err)) + return "", "" + } + return tokenStr, fmt.Sprintf("%v", expire.Unix()) +} + +// MidValidToken 检验token是否有效,返回token解密后的string +func MidValidToken(tokenStr string, source int) (int, string) { + // 解析token + token, err := jwt.ParseWithClaims(tokenStr, &LoginClaims{}, func(token *jwt.Token) (i interface{}, err error) { + return JwtSigningKey, nil + }) + if err != nil { + if strings.Contains(err.Error(), "token is expired") { + return TokenExpire, "" + } + // loghelper.Error("MidValidToken解析token失败", zap.Error(err)) + return TokenNotValid, "" + } + if !token.Valid { + log.Error("MidValidToken解析token无效", zap.Error(err)) + return TokenNotValid, "" + } + claims, ok := token.Claims.(*LoginClaims) + if !ok { + return TokenNotValid, "" + } + + // 校验token + key := fmt.Sprintf(rediskey.AppLoginUserToken, claims.UserID) + if source == 3 { + key = fmt.Sprintf(rediskey.PCLoginUserToken, claims.UserID) + } + appToken, _ := helper.DefaultRedis.GetString(key) + if len(appToken) == 0 { + //说明未登入或者过期 + return TokenExpire, "" + } + if appToken != tokenStr { + //说明是被t + return TokenExpire, "" + } + + return TokenSuccess, strings.Join([]string{utility.IntToString(claims.UserID), + claims.NickName, claims.Phone, claims.Email}, ",") +} + +// GetLoginUserJwt 解析用户登入信息 +func GetLoginUserJwt(tokenStr string) LoginUserJwt { + if len(tokenStr) == 0 { + return LoginUserJwt{} + } + tokenArr := strings.Split(tokenStr, ",") + arrLen := len(tokenArr) + item := LoginUserJwt{ + UserID: utility.StringAsInteger(tokenArr[0]), + } + if arrLen > 1 { + item.NickName = tokenArr[1] + } + if arrLen > 2 { + item.Phone = tokenArr[2] + } + if arrLen > 3 { + item.Email = tokenArr[3] + } + return item +} + +// AgentLoginJwt ==============Agent 代理商 JWT========================// +type AgentLoginJwt struct { + AgentId int `json:"agent_id"` // 代理商ID + UserId int `json:"user_id"` // 代理商关联的用户ID + Name string `json:"name"` // 代理商姓名 + Email string `json:"email"` // 代理商邮件 + IP string `json:"ip"` // 代理商IP +} +type AgentLoginClaims struct { + AgentId int `json:"agent_id"` // 代理商ID + UserId int `json:"user_id"` // 代理商关联的用户ID + Name string `json:"name"` // 代理商姓名 + Email string `json:"email"` // 代理商邮件 + IP string `json:"ip"` // 代理商IP + jwt.RegisteredClaims //StandardClaims +} + +var JwtAgentSigningKey = []byte("tokexagent.com") //Jwt signing key + +// CreateAgentJwtToken 代理商产生的token +func CreateAgentJwtToken(userJwt AgentLoginJwt, minute int) string { + // 创建一个我们自己的声明 + exp := time.Now().Add(time.Minute * time.Duration(minute)) + c := AgentLoginClaims{ + userJwt.AgentId, + userJwt.UserId, // 自定义字段 + userJwt.Name, + userJwt.Email, + userJwt.IP, + jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(exp), // 过期时间 + Issuer: "Tokex-Agent", // 签发人 + }, + } + // 使用指定的签名方法创建签名对象 + token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) + tokenStr, err := token.SignedString(JwtAgentSigningKey) + if err != nil { + log.Error("获取jwt token失败:", zap.Error(err)) + return "" + } + return tokenStr +} + +// MidValidAgentToken 检验token是否有效,返回token解密后的string +func MidValidAgentToken(tokenStr string) (int, string) { + // 解析token + token, err := jwt.ParseWithClaims(tokenStr, &AgentLoginClaims{}, func(token *jwt.Token) (i interface{}, err error) { + return JwtAgentSigningKey, nil + }) + if err != nil { + return TokenNotValid, "" + } + if !token.Valid { + log.Error("MidValidAgentToken解析token无效", zap.Error(err)) + return TokenNotValid, "" + } + claims, ok := token.Claims.(*AgentLoginClaims) + if !ok { + return TokenNotValid, "" + } + + // 校验token + key := fmt.Sprintf(rediskey.AgentLoginUserToken, claims.AgentId) + appToken, _ := helper.DefaultRedis.GetString(key) + if len(appToken) == 0 { + return TokenExpire, "" + } + if appToken != tokenStr { + return TokenExpire, "" + } + + return TokenSuccess, strings.Join([]string{utility.IntToString(claims.AgentId), utility.IntToString(claims.UserId), + claims.Name, claims.Email, claims.IP}, ",") +} + +// GetLoginAgentJwt 解析代理商登入信息 +func GetLoginAgentJwt(tokenStr string) AgentLoginJwt { + if len(tokenStr) == 0 { + return AgentLoginJwt{} + } + tokenArr := strings.Split(tokenStr, ",") + arrLen := len(tokenArr) + item := AgentLoginJwt{ + AgentId: utility.StringAsInteger(tokenArr[0]), + } + if arrLen > 1 { + item.UserId = utility.StringAsInteger(tokenArr[1]) + } + if arrLen > 2 { + item.Name = tokenArr[2] + } + if arrLen > 3 { + item.Email = tokenArr[3] + } + if arrLen > 4 { + item.IP = tokenArr[4] + } + return item +} + +// ========== Admin 专用 JWT ========== // + +type AdminLogin struct { + UserID int `json:"user_id"` + Account string `json:"account"` + NickName string `json:"nickname"` + Phone string `json:"phone"` + Email string `json:"email"` + IP string `json:"ip"` +} + +type AdminLoginClaims struct { + AdminLogin + jwt.RegisteredClaims //StandardClaims +} + +var AdminSignedKey = []byte("adm23inTOKexKey:237") // Admin 签名 Key + +const AdminTokenIssue = "tokexAdmin" // Admin 签发人 + +// GenerateAdminLoginToken 生成管理员登录 Token +func GenerateAdminLoginToken(admin AdminLogin, expires time.Duration) string { + exp := time.Now().Add(expires) + claims := AdminLoginClaims{ + admin, + jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(exp), // 过期时间 + Issuer: AdminTokenIssue, // 签发人 + }, + } + + // 使用指定的签名方法创建签名对象 + jwtToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(AdminSignedKey) + if err != nil { + log.Error("获取 jwtToken 失败:", zap.Error(err)) + } + + return jwtToken +} + +// ParseAdminLoginToken 解析管理员登录 Token +func ParseAdminLoginToken(jwtToken string) (pass bool, adminLogin AdminLoginClaims) { + // 解析 jwtToken + token, err := jwt.ParseWithClaims(jwtToken, &AdminLoginClaims{}, func(token *jwt.Token) (i interface{}, err error) { + return AdminSignedKey, nil + }) + if err != nil { + log.Error("解析 jwtToken 失败:", zap.Error(err)) + return false, adminLogin + } + + // 判断是否验证通过,并将 Claims => AdminLoginClaims + if claims, pass := token.Claims.(*AdminLoginClaims); pass && token.Valid { + return pass, *claims + } + + return false, adminLogin +} diff --git a/pkg/cryptohelper/md5helper/md5helper.go b/pkg/cryptohelper/md5helper/md5helper.go new file mode 100644 index 0000000..eea1507 --- /dev/null +++ b/pkg/cryptohelper/md5helper/md5helper.go @@ -0,0 +1,17 @@ +package md5helper + +import ( + "crypto/md5" + "encoding/hex" +) + +// MD5 md5加密 +func MD5(input string) string { + cc := md5.Sum([]byte(input)) + return hex.EncodeToString(cc[:]) +} + +// MD52 md52次 +func MD52(input string) string { + return MD5(MD5(input)) +} diff --git a/pkg/cryptohelper/md5helper/md5helper_test.go b/pkg/cryptohelper/md5helper/md5helper_test.go new file mode 100644 index 0000000..29dfb85 --- /dev/null +++ b/pkg/cryptohelper/md5helper/md5helper_test.go @@ -0,0 +1,55 @@ +package md5helper + +import ( + "crypto/md5" + "encoding/hex" + "fmt" + "testing" +) + +func TestMD52(t *testing.T) { + got := MD52("Qq123456") + fmt.Println(got) +} + +func TestMD5(t *testing.T) { + got := []byte("Qq123456") + s := md5.New() + s.Write(got) + fmt.Println(hex.EncodeToString(s.Sum(nil))) + + cc := md5.Sum(got) + fmt.Println(hex.EncodeToString(cc[:])) + + fmt.Printf("%x\n", md5.Sum(got)) + +} + +// go test -bench=_QE_ -benchmem -run=^$ +func Benchmark_QE_1(b *testing.B) { + for i := 0; i < b.N; i++ { + input := `Qq123456` + s := md5.New() + s.Write([]byte(input)) + hex.EncodeToString(s.Sum(nil)) + } +} + +func Benchmark_QE_2(b *testing.B) { + for i := 0; i < b.N; i++ { + input := `Qq123456` + cc := md5.Sum([]byte(input)) + hex.EncodeToString(cc[:]) + } +} + +func Benchmark_QE_3(b *testing.B) { + for i := 0; i < b.N; i++ { + input := `Qq123456` + fmt.Sprintf("%x\n", md5.Sum([]byte(input))) + } +} + +// Benchmark_QE_1-6 6354160 189.8 ns/op 48 B/op 2 allocs/op +// Benchmark_QE_2-6 7352328 162.9 ns/op 32 B/op 1 allocs/op +// Benchmark_QE_3-6 3007480 396.9 ns/op 80 B/op 3 allocs/op diff --git a/pkg/emailhelper/emailhelper.go b/pkg/emailhelper/emailhelper.go new file mode 100644 index 0000000..b60ac96 --- /dev/null +++ b/pkg/emailhelper/emailhelper.go @@ -0,0 +1,181 @@ +package emailhelper + +import ( + "errors" + "fmt" + "go-admin/config" + "go-admin/pkg/utility" + "regexp" + "sync" + + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/mailjet/mailjet-apiv3-go" + "go.uber.org/zap" + + "gopkg.in/gomail.v2" +) + +var ( + cacheEmail = make(map[string]*gomail.Dialer) + mu = sync.RWMutex{} +) + +// getGoMailIn 邮件连接池 +func getGoMailIn(key string) *gomail.Dialer { + mu.RLock() + defer mu.RUnlock() + + item, ok := cacheEmail[key] + if ok { + return item + } + + return nil +} + +// CheckIsEmail 检测是否是邮箱 +func CheckIsEmail(email string) bool { + if email == `` { + return false + } + pattern := `\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*` // 匹配电子邮箱 + reg := regexp.MustCompile(pattern) + + return reg.MatchString(email) +} + +// SendFrontedEmail 发送邮件 +func SendFrontedEmail(toEmail string, code string) error { + // 邮箱配置 + from := config.ExtConfig.EmailConfig.MailFrom // 发送者邮箱 + password := config.ExtConfig.EmailConfig.MailSmtpPass // Gmail 密码或应用专用密码 + to := toEmail // 收件人邮箱 + smtpHost := config.ExtConfig.EmailConfig.MailSmtpHost // Gmail SMTP 服务器 + smtpPort := config.ExtConfig.EmailConfig.MailSmtpPort // SMTP 端口 + + link := fmt.Sprintf("%s/verify?email=%s&verify_code=%s&type=register", config.ExtConfig.Domain, toEmail, code) + // 创建邮件消息 + subject := "注册验证" + body := fmt.Sprintf("

注册验证

您收到此电子邮件,用于进行邮箱验证,请点击下面的链接或打开下面的网址继续。

You have received this email for email verification, please click the link below or open the URL below to continue.

%s

", link) + + m := gomail.NewMessage() + m.SetHeader("From", from) // 发件人 + m.SetHeader("To", to) // 收件人 + m.SetHeader("Subject", subject) // 邮件主题 + m.SetBody("text/html", body) // 邮件内容(纯文本) + + // 设置 SMTP 服务器信息 + d := gomail.NewDialer(smtpHost, utility.StringToInt(smtpPort), from, password) + + // 发送邮件 + if err := d.DialAndSend(m); err != nil { + log.Error("发送邮件失败: %v", err) + return err + } + return nil +} + +// SendEmail 发送邮件 +//func SendEmail(send config.EmailSend) (string, bool) { +// m := gomail.NewMessage() +// m.SetHeader("From", m.FormatAddress(send.From, send.Username)) // 这里等软件名字定下来以后使用多语言配置具体名称 +// m.SetHeader("To", send.To) // "bob@example.com", "cora@example.com") +// // m.SetAddressHeader("Cc", "dan@example.com", "Dan") +// m.SetHeader("Subject", send.Subject) +// m.SetBody("text/html", send.Content) // "Hello Bob and Cora!") +// // m.Attach("/home/Alex/lolcat.jpg") +// key := send.Server + send.From +// gmailClient := getGoMailIn(key) +// if gmailClient == nil { +// mu.Lock() +// d := gomail.NewDialer(send.Server, send.Port, send.From, send.Secret) +// d.TLSConfig = &tls.Config{InsecureSkipVerify: true} +// cacheEmail[key] = d +// mu.Unlock() +// gmailClient = d +// } +// +// // Send the email to Bob, Cora and Dan. +// if err := gmailClient.DialAndSend(m); err != nil { +// log.Error("d.DialAndSend", zap.Error(err)) +// return "发送失败", false +// } +// +// return "", true +//} + +var ( + apiKeyPublic = "00d2889da90d5d90767bf04dc1bdc6fa" + apiKeyPrivate = "f68cd84cd88b7e2aabce79c878a77e97" +) + +func MailJetSend(receive string, code string) error { + mailjetClient := mailjet.NewMailjetClient(apiKeyPublic, apiKeyPrivate) //"00d2889da90d5d90767bf04dc1bdc6fa", "f68cd84cd88b7e2aabce79c878a77e97") + messagesInfo := []mailjet.InfoMessagesV31{ + { + From: &mailjet.RecipientV31{ + Email: "email@tokex.shop", + Name: "Tokex", + }, + To: &mailjet.RecipientsV31{ + mailjet.RecipientV31{ + Email: receive, + Name: "", + }, + }, + Subject: "【Tokex】邮箱验证码", + TextPart: "您的邮箱验证码为:" + code, + // HTMLPart: "

欢迎注册登录我们的服务,您的验证码为:1234567


May the delivery force be with you!", + // CustomID: "AppGettingStartedTest", + // TemplateID: 1234, + }, + } + messages := mailjet.MessagesV31{Info: messagesInfo} + res, err := mailjetClient.SendMailV31(&messages) + if err != nil { + log.Error("MailJetSend", zap.Error(err)) + return err + } + + // fmt.Printf("Data: %+v\n", res) + if res.ResultsV31[0].Status != "success" { + log.Error("发送失败", zap.Any("resilt", res)) + return errors.New("发送失败") + } + + return nil +} + +func MailJetSendMsg(receive string, subject, textPart string) error { + mailjetClient := mailjet.NewMailjetClient(apiKeyPublic, apiKeyPrivate) //"00d2889da90d5d90767bf04dc1bdc6fa", "f68cd84cd88b7e2aabce79c878a77e97") + messagesInfo := []mailjet.InfoMessagesV31{ + { + From: &mailjet.RecipientV31{ + Email: "email@tokex.shop", + Name: "Tokex", + }, + To: &mailjet.RecipientsV31{ + mailjet.RecipientV31{ + Email: receive, + Name: "", + }, + }, + Subject: subject, + TextPart: textPart, + }, + } + messages := mailjet.MessagesV31{Info: messagesInfo} + res, err := mailjetClient.SendMailV31(&messages) + if err != nil { + log.Error("MailJetSend", zap.Error(err)) + return err + } + + // fmt.Printf("Data: %+v\n", res) + if res.ResultsV31[0].Status != "success" { + log.Error("发送失败", zap.Any("resilt", res)) + return errors.New("发送失败") + } + + return nil +} diff --git a/pkg/httpclient/httpclient.go b/pkg/httpclient/httpclient.go new file mode 100644 index 0000000..9950ea0 --- /dev/null +++ b/pkg/httpclient/httpclient.go @@ -0,0 +1,236 @@ +package httpclient + +import ( + "bytes" + "crypto/tls" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" + + "go.uber.org/zap" + + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/valyala/fasthttp" +) + +var ( + client = &fasthttp.Client{MaxConnsPerHost: 1000000, TLSConfig: &tls.Config{InsecureSkipVerify: true}, ReadTimeout: time.Second * 60, MaxIdemponentCallAttempts: 0} + clientForm = &fasthttp.Client{MaxConnsPerHost: 1000000, TLSConfig: &tls.Config{InsecureSkipVerify: true}, ReadTimeout: time.Second * 60, MaxIdemponentCallAttempts: 0} +) + +// JumioPostBasicAuth +func JumioPostBasicAuth(uri, username, password string, data url.Values) ([]byte, error) { + client := &http.Client{ + Timeout: time.Second * 10, + } + + req, err := http.NewRequest("POST", uri, strings.NewReader(data.Encode())) + if err != nil { + return nil, err + } + + req.SetBasicAuth(username, password) + req.Header.Set("Accept", "application/json") + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + response, err := client.Do(req) + if err != nil { + return nil, err + } + defer response.Body.Close() + + body, _ := ioutil.ReadAll(response.Body) + return body, nil +} + +// JumioPostOauth +func JumioPostOauth(uri, token string, data []byte) ([]byte, error) { + client := &http.Client{ + Timeout: time.Second * 10, + } + req, err := http.NewRequest("POST", uri, bytes.NewReader(data)) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", token)) + req.Header.Set("User-Agent", "myapp-v1.0.0") + req.Header.Set("Accept", "*/*") + req.Header.Set("Content-Type", "application/json") + + response, err := client.Do(req) + if err != nil { + return nil, err + } + defer response.Body.Close() + + body, _ := ioutil.ReadAll(response.Body) + return body, nil + +} + +// JumioHttp +func JumioHttp(method, uri, accessToken string, data url.Values) ([]byte, error) { + client := &http.Client{ + Timeout: time.Second * 10, + } + req, err := http.NewRequest(method, uri, strings.NewReader(data.Encode())) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", accessToken)) + + response, err := client.Do(req) + if err != nil { + return nil, err + } + defer response.Body.Close() + + body, _ := ioutil.ReadAll(response.Body) + xx := string(body) + fmt.Println(xx) + return body, nil +} + +// FastPostByte do application/json POST request via fasthttp +func FastPostByte(url string, body []byte) ([]byte, error) { + req := fasthttp.AcquireRequest() + resp := fasthttp.AcquireResponse() + defer func() { + fasthttp.ReleaseResponse(resp) + fasthttp.ReleaseRequest(req) + }() + req.SetRequestURI(url) + //req.Header.SetContentType("application/x-www-form-urlencoded") + req.Header.SetContentType("application/json; charset=utf-8") + req.Header.Add("Accept", "application/json") + // if w.Authentication && len(w.JwtToken) > 0 { + // req.Header.Set("Authorization", "Bearer "+w.JwtToken) + // } + + req.Header.SetMethod("POST") + req.SetBody(body) + + // if !loghelper.LevelIsError() { + // loghelper.InfoLog("FastPostByte:", string(body)) + // } + err := client.Do(req, resp) + if err != nil { + return nil, err + } + recbody := resp.Body() + arr := make([]byte, len(recbody)) + copy(arr, recbody) + return arr, nil +} + +// FastGet Fast Get +// +// @params header 格式 []string{ +// "key:value", +// "key:value", +// } +// +// @return "中国,深圳" +func FastGet(url string, header ...string) ([]byte, error) { + req := fasthttp.AcquireRequest() + resp := fasthttp.AcquireResponse() + defer func() { + fasthttp.ReleaseResponse(resp) + fasthttp.ReleaseRequest(req) + }() + req.SetRequestURI(url) + + // if w.Authentication && len(w.JwtToken) > 0 { + // req.Header.Set("Authorization", "Bearer "+w.JwtToken) + // } + + for _, h := range header { + kv := strings.Split(h, ":") + req.Header.Set(kv[0], kv[1]) + } + + // define webapi client request Method + req.Header.SetMethod("GET") + // DO GET request + err := client.Do(req, resp) + if err != nil { + return nil, err + } + body := resp.Body() + // arr := make([]byte, len(body)) + // copy(arr, body) + return body, nil +} + +// fastFormPost do POST request via fasthttp +func fastFormPost(url string, parmlist map[string]string) (*fasthttp.Response, error) { + req := fasthttp.AcquireRequest() + resp := fasthttp.AcquireResponse() + defer func() { + fasthttp.ReleaseResponse(resp) + fasthttp.ReleaseRequest(req) + }() + req.SetRequestURI(url) + // if w.Authentication && len(w.JwtToken) > 0 { + // req.Header.Set("Authorization", "Bearer "+w.JwtToken) + // } + req.Header.SetMethod("POST") + args := req.PostArgs() + for k, v := range parmlist { + args.Set(k, v) + } + err := clientForm.Do(req, resp) // fasthttp.DoTimeout(req, resp, timeOut) + if err != nil { + log.Error("post request error", zap.Error(err)) + return nil, err + } + out := fasthttp.AcquireResponse() + resp.CopyTo(out) + return out, nil +} + +// PostForm do POST request via fasthttp +func PostForm(url string, parmlist map[string]string) ([]byte, error) { + resp, err := fastFormPost(url, parmlist) + if err != nil { + return nil, err + } + arr := make([]byte, len(resp.Body())) + copy(arr, resp.Body()) + // defer fasthttp.ReleaseResponse(resp) + // return resp.Body() + return arr, nil +} + +// ClientIP 获取真实的IP 1.1.1.1, 2.2.2.2, 3.3.3.3 +func ClientIP(ctx *fasthttp.RequestCtx) string { + clientIP := string(ctx.Request.Header.Peek("X-Forwarded-For")) + if index := strings.IndexByte(clientIP, ','); index >= 0 { + clientIP = clientIP[0:index] + // 获取最开始的一个 即 1.1.1.1 + } + clientIP = strings.TrimSpace(clientIP) + if len(clientIP) > 0 { + return clientIP + } + clientIP = strings.TrimSpace(string(ctx.Request.Header.Peek("X-Real-Ip"))) + if len(clientIP) > 0 { + return clientIP + } + return ctx.RemoteIP().String() +} + +// MergeQuery appends additional query values to an existing URL. +func MergeQuery(u url.URL, q url.Values) url.URL { + uv := u.Query() + for k, vs := range q { + for _, v := range vs { + uv.Add(k, v) + } + } + u.RawQuery = uv.Encode() + return u +} diff --git a/pkg/httpclient/httputils.go b/pkg/httpclient/httputils.go new file mode 100644 index 0000000..1c9dfe8 --- /dev/null +++ b/pkg/httpclient/httputils.go @@ -0,0 +1,192 @@ +package httpclient + +// http request 工具函数 +import ( + "crypto/tls" + "errors" + "fmt" + "log" + "net/url" + "strings" + "time" + + "github.com/bytedance/sonic" + "github.com/valyala/fasthttp" + "github.com/valyala/fasthttp/fasthttpproxy" +) + +var ( + fastHttpClient = &fasthttp.Client{ + Name: "http", + MaxConnsPerHost: 10000, + MaxIdleConnDuration: 30 * time.Second, + ReadTimeout: 50 * time.Second, + WriteTimeout: 50 * time.Second, + TLSConfig: &tls.Config{InsecureSkipVerify: true}, + } + socksDialer fasthttp.DialFunc + proxyUrl = "" + proxyScheme = "socks5" +) + +func NewHttpRequestWithFasthttp(reqMethod, reqUrl, postData string, headers map[string]string) ([]byte, error) { + // loghelper.InfoLog("use fasthttp client") + if len(proxyUrl) > 0 { + + if socksDialer == nil { + socksDialer = fasthttpproxy.FasthttpSocksDialer(strings.TrimPrefix(proxyUrl, proxyScheme+"://")) + fastHttpClient.Dial = socksDialer + } + } + req := fasthttp.AcquireRequest() + resp := fasthttp.AcquireResponse() + defer func() { + fasthttp.ReleaseRequest(req) + fasthttp.ReleaseResponse(resp) + }() + + for k, v := range headers { + req.Header.Set(k, v) + } + req.Header.SetMethod(reqMethod) + req.SetRequestURI(reqUrl) + req.SetBodyString(postData) + + err := fastHttpClient.Do(req, resp) + if err != nil { + return nil, err + } + + if resp.StatusCode() != 200 { + return nil, errors.New(fmt.Sprintf("HttpStatusCode:%d ,Desc:%s", resp.StatusCode(), string(resp.Body()))) + } + return resp.Body(), nil +} + +func HttpGet(reqUrl string) (map[string]interface{}, error) { + respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", nil) + if err != nil { + return nil, err + } + + var bodyDataMap map[string]interface{} + err = sonic.Unmarshal(respData, &bodyDataMap) + if err != nil { + log.Println(string(respData)) + return nil, err + } + return bodyDataMap, nil +} + +func HttpGet2(reqUrl string, headers map[string]string) (map[string]interface{}, error) { + if headers == nil { + headers = map[string]string{} + } + headers["Content-Type"] = "application/x-www-form-urlencoded" + respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", headers) + if err != nil { + return nil, err + } + + var bodyDataMap map[string]interface{} + err = sonic.Unmarshal(respData, &bodyDataMap) + if err != nil { + log.Println("respData", string(respData)) + return nil, err + } + return bodyDataMap, nil +} + +func HttpGet3(reqUrl string, headers map[string]string) ([]interface{}, error) { + if headers == nil { + headers = map[string]string{} + } + headers["Content-Type"] = "application/x-www-form-urlencoded" + respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", headers) + if err != nil { + return nil, err + } + // println(string(respData)) + var bodyDataMap []interface{} + err = sonic.Unmarshal(respData, &bodyDataMap) + if err != nil { + log.Println("respData", string(respData)) + return nil, err + } + return bodyDataMap, nil +} + +func HttpGet4(reqUrl string, headers map[string]string, result interface{}) error { + if headers == nil { + headers = map[string]string{} + } + headers["Content-Type"] = "application/x-www-form-urlencoded" + respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", headers) + if err != nil { + return err + } + + err = sonic.Unmarshal(respData, result) + if err != nil { + log.Printf("HttpGet4 - jsonhelper.Unmarshal failed : %v, resp %s", err, string(respData)) + return err + } + + return nil +} + +func HttpGet5(reqUrl string, headers map[string]string) ([]byte, error) { + if headers == nil { + headers = map[string]string{} + } + headers["Content-Type"] = "application/x-www-form-urlencoded" + respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", headers) + if err != nil { + return nil, err + } + + return respData, nil +} + +func HttpPostForm(reqUrl string, postData url.Values) ([]byte, error) { + headers := map[string]string{ + "Content-Type": "application/x-www-form-urlencoded"} + return NewHttpRequestWithFasthttp("POST", reqUrl, postData.Encode(), headers) +} + +func HttpPostForm2(reqUrl string, postData url.Values, headers map[string]string) ([]byte, error) { + if headers == nil { + headers = map[string]string{} + } + headers["Content-Type"] = "application/x-www-form-urlencoded" + return NewHttpRequestWithFasthttp("POST", reqUrl, postData.Encode(), headers) +} + +func HttpPostForm3(reqUrl string, postData string, headers map[string]string) ([]byte, error) { + return NewHttpRequestWithFasthttp("POST", reqUrl, postData, headers) +} + +func HttpPostForm4(reqUrl string, postData map[string]string, headers map[string]string) ([]byte, error) { + if headers == nil { + headers = map[string]string{} + } + headers["Content-Type"] = "application/json" + data, _ := sonic.Marshal(postData) + return NewHttpRequestWithFasthttp("POST", reqUrl, string(data), headers) +} + +func HttpDeleteForm(reqUrl string, postData url.Values, headers map[string]string) ([]byte, error) { + if headers == nil { + headers = map[string]string{} + } + headers["Content-Type"] = "application/x-www-form-urlencoded" + return NewHttpRequestWithFasthttp("DELETE", reqUrl, postData.Encode(), headers) +} + +func HttpPut(reqUrl string, postData url.Values, headers map[string]string) ([]byte, error) { + if headers == nil { + headers = map[string]string{} + } + headers["Content-Type"] = "application/x-www-form-urlencoded" + return NewHttpRequestWithFasthttp("PUT", reqUrl, postData.Encode(), headers) +} diff --git a/pkg/httputils/httputils.go b/pkg/httputils/httputils.go new file mode 100644 index 0000000..f3b57cb --- /dev/null +++ b/pkg/httputils/httputils.go @@ -0,0 +1,367 @@ +package httputils + +// http request 工具函数 +import ( + "crypto/tls" + "errors" + "fmt" + "io/ioutil" + "log" + "net" + "net/http" + "net/url" + "strings" + "time" + + "github.com/bytedance/sonic" + "github.com/valyala/fasthttp" + "golang.org/x/net/proxy" +) + +var ( + fastHttpClient = &fasthttp.Client{ + Name: "http", + MaxConnsPerHost: 10000, + MaxIdleConnDuration: 30 * time.Second, + ReadTimeout: 50 * time.Second, + WriteTimeout: 50 * time.Second, + TLSConfig: &tls.Config{InsecureSkipVerify: true}, // 跳过证书验证 + } + clientHuoBi *http.Client // 火币交易所 HTTP 客户端 + clientBinance *http.Client // 币安交易所 HTTP 客户端 +) + +func init() { + // 克隆默认的 HTTP 传输配置 + t := http.DefaultTransport.(*http.Transport).Clone() + t.MaxIdleConns = 100 + t.MaxConnsPerHost = 10000 + t.MaxIdleConnsPerHost = 100 + t.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} // 跳过证书验证 + + // 初始化火币交易所 HTTP 客户端 + clientHuoBi = &http.Client{ + Timeout: 50 * time.Second, // 请求超时时间 + Transport: t, + } + + // 初始化币安交易所 HTTP 客户端 + clientBinance = &http.Client{ + Timeout: 50 * time.Second, // 请求超时时间 + Transport: t, + } +} + +/* +初始化请求代理 +- @proxy 代理地址(http) ip:port +*/ +func InitProxy(proxy string) { + if proxy != "" { + fastHttpClient.Dial = createHTTPProxyDialer(proxy) // 设置代理拨号器 + } +} + +// net http 请求(币安) +func NewHttpRequestBinance(reqMethod, reqUrl, postData string, reqHeaders map[string]string) ([]byte, error) { + req, _ := http.NewRequest(reqMethod, reqUrl, strings.NewReader(postData)) // 创建新的 HTTP 请求 + if req.Header.Get("User-Agent") == "" { + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36") // 设置用户代理 + } + if reqHeaders != nil { + for k, v := range reqHeaders { + req.Header.Add(k, v) // 添加请求头 + } + } + + resp, err := clientBinance.Do(req) // 发送请求 + if err != nil { + return nil, err + } + + defer resp.Body.Close() // 确保关闭响应体 + + bodyData, err := ioutil.ReadAll(resp.Body) // 读取响应体 + if err != nil { + return nil, err + } + + arr := make([]byte, len(bodyData)) + copy(arr, bodyData) + + if resp.StatusCode != 200 { // 检查响应状态码 + return nil, errors.New(string(arr)) + } + + return arr, nil +} + +// fast http 执行请求 +func NewHttpRequestWithFasthttp(reqMethod, reqUrl, postData string, headers map[string]string) ([]byte, error) { + req := fasthttp.AcquireRequest() // 从 fasthttp 获取请求 + resp := fasthttp.AcquireResponse() // 从 fasthttp 获取响应 + defer func() { + fasthttp.ReleaseRequest(req) // 释放请求 + fasthttp.ReleaseResponse(resp) // 释放响应 + }() + + for k, v := range headers { + req.Header.Set(k, v) // 设置请求头 + } + req.Header.SetMethod(reqMethod) // 设置请求方法 + req.SetRequestURI(reqUrl) // 设置请求 URL + req.SetBodyString(postData) // 设置请求体 + + err := fastHttpClient.Do(req, resp) // 执行请求 + if err != nil { + return nil, err + } + + recbody := resp.Body() // 获取响应体 + arr := make([]byte, len(recbody)) + copy(arr, recbody) + + if resp.StatusCode() != 200 { // 检查响应状态码 + return nil, errors.New(fmt.Sprintf("HttpStatusCode:%d ,Desc:%s", resp.StatusCode(), string(recbody))) + } + + return recbody, nil +} + +// HTTP GET 请求,返回 map +func HttpGet(reqUrl string) (map[string]interface{}, error) { + respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", nil) // 发送 GET 请求 + if err != nil { + return nil, err + } + + var bodyDataMap map[string]interface{} + err = sonic.Unmarshal(respData, &bodyDataMap) // 解析 JSON 响应 + if err != nil { + log.Println(string(respData)) // 打印响应数据 + return nil, err + } + return bodyDataMap, nil +} + +// HTTP GET 请求,带请求头 +func HttpGet2(reqUrl string, headers map[string]string) (map[string]interface{}, error) { + if headers == nil { + headers = map[string]string{} + } + headers["Content-Type"] = "application/x-www-form-urlencoded" // 设置内容类型 + respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", headers) // 发送 GET 请求 + if err != nil { + return nil, err + } + + var bodyDataMap map[string]interface{} + err = sonic.Unmarshal(respData, &bodyDataMap) // 解析 JSON 响应 + if err != nil { + log.Println("respData", string(respData)) // 打印响应数据 + return nil, err + } + return bodyDataMap, nil +} + +// HTTP GET 请求,返回接口切片 +func HttpGet3(reqUrl string, headers map[string]string) ([]interface{}, error) { + if headers == nil { + headers = map[string]string{} + } + headers["Content-Type"] = "application/x-www-form-urlencoded" // 设置内容类型 + respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", headers) // 发送 GET 请求 + if err != nil { + return nil, err + } + var bodyDataMap []interface{} + err = sonic.Unmarshal(respData, &bodyDataMap) // 解析 JSON 响应 + if err != nil { + log.Println("respData", string(respData)) // 打印响应数据 + return nil, err + } + return bodyDataMap, nil +} + +// HTTP GET 请求,结果存储在 result 中 +func HttpGet4(reqUrl string, headers map[string]string, result interface{}) error { + if headers == nil { + headers = map[string]string{} + } + headers["Content-Type"] = "application/x-www-form-urlencoded" // 设置内容类型 + respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", headers) // 发送 GET 请求 + if err != nil { + return err + } + + err = sonic.Unmarshal(respData, result) // 解析 JSON 响应 + if err != nil { + log.Printf("HttpGet4 - json.Unmarshal failed : %v, resp %s", err, string(respData)) + return err + } + + return nil +} + +// HTTP GET 请求,返回原始字节 +func HttpGet5(reqUrl string, headers map[string]string) ([]byte, error) { + if headers == nil { + headers = map[string]string{} + } + headers["Content-Type"] = "application/x-www-form-urlencoded" // 设置内容类型 + respData, err := NewHttpRequestWithFasthttp("GET", reqUrl, "", headers) // 发送 GET 请求 + if err != nil { + return nil, err + } + + return respData, nil +} + +// HTTP POST 请求,表单数据 +func HttpPostForm(reqUrl string, postData url.Values) ([]byte, error) { + respData, err := NewHttpRequestWithFasthttp("POST", reqUrl, postData.Encode(), nil) // 发送 POST 请求 + if err != nil { + return nil, err + } + + return respData, nil +} + +// HTTP POST 请求,返回 map +func HttpPost(reqUrl string, postData string) (map[string]interface{}, error) { + respData, err := NewHttpRequestWithFasthttp("POST", reqUrl, postData, nil) // 发送 POST 请求 + if err != nil { + return nil, err + } + + var bodyDataMap map[string]interface{} + err = sonic.Unmarshal(respData, &bodyDataMap) // 解析 JSON 响应 + if err != nil { + log.Println(string(respData)) // 打印响应数据 + return nil, err + } + return bodyDataMap, nil +} + +// HTTP POST 请求,返回原始字节 +func HttpPost2(reqUrl string, postData string) ([]byte, error) { + respData, err := NewHttpRequestWithFasthttp("POST", reqUrl, postData, nil) // 发送 POST 请求 + if err != nil { + return nil, err + } + + return respData, nil +} + +// HTTP POST 请求,返回接口切片 +func HttpPost3(reqUrl string, postData string) ([]interface{}, error) { + respData, err := NewHttpRequestWithFasthttp("POST", reqUrl, postData, nil) // 发送 POST 请求 + if err != nil { + return nil, err + } + + var bodyDataMap []interface{} + err = sonic.Unmarshal(respData, &bodyDataMap) // 解析 JSON 响应 + if err != nil { + log.Println(string(respData)) // 打印响应数据 + return nil, err + } + return bodyDataMap, nil +} + +// HTTP POST 请求,结果存储在 result 中 +func HttpPost4(reqUrl string, postData string, result interface{}) error { + respData, err := NewHttpRequestWithFasthttp("POST", reqUrl, postData, nil) // 发送 POST 请求 + if err != nil { + return err + } + + err = sonic.Unmarshal(respData, result) // 解析 JSON 响应 + if err != nil { + log.Printf("HttpPost4 - json.Unmarshal failed : %v, resp %s", err, string(respData)) + return err + } + + return nil +} + +// HTTP POST 请求,返回原始字节 +func HttpPost5(reqUrl string, postData string) ([]byte, error) { + respData, err := NewHttpRequestWithFasthttp("POST", reqUrl, postData, nil) // 发送 POST 请求 + if err != nil { + return nil, err + } + + return respData, nil +} + +// HTTP POST 请求,表单数据,带请求头 +func HttpPostForm2(reqUrl string, postData url.Values, headers map[string]string) ([]byte, error) { + respData, err := NewHttpRequestWithFasthttp("POST", reqUrl, postData.Encode(), headers) // 发送 POST 请求 + if err != nil { + return nil, err + } + + return respData, nil +} + +// 创建 HTTP 代理拨号器 +func createHTTPProxyDialer(proxy string) fasthttp.DialFunc { + // 解析代理 URL + proxyURL, err := url.Parse(proxy) + if err != nil { + log.Fatal(err) + } + + // 返回拨号器函数 + return func(addr string) (net.Conn, error) { + + // 选择代理协议 + switch proxyURL.Scheme { + case "http", "https": + proxyConn, err := net.Dial("tcp", proxyURL.Host) + if err != nil { + return nil, err + } + + // Send the HTTP CONNECT request to the proxy + _, err = proxyConn.Write([]byte("CONNECT " + addr + " HTTP/1.1\r\nHost: " + addr + "\r\n\r\n")) + if err != nil { + return nil, err + } + + // Read the response from the proxy + buf := make([]byte, 4096) + n, err := proxyConn.Read(buf) + if err != nil { + return nil, err + } + + // Check for a successful response (HTTP 200) + if !isConnectSuccess(buf[:n]) { + return nil, err + } + return proxyConn, nil + case "socks5": + return socks5Dial(proxyURL.Host) + default: + return nil, fmt.Errorf("不支持的代理协议: %s", proxyURL.Scheme) + } + } +} + +func isConnectSuccess(response []byte) bool { + return len(response) > 0 && strings.HasPrefix(string(response), "HTTP/1.1 200") +} + +// socks5Dial 使用 SOCKS5 代理拨号 +func socks5Dial(proxyAddr string) (net.Conn, error) { + // 创建 SOCKS5 代理拨号器 + dialer, err := proxy.SOCKS5("tcp", proxyAddr, nil, proxy.Direct) + if err != nil { + return nil, err + } + + // 使用代理拨号 + return dialer.Dial("tcp", "destination_address:port") // 替换为目标地址和端口 +} diff --git a/pkg/jsonhelper/jsonhelper.go b/pkg/jsonhelper/jsonhelper.go new file mode 100644 index 0000000..cf51d6b --- /dev/null +++ b/pkg/jsonhelper/jsonhelper.go @@ -0,0 +1,36 @@ +package jsonhelper + +import ( + "github.com/bytedance/sonic" + jsonIterator "github.com/json-iterator/go" + "github.com/vmihailenco/msgpack/v5" +) + +var ( + IJson = jsonIterator.ConfigCompatibleWithStandardLibrary + // Marshal is exported by common package. + Marshal = IJson.Marshal + // Unmarshal is exported by common package. + Unmarshal = IJson.Unmarshal + // MarshalIndent is exported by common package. + MarshalIndent = IJson.MarshalIndent + // NewDecoder is exported by common package. + NewDecoder = IJson.NewDecoder + // NewEncoder is exported by common package. + NewEncoder = IJson.NewEncoder + + // MarshalMsgPack msgpack方式序列化 + MarshalMsgPack = msgpack.Marshal + // msgpack方式反序列化 + UnmarshalMsgPack = msgpack.Unmarshal + NewDecoderMsgPack = msgpack.NewDecoder + NewEncoderMsgPack = msgpack.NewEncoder +) + +func ToJsonString(v interface{}) string { + if result, err := sonic.Marshal(v); err == nil { + return string(result) + } + + return "" +} diff --git a/pkg/jsonhelper/jsonhelper_test.go b/pkg/jsonhelper/jsonhelper_test.go new file mode 100644 index 0000000..3f53cdb --- /dev/null +++ b/pkg/jsonhelper/jsonhelper_test.go @@ -0,0 +1,31 @@ +package jsonhelper + +import ( + "encoding/json" + "testing" +) + +var jsonStr = `{"W":1561651}` + +var obj struct { + W int +} + +// 对比下 IJson 和 系统自带的 json 的效率 +// go test -bench=_QE_ -benchmem -run=^$ +// -benchtime 默认为1秒 -benchmem 获得内存分配的统计数据 +func Benchmark_QE_1(b *testing.B) { + for i := 0; i < b.N; i++ { + json.Unmarshal([]byte(jsonStr), &obj) + } +} + +func Benchmark_QE_2(b *testing.B) { + for i := 0; i < b.N; i++ { + Unmarshal([]byte(jsonStr), &obj) + } +} + +// Benchmark_QE_1-6 2252055 531.2 ns/op 240 B/op 6 allocs/op +// Benchmark_QE_2-6 7650109 158.7 ns/op 16 B/op 1 allocs/op +// 确实快了很多 不知道复杂的结构会不会不一样 diff --git a/pkg/timehelper/timehelper.go b/pkg/timehelper/timehelper.go new file mode 100644 index 0000000..d26a058 --- /dev/null +++ b/pkg/timehelper/timehelper.go @@ -0,0 +1,119 @@ +package timehelper + +import "time" + +//GetDateUnixDate 返回带毫秒的时间戳,如果需要转化为时间类型time, +//不带毫秒的:time.Unix(1663315884651/1000,0), +//带毫秒的time.Unix(1663315884651/1000, 1000000*(1663315884651%1000)) +func GetDateUnixDate(date time.Time) int64 { + return date.UnixMilli() //Unix() //* 1000 +} + +//GetUnixDate 时间撮转化为time类型 +func GetUnixDate(date int64) time.Time { + return time.Unix(date/1000, 1000000*(date%1000)) +} + +func ConvertTimeLocalSec(date int64) time.Time { + d1 := time.Unix(date/1000, 1000000*(date%1000)) + d2 := time.Date(d1.Year(), d1.Month(), d1.Day(), d1.Hour(), d1.Minute(), d1.Second(), d1.Nanosecond(), time.Local) + return d2 +} + +//TimeSubDays 时间间隔天数 +func TimeSubDays(t1, t2 time.Time) int { + + if t1.Location().String() != t2.Location().String() { + return -1 + } + hours := t1.Sub(t2).Hours() + + if hours <= 0 { + return -1 + } + // sub hours less than 24 + if hours < 24 { + // may same day + t1y, t1m, t1d := t1.Date() + t2y, t2m, t2d := t2.Date() + isSameDay := t1y == t2y && t1m == t2m && t1d == t2d + + if isSameDay { + return 0 + } + return 1 + } + // equal or more than 24 + if (hours/24)-float64(int(hours/24)) == 0 { // just 24's times + return int(hours / 24) + } + // more than 24 hours + return int(hours/24) + 1 +} + +//ConvertTimeLocal 时间在数据库返回给结构体的时候,结构体的时间时区是非本地的,需要转化为本地的再转变为时间戳 +func ConvertTimeLocal(d1 time.Time) time.Time { + d2 := time.Date(d1.Year(), d1.Month(), d1.Day(), d1.Hour(), d1.Minute(), d1.Second(), 0, time.Local) + return d2 +} + +//ConvertTimeDayLocal s +func ConvertTimeDayLocal(d1 time.Time) time.Time { + d2 := time.Date(d1.Year(), d1.Month(), d1.Day(), 0, 0, 0, 0, time.Local) + return d2 +} + +//转为时分秒 +func ToTimeHour(ltime int64) string { + now := IntToTime(ltime) + return now.Format("15:04:05") +} + +// func ParseInLocation(layout, value string, loc *Location) (Time, error) (layout已带时区时可直接用Parse) +//time.ParseInLocation("2006-01-02 15:04:05", "2017-05-11 14:06:06", time.Local) + +//转为时间类型 +func IntToTime(intime int64) time.Time { + return time.Unix(intime/1000, 0) +} + +//GetPastDay 当前时间算起,过去num天内的开始日期、结束日期 +func GetPastDay(num int) (start, end time.Time) { + tn := time.Now() + //当前时间,天数为单位 + nowday := time.Date(tn.Year(), tn.Month(), tn.Day(), 0, 0, 0, 0, time.Local) + //过去30天 + oldTime := nowday.AddDate(0, 0, -num) + return oldTime, nowday +} + +//ConvertTimeToString 格式时间,返回前台,yyyy-mm-dd hh:mm:ss +func ConvertTimeToString(t time.Time) string { + return t.Format("2006-01-02 15:04:05") +} + +//ConvertStringToTime 格式时间,返回前台,yyyy-mm-dd hh:mm:ss +func ConvertStringToTime(t string) time.Time { + t2, _ := time.Parse("2006-01-02 15:04:05", t) + return t2 +} + +//ConvertTimeToStringMin 格式时间,返回前台,yyyy-mm-dd hh:mm:ss +func ConvertTimeToStringMin(t time.Time) string { + return t.Format("2006-01-02 15:04") +} + +//ConvertTimeToStringDay 格式时间,返回前台,yyyy-mm-dd +func ConvertTimeToStringDay(t time.Time) string { + return t.Format("2006-01-02") +} + +//ConvertTimeToString2 格式时间,返回前台,yyyy.mm.dd hh:mm 2020.06.02 17:50 +func ConvertTimeToString2(t time.Time) string { + return t.Format("2006.01.02 15:04") +} + +//ConvertTimeToStringYear 格式时间,返回前台,mm-dd yyyy +func ConvertTimeToStringYear(s time.Time) string { + return s.Format("01-02 2006") +} diff --git a/pkg/udunhelper/udun.go b/pkg/udunhelper/udun.go new file mode 100644 index 0000000..64befa6 --- /dev/null +++ b/pkg/udunhelper/udun.go @@ -0,0 +1,295 @@ +package udunhelper + +import ( + "crypto/md5" + "crypto/tls" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + log "github.com/go-admin-team/go-admin-core/logger" + ext "go-admin/config" + "go-admin/pkg/jsonhelper" + "go-admin/pkg/utility/seqs" + "io" + "net/http" + "strings" + "time" + + "go.uber.org/zap" +) + +const ( + createAddressUrl = "/mch/address/create" + withdrawUrl = "/mch/withdraw" + checkAddressUrl = "/mch/check/address" + supportCoinsUrl = "/mch/support-coins" + existAddressUrl = "/mch/exist/address" + + Notify = "/api/v1/line/notify" +) + +var ( + clientUDun *http.Client +) + +func init() { + t := http.DefaultTransport.(*http.Transport).Clone() + t.MaxIdleConns = 100 + t.MaxConnsPerHost = 10000 + t.MaxIdleConnsPerHost = 100 + t.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + + //u盾client + clientUDun = &http.Client{ + Timeout: 50 * time.Second, //time.Duration(timeout) * time.Second, + Transport: t, + } + //UdKey = config.AppGlobalConfig.UDunKey // "d333ae13beb3a96c0225847098267cf3" + //UdMerchantId = config.AppGlobalConfig.UDunMerchantID // "311129" + //CallUrl = config.AppGlobalConfig.CurrServerIp + "/api/recharge/notify" // "https://8.218.110.85/api/recharge/notify" // 回调地址 + //baseUrl = config.AppGlobalConfig.UDunUrl //"https://sig10.udun.io" +} + +/* 回调例子 +"timestamp": 1535005047, + "nonce": 100000, + "sign": "e1bee3a417b9c606ba6cedda26db761a", + "body": "{\"address\":\"DJY781Z8qbuJeuA7C3McYivbX8kmAUXPsW\",\"amount\":\"12345678\",\"blockHigh\":\"102419\",\"coinType\":\"206\",\"decimals\":\"8\",\"fee\":\"452000\", +\"mainCoinType\":\"206\",\"status\":3,\"tradeId\":\"20181024175416907\",\"tradeType\":1,\"txId\":\"31689c332536b56a2246347e206fbed2d04d461a3d668c4c1de32a75a8d436f0\"}" +*/ + +// GetSupportCoinsByMerchant 获取商家支持币种 +func GetSupportCoinsByMerchant(baseReq BaseRequest, showBalance bool) BaseCoinsMerchant { + mp := make(map[string]interface{}, 0) + //mp["merchantId"] = config.AppGlobalConfig.UDunMerchantID + mp["merchantId"] = ext.ExtConfig.UDunConfig.UDunMerchantID + mp["showBalance"] = showBalance + reqBody, _ := json.Marshal(mp) + sign := Signature(baseReq.Nonce, baseReq.Timestamp, string(reqBody)) + url := fmt.Sprintf("%v%v", ext.ExtConfig.UDunConfig.UDunUrl, supportCoinsUrl) + re, err := sendRequestUDun(url, baseReq.Nonce, baseReq.Timestamp, string(reqBody), sign) + if err != nil { + log.Error("GetSupportCoinsByMerchant", zap.Error(err)) + return BaseCoinsMerchant{} + } + + var res BaseCoinsMerchant + err = jsonhelper.Unmarshal(re, &res) + if err != nil { + return BaseCoinsMerchant{} + } + return res +} + +// CreateAddress 创建地址 +func CreateAddress(baseReq BaseRequest, callback []*CallbackRequest) BaseCoinAddress { + for _, c := range callback { + c.MerchantId = ext.ExtConfig.UDunConfig.UDunMerchantID + c.CallUrl = ext.ExtConfig.UDunConfig.CurrServerIp + Notify // "/api/recharge/notify" + } + reqBody, _ := json.Marshal(callback) + sign := Signature(baseReq.Nonce, baseReq.Timestamp, string(reqBody)) + url := fmt.Sprintf("%v%v", ext.ExtConfig.UDunConfig.UDunUrl, createAddressUrl) + re, err := sendRequestUDun(url, baseReq.Nonce, baseReq.Timestamp, string(reqBody), sign) + if err != nil { + log.Error("CreateAddress", zap.Error(err), zap.String("url", url)) + return BaseCoinAddress{} + } + var res BaseCoinAddress + err = jsonhelper.Unmarshal(re, &res) + if err != nil { + log.Error("CreateAddress Unmarshal", zap.Error(err)) + return BaseCoinAddress{} + } + return res +} + +// Withdraw 提现 +func Withdraw(withdraw []*WithdrawRequest) (code int, msg string) { + timeStamp := time.Now().Unix() + nonce := seqs.Rand().RandInt(600000) + // 序列化 + reqBody, _ := json.Marshal(withdraw) + // 签名 + sign := Signature(nonce, timeStamp, string(reqBody)) + url := fmt.Sprintf("%v%v", ext.ExtConfig.UDunConfig.UDunUrl, withdrawUrl) + // 提交 + re, err := sendRequestUDun(url, nonce, timeStamp, string(reqBody), sign) + if err != nil { + log.Error("Withdraw", zap.Error(err)) + return 0, "调用失败" + } + // 反序列化 + var res BaseRes + if err := jsonhelper.Unmarshal(re, &res); err != nil { + return 0, "Withdraw jsonhelper.Unmarshal失败" + } + return res.Code, getWithDrawMsg(res.Code) //res.Message +} + +// CheckAddress 检查地址是否正确 +func CheckAddress(baseReq BaseRequest, verify []*VerifyRequest) (code int, msg string) { + reqBody, _ := json.Marshal(verify) + sign := Signature(baseReq.Nonce, baseReq.Timestamp, string(reqBody)) + url := fmt.Sprintf("%v%v", ext.ExtConfig.UDunConfig.UDunUrl, checkAddressUrl) + re, err := sendRequestUDun(url, baseReq.Nonce, baseReq.Timestamp, string(reqBody), sign) + if err != nil { + log.Error("CheckAddress", zap.Error(err)) + return 0, "调用失败" + } + var res BaseRes + err = jsonhelper.Unmarshal(re, &res) + if err != nil { + return 0, "jsonhelper.Unmarshal失败" + } + return res.Code, res.Message +} + +// ExistAddress 检查地址是否在udun存在 +func ExistAddress(baseReq BaseRequest, verify []*VerifyRequest) (code int, msg string) { + reqBody, _ := json.Marshal(verify) + sign := Signature(baseReq.Nonce, baseReq.Timestamp, string(reqBody)) + url := fmt.Sprintf("%v%v", ext.ExtConfig.UDunConfig.UDunUrl, existAddressUrl) + re, err := sendRequestUDun(url, baseReq.Nonce, baseReq.Timestamp, string(reqBody), sign) + if err != nil { + log.Error("ExistAddress", zap.Error(err)) + return 0, "调用失败" + } + var res BaseRes + err = jsonhelper.Unmarshal(re, &res) + if err != nil { + return 0, "jsonhelper.Unmarshal失败" + } + return res.Code, res.Message +} + +func Signature(nonce int, timestamp int64, body string) string { + msg := fmt.Sprintf("%v%v%v%v", body, ext.ExtConfig.UDunConfig.UDunKey, nonce, timestamp) + h := md5.New() + h.Write([]byte(msg)) + return hex.EncodeToString(h.Sum(nil)) +} + +func CheckCallBackSign(timestamp string, nonce string, body string) string { + raw := body + ext.ExtConfig.UDunConfig.UDunKey + nonce + timestamp + h := md5.New() + h.Write([]byte(raw)) + return hex.EncodeToString(h.Sum(nil)) +} + +func sendRequestUDun(url string, nonce int, timestamp int64, reqBody string, sign string) ([]byte, error) { + q := HttpRequest{Nonce: nonce, Timestamp: timestamp, Sign: sign, Body: reqBody} + body, _ := json.Marshal(q) + headers := map[string]string{} + headers["Content-Type"] = "application/json" + resp, err := newHttpRequest(url, string(body), headers) + if err != nil { + return nil, err + } + return resp, nil +} + +func newHttpRequest(reqUrl, postData string, reqHeaders map[string]string) ([]byte, error) { + req, _ := http.NewRequest("POST", reqUrl, strings.NewReader(postData)) + if req.Header.Get("User-Agent") == "" { + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36") + } + if reqHeaders != nil { + for k, v := range reqHeaders { + req.Header.Add(k, v) + } + } + + resp, err := clientUDun.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + bodyData, err := io.ReadAll(resp.Body) // ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + arr := make([]byte, len(bodyData)) + copy(arr, bodyData) + + if resp.StatusCode != 200 { + return nil, errors.New(string(arr)) + } + + return arr, nil +} + +/* +createAddressUrl 创建地址 接口返回code说明 +-1 生成地址失敗 +200 生成地址成功 +4001 商户不存在 +4005 非法參數 +4045 幣種信息錯誤 +4162 簽名異常 +4163 簽名錯誤 +4166 商戶沒有配置套餐 +4168 商戶地址達到上限 +4169 商戶已禁用 +4175 錢包編號錯誤 +4017 商戶沒有創建錢包 +4176 錢包未添加支持該幣種 +4188 暫不支持 +4226 商戶普通賬戶被禁用 +4261 商戶管理員賬戶被禁用 +4262 賬戶不存在 + +withdrawUrl 提币 接口返回code说明 +200 提幣成功 +523 參數為空 +581 無效的提幣金額 +4005 非法參數 +4014 幣種為空 +4034 未找到該幣種信息 +4162 簽名異常 +4163 簽名錯誤 +4169 商戶已被禁用 +4183 到賬地址異常 +4193 EOS金額小數點後超過4位長度 +4214 暫無可用的幣種 +4226 商戶普通賬戶被禁用 +4261 商戶管理員賬戶被禁用 +4284 商户不存在 +4288 業務編號(BusinessId)重復,請勿重復申請 +4598 傳入body中的list對象中的所有merchantId必須保持一致 +4001 商户不存在 +*/ + +//分充值回調和提幣回調, 其中提幣最多會進行兩次回調( 審核回調 + 交易結果回調) + +//func SendRequest(url string, nonce int, timestamp int64, reqBody string, sign string) (code int64, msg string, extra string) { +// q := HttpRequest{Nonce: nonce, Timestamp: timestamp, Sign: sign, Body: reqBody} +// body, _ := json.Marshal(q) +// resp, err := clientUDun.Post(url, "application/json", bytes.NewBuffer(body)) +// if err != nil { +// return http.StatusBadRequest, err.Error(), "" +// } +// defer resp.Body.Close() +// r, err := ioutil.ReadAll(resp.Body) +// if err != nil { +// return http.StatusBadGateway, err.Error(), "" +// } +// +// js := gjson.ParseBytes(r) +// code = js.Get("code").Int() +// msg = js.Get("message").String() +// if js.Get("data").Exists() { +// extra = js.Get("data").String() +// } +// return +//} + +// CallBackFun 網關收到交易處理結果,調用商戶提供的回調接口,通知商戶具體變化信息。該接口網關發送給您指定的回調地址的內容,處理您的業務信息。 +// 分充值回調和提幣回調, 其中提幣最多會進行兩次回調( 審核回調 + 交易結果回調) +func CallBackFun(res CallBackBase) { + +} diff --git a/pkg/udunhelper/udunmodel.go b/pkg/udunhelper/udunmodel.go new file mode 100644 index 0000000..1726629 --- /dev/null +++ b/pkg/udunhelper/udunmodel.go @@ -0,0 +1,153 @@ +package udunhelper + +type VerifyRequest struct { + MerchantId string `json:"merchantId"` + MainCoinType string `json:"mainCoinType"` + Address string `json:"address"` +} + +type CallbackRequest struct { + MerchantId string `json:"merchantId"` //商户号 + MainCoinType int `json:"mainCoinType"` //主幣種編號,使用獲取商戶幣種信息接口 + CallUrl string `json:"callUrl"` //回調地址,通過該接口創建的地址,以後關於該地址的充幣信息會通過您指定的回調地址通知您 + WalletId string `json:"walletId"` //錢包編號,默認根據主錢包生成地址,可不填写 + Alias string `json:"alias"` //地址別名,可不填写 +} + +type WithdrawRequest struct { + /** + { + "address":"raadSxrUhG5EQVCY75CSGaVLWCeXd6yH6s", + "amount":"0.11", + "merchantId":"100109", + "mainCoinType":"144", + "coinType":"144", + "callUrl":"http://localhost:8080/mch/callBack", + "businessId":"15", + "memo":"10112" + } + */ + + Address string `json:"address"` //提币地址 + Amount string `json:"amount"` //提币数量 + MerchantId string `json:"merchantId"` //商户号 + MainCoinType string `json:"mainCoinType"` //主幣種編號,使用獲取商戶幣種信息接口 + CoinType string `json:"coinType"` //子幣種編號,使用獲取商戶幣種信息接口 + CallUrl string `json:"callUrl"` //回調地址,通過該callUrl告知您該筆提幣交易的狀態 + BusinessId string `json:"businessId"` //業務編號,必須保證該字段在系統內唯一,如果重復,則該筆提幣錢包將不會進行接收 + WalletId string `json:"-"` + Memo string `json:"memo"` //備註,XRP和EOS,這兩種幣的提幣申請該字段可選,其他類型幣種不填 +} +type BaseRequest struct { + Timestamp int64 `json:"timestamp"` + Nonce int `json:"nonce"` +} + +type HttpRequest struct { + Timestamp int64 `json:"timestamp"` //时间戳 + Nonce int `json:"nonce"` //随机数 + Sign string `json:"sign"` //签名 + Body string `json:"body"` //请求body +} + +type BaseRes struct { + Code int `json:"code"` //200才是返回成功调用 + Message string `json:"message"` // +} + +type BaseCoinsMerchant struct { + Code int `json:"code"` //200才是返回成功调用 + Message string `json:"message"` // + Data []CoinsMerchant `json:"data"` // +} + +//CoinsMerchant 商家持仓的币种信息 +type CoinsMerchant struct { + MainCoinType string `json:"mainCoinType"` //主幣種類型 + CoinType string `json:"coinType"` // 幣種類型 + Symbol string `json:"symbol"` //BTC + Name string `json:"name"` //幣種別名,BTC + Logo string `json:"logo"` //幣種log地址 + CoinName string `json:"coinName"` //幣種全稱,Bitcoin + MainSymbol string `json:"mainSymbol"` //主幣種單位 + Decimals string `json:"decimals"` //幣種精度,8 + TokenStatus int `json:"tokenStatus"` //0: 主幣 1:代幣 + Balance int64 `json:"balance"` //幣種余額 +} + +type BaseCoinAddress struct { + Code int `json:"code"` //200才是返回成功调用 + Message string `json:"message"` // + Data CoinAddress `json:"data"` // +} + +//CoinAddress 地址返回 +type CoinAddress struct { + CoinType int `json:"coinType"` // 幣種類型 + Address string `json:"address"` //地址 +} + +type CallBackBase struct { + TimeStamp int64 `json:"timestamp"` + Nonce int `json:"nonce"` + Sign string `json:"sign"` + Body CallBackRes `json:"body"` // +} + +//CallBackRes 回调结构体 +type CallBackRes struct { + Address string `json:"address"` //地址 + Amount string `json:"amount"` //交易數量,根據幣種精度獲取實際金額,實際金額=amount/pow(10,decimals),即實際金額等於amount除以10的decimals次方 + Fee string `json:"fee"` //礦工費,根據幣種精度獲取實際金額,實際金額獲取同上 + Decimals string `json:"decimals"` //幣種精度 + CoinType string `json:"coinType"` //子幣種編號,使用獲取商戶幣種信息接口 + MainCoinType string `json:"mainCoinType"` //主幣種類型 + BusinessId string `json:"businessId"` //業務編號,提幣回調時為提幣請求時傳入的,充幣回調無值 + BlockHigh string `json:"blockHigh"` //區塊高度 + TradeId string `json:"tradeId"` //業務流水號 + TxId string `json:"txid"` //區塊鏈交易哈希 + Memo string `json:"memo"` //備註,XRP和EOS,使用獲取商戶幣種信息接口,這2種類型幣的充提幣可能有值 + Status int `json:"status"` //狀態,0待審核,1審核成功,2審核駁回,3交易成功,4交易失敗 + TradeType int `json:"tradeType"` //交易類型,1充幣回調,2提幣回調 +} + +func getWithDrawMsg(code int) string { + msg := "" + switch code { + case 200: + msg = "提币成功" + case 523: + msg = "参数为空" + case 581: + msg = "无效的提币金额" + case 4005: + msg = "非法参数" + case 4034: + msg = "未找到该币种信息" + case 4162: + msg = "签名异常" + case 4163: + msg = "签名错误" + case 4169: + msg = "商户已被禁用" + case 4183: + msg = "到账地址异常" + case 4193: + msg = "EOS金额小数点后超过4位长度" + case 4214: + msg = "暂无可用的币种" + case 4226: + msg = "商户普通账户被禁用" + case 4261: + msg = "商户管理员账户被禁用" + case 4284: + msg = "商户不存在" + case 4288: + msg = "业务编号重复,请勿重复申请" + case 4598: + msg = "传入body中的list对象中的所有merchantId必须保持一致" + case 4001: + msg = "商户不存在" + } + return msg +} diff --git a/pkg/utility/biginthelper/bighelper.go b/pkg/utility/biginthelper/bighelper.go new file mode 100644 index 0000000..031d0ce --- /dev/null +++ b/pkg/utility/biginthelper/bighelper.go @@ -0,0 +1,35 @@ +package biginthelper + +import ( + "math/big" + "regexp" + "strconv" +) + +// ParseBigInt 将字符串转换为 big.Int +func ParseBigInt(value string) *big.Int { + bi := new(big.Int) + i, ok := bi.SetString(value, 10) + if !ok { + return bi + } + return i +} + +// IntToHex convert int to hexadecimal representation +// int64转换为16进制字符串 +func IntToHex(i int64) string { + return strconv.FormatInt(i, 16) +} + +// BigToHex 将bigint转化为16进制带 0x 的字符串 +func BigToHex(bigInt big.Int) string { + return "0x" + IntToHex(bigInt.Int64()) +} + +// CheckIsAddress Check is a eth Address +// 检查是否是以太坊地址 正则匹配 +func CheckIsAddress(addr string) bool { + re := regexp.MustCompile("^0x[0-9a-fA-F]{40}$") + return re.MatchString(addr) +} diff --git a/pkg/utility/biginthelper/bighelper_test.go b/pkg/utility/biginthelper/bighelper_test.go new file mode 100644 index 0000000..49a3c8c --- /dev/null +++ b/pkg/utility/biginthelper/bighelper_test.go @@ -0,0 +1,55 @@ +package biginthelper + +import ( + "fmt" + "math/rand" + "strconv" + "strings" + "testing" +) + +func Test_ParseBigInt(t *testing.T) { + fmt.Println(ParseBigInt(`231513`)) + fmt.Println(ParseBigInt(`654wdf16`)) + fmt.Println(ParseBigInt(`5455_1655`)) + fmt.Println(ParseBigInt(``)) + fmt.Println(ParseBigInt(`af`)) +} +func Test_IntToHex(t *testing.T) { + fmt.Println(strings.TrimLeft(`01615`, "0")) + fmt.Println(strings.TrimLeft(`1615`, "0")) + fmt.Println(strings.TrimLeft(`0x1615`, "0")) +} + +// i := int64(32) +// s := strconv.FormatInt(i, 16) +// println(s) + +// 对比strconv.FormatInt(i, 16)和fmt.Sprintf("0x%x", i)的性能消耗 +// go test -bench=_QE_ -benchmem +// -benchtime 默认为1秒 -benchmem 获得内存分配的统计数据 +func Benchmark_QE_strconv(b *testing.B) { + for i := 0; i < b.N; i++ { + strconv.FormatInt(getInt64(), 16) + } +} + +func Benchmark_QE_fmt(b *testing.B) { + for i := 0; i < b.N; i++ { + fmt.Sprintf("0x%x", getInt64()) + } +} + +func getInt64() int64 { + return int64(rand.Intn(10000)) +} + +// 结果 +// Benchmark_QE_strconv-6 47142570 24.29 ns/op 5 B/op 1 allocs/op +// Benchmark_QE_fmt-6 14787649 82.41 ns/op 8 B/op 1 allocs/op + +// 改为随机数后 +// Benchmark_QE_strconv-6 27890760 42.48 ns/op 3 B/op 0 allocs/op +// Benchmark_QE_fmt-6 10595380 108.6 ns/op 15 B/op 1 allocs/op + +// 结论 尽量使用 strconv.FormatInt(i, 16) 进行16进制的转换 diff --git a/pkg/utility/biginthelper/biginthelper.go b/pkg/utility/biginthelper/biginthelper.go new file mode 100644 index 0000000..1229492 --- /dev/null +++ b/pkg/utility/biginthelper/biginthelper.go @@ -0,0 +1,119 @@ +package biginthelper + +import ( + "bytes" + "fmt" + "math/big" + "strings" +) + +// BigIntString BigIntString +func BigIntString(balance *big.Int, decimals int64) string { + amount := BigIntFloat(balance, decimals) + deci := fmt.Sprintf("%%0.%vf", decimals) + return clean(fmt.Sprintf(deci, amount)) +} + +// BigIntFloat BigIntFloat +func BigIntFloat(balance *big.Int, decimals int64) *big.Float { + if balance.Sign() == 0 { + return big.NewFloat(0) + } + bal := new(big.Float) + bal.SetInt(balance) + pow := bigPow(10, decimals) + p := big.NewFloat(0) + p.SetInt(pow) + bal.Quo(bal, p) + return bal +} + +func bigPow(a, b int64) *big.Int { + r := big.NewInt(a) + return r.Exp(r, big.NewInt(b), nil) +} + +func clean(newNum string) string { + stringBytes := bytes.TrimRight([]byte(newNum), "0") + newNum = string(stringBytes) + if stringBytes[len(stringBytes)-1] == 46 { + newNum += "0" + } + if stringBytes[0] == 46 { + newNum = "0" + newNum + } + return newNum +} + +// GetActualHex 获取真实的十六进制.. +func GetActualHex(h string) string { + h = strings.TrimLeft(h, "0") + var hex string + if strings.Index(h, "0x") == 0 { + hex = h[2:] + } else { + hex = h + } + if len(h)%2 != 0 { + hex = "0" + hex + } + return "0x" + hex +} + +// HexToBig HexToBig +func HexToBig(h string) *big.Int { + i := big.NewInt(0) + h = strings.Replace(h, "0x", "", -1) + if h == "" { + return i + } + if _, ok := i.SetString(h, 16); !ok { + return nil + } + return i +} + +// ConvertNumToFloat ConvertNumToFloat +func ConvertNumToFloat(num int64) float64 { + switch num { + case 1: + return 10.0 + case 2: + return 100.0 + case 3: + return 1000.0 + case 4: + return 10000.0 + case 5: + return 100000.0 + case 6: + return 1000000.0 + case 7: + return 10000000.0 + case 8: + return 100000000.0 + case 9: + return 1000000000.0 + case 10: + return 10000000000.0 + case 11: + return 100000000000.0 + case 12: + return 1000000000000.0 + case 13: + return 10000000000000.0 + case 14: + return 100000000000000.0 + case 15: + return 1000000000000000.0 + case 16: + return 10000000000000000.0 + case 17: + return 100000000000000000.0 + case 18: + return 1000000000000000000.0 + default: + return 0.0 + } + +} diff --git a/pkg/utility/cmap/concurrentmap.go b/pkg/utility/cmap/concurrentmap.go new file mode 100644 index 0000000..e518c4c --- /dev/null +++ b/pkg/utility/cmap/concurrentmap.go @@ -0,0 +1,197 @@ +package cmap + +import ( + "sync" +) + +var SHARD_COUNT = 32 + +// 一个分片的map存储器 可并发 + +const ShardCount = 31 // 分区数量 + +// ConcurrentMap A "thread" safe map of type string:Anything. +// To avoid lock bottlenecks this map is dived to several (ShardCount) map shards. +type ConcurrentMap []*ConcurrentMapShared // 分片存储map 可并发 + +// ConcurrentMapShared A "thread" safe string to anything map. +type ConcurrentMapShared struct { + items map[string]interface{} + sync.RWMutex // Read Write mutex, guards access to internal map. +} + +// New Creates a new concurrent map. +func New() ConcurrentMap { + m := make(ConcurrentMap, SHARD_COUNT) + for i := 0; i < SHARD_COUNT; i++ { + m[i] = &ConcurrentMapShared{items: make(map[string]interface{})} + } + return m +} + +// GetNoLock retrieves an element from map under given key. +func (m ConcurrentMap) GetNoLock(shard *ConcurrentMapShared, key string) (interface{}, bool) { + // Get item from shard. + val, ok := shard.items[key] + return val, ok +} + +// SetNoLock retrieves an element from map under given key. +func (m ConcurrentMap) SetNoLock(shard *ConcurrentMapShared, key string, value interface{}) { + shard.items[key] = value +} + +// NewConcurrentMap 创建 +func NewConcurrentMap() ConcurrentMap { + m := make(ConcurrentMap, ShardCount) + for i := 0; i < ShardCount; i++ { + m[i] = &ConcurrentMapShared{items: make(map[string]interface{})} + } + return m +} + +// GetShard 返回给定键下的分片 +func (m ConcurrentMap) GetShard(key string) *ConcurrentMapShared { + return m[fnv32(key)&ShardCount] +} + +// MSet 存储一组map +func (m ConcurrentMap) MSet(data map[string]interface{}) { + for key, value := range data { + shard := m.GetShard(key) + shard.Lock() + shard.items[key] = value + shard.Unlock() + } +} + +// Set the given value under the specified key. +// 在指定的键下设置给定的值。 +func (m ConcurrentMap) Set(key string, value interface{}) { + // Get map shard. + shard := m.GetShard(key) + shard.Lock() + shard.items[key] = value + shard.Unlock() +} + +// UpsertCb Callback to return new element to be inserted into the map +// It is called while lock is held, therefore it MUST NOT +// try to access other keys in same map, as it can lead to deadlock since +// Go sync.RWLock is not reentrant +// 回调函数返回新元素插入到映射中。它在锁被持有时被调用,因此它一定不要试图访问同一映射中的其他键,因为它可能导致死锁。 RWLock是不可重入的 +type UpsertCb func(exist bool, valueInMap interface{}, newValue interface{}) interface{} + +// Upsert Insert or Update - updates existing element or inserts a new one using UpsertCb +// 插入或更新——使用UpsertCb更新现有元素或插入新元素 +func (m ConcurrentMap) Upsert(key string, value interface{}, cb UpsertCb) (res interface{}) { + shard := m.GetShard(key) + shard.Lock() + v, ok := shard.items[key] + res = cb(ok, v, value) + shard.items[key] = res + shard.Unlock() + return res +} + +// SetIfAbsent Sets the given value under the specified key if no value was associated with it. +// 如果没有值与指定键关联,则在指定键下设置给定值。 +func (m ConcurrentMap) SetIfAbsent(key string, value interface{}) bool { + // Get map shard. + shard := m.GetShard(key) + shard.Lock() + _, ok := shard.items[key] + if !ok { + shard.items[key] = value + } + shard.Unlock() + return !ok +} + +// Get retrieves an element from map under given key. +func (m ConcurrentMap) Get(key string) (interface{}, bool) { + shard := m.GetShard(key) + shard.RLock() + val, ok := shard.items[key] + shard.RUnlock() + return val, ok +} + +// Count returns the number of elements within the map. +func (m ConcurrentMap) Count() int { + count := 0 + for i := 0; i < ShardCount; i++ { + shard := m[i] + shard.RLock() + count += len(shard.items) + shard.RUnlock() + } + return count +} + +// Has Looks up an item under specified key 存在性 +func (m ConcurrentMap) Has(key string) bool { + shard := m.GetShard(key) + shard.RLock() + _, ok := shard.items[key] + shard.RUnlock() + return ok +} + +// Remove removes an element from the map. 移除 +func (m ConcurrentMap) Remove(key string) { + // Try to get shard. + shard := m.GetShard(key) + shard.Lock() + delete(shard.items, key) + shard.Unlock() +} + +// RemoveCb is a callback executed in a map.RemoveCb() call, while Lock is held +// If returns true, the element will be removed from the map +// 是一个在map.RemoveCb()调用中执行的回调函数,当Lock被持有时,如果返回true,该元素将从map中移除 +type RemoveCb func(key string, v interface{}, exists bool) bool + +// RemoveCb locks the shard containing the key, retrieves its current value and calls the callback with those params +// If callback returns true and element exists, it will remove it from the map +// Returns the value returned by the callback (even if element was not present in the map) +// 如果callback返回true且element存在,则将其从map中移除。返回callback返回的值(即使element不存在于map中) +func (m ConcurrentMap) RemoveCb(key string, cb RemoveCb) bool { + shard := m.GetShard(key) + shard.Lock() + v, ok := shard.items[key] + remove := cb(key, v, ok) + if remove && ok { + delete(shard.items, key) + } + shard.Unlock() + return remove +} + +// Pop removes an element from the map and returns it +// 从映射中移除一个元素并返回它 +func (m ConcurrentMap) Pop(key string) (v interface{}, exists bool) { + // Try to get shard. + shard := m.GetShard(key) + shard.Lock() + v, exists = shard.items[key] + delete(shard.items, key) + shard.Unlock() + return v, exists +} + +// IsEmpty checks if map is empty. 是否是空的 +func (m ConcurrentMap) IsEmpty() bool { + return m.Count() == 0 +} + +// 将键值映射为数字uint32 +func fnv32(key string) uint32 { + const prime32 = uint32(16777619) + hash := uint32(2166136261) + for i := 0; i < len(key); i++ { + hash *= prime32 + hash ^= uint32(key[i]) + } + return hash +} diff --git a/pkg/utility/cmap/concurrentmap_test.go b/pkg/utility/cmap/concurrentmap_test.go new file mode 100644 index 0000000..1d88e3e --- /dev/null +++ b/pkg/utility/cmap/concurrentmap_test.go @@ -0,0 +1,60 @@ +package cmap + +import ( + "fmt" + "go-admin/pkg/utility" + "hash/crc32" + "testing" +) + +// 离散性测试 +func Test_fnv32(t *testing.T) { + st := make(map[uint32]int) + for i := 0; i < 1000000; i++ { + fnv := crc32.ChecksumIEEE([]byte(utility.GenerateRandString(8))) + k := fnv & 15 + count, ok := st[k] + if !ok { + st[k] = 1 + } + st[k] = count + 1 + } + for k, v := range st { + fmt.Println(k, "\t", float64(v)/1000000) + } +} + +// go test -bench=_QE_ -benchmem -run=^$ +// -benchtime 默认为1秒 -benchmem 获得内存分配的统计数据 +// Benchmark_QE_1-6 146641 8192 ns/op 32 B/op 3 allocs/op +// Benchmark_QE_2-6 143118 8246 ns/op 40 B/op 4 allocs/op +// +// Benchmark_QE_1-6 146289 8212 ns/op 32 B/op 3 allocs/op +// Benchmark_QE_2-6 144918 8239 ns/op 40 B/op 4 allocs/op +func Benchmark_QE_1(b *testing.B) { + for i := 0; i < b.N; i++ { + fnv32(utility.GenerateRandString(8)) + } +} + +func Benchmark_QE_2(b *testing.B) { + for i := 0; i < b.N; i++ { + crc32.ChecksumIEEE([]byte(utility.GenerateRandString(8))) + } +} + +// go test -bench=_QE2_ -benchmem -benchtime=5s -run=^$ +// -benchtime 默认为1秒 -benchmem 获得内存分配的统计数据 +// Benchmark_QE2_1-6 1000000000 0.2623 ns/op 0 B/op 0 allocs/op +// Benchmark_QE2_2-6 1000000000 0.2631 ns/op 0 B/op 0 allocs/op +func Benchmark_QE2_1(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = i & 31 + } +} + +func Benchmark_QE2_2(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = i % 31 + } +} diff --git a/pkg/utility/cmap/iterator.go b/pkg/utility/cmap/iterator.go new file mode 100644 index 0000000..bb8658e --- /dev/null +++ b/pkg/utility/cmap/iterator.go @@ -0,0 +1,160 @@ +package cmap + +import ( + "sync" + + "github.com/bytedance/sonic" +) + +// 迭代器部分 + +// Tuple Used by the Iter & IterBuffered functions to wrap two variables together over a channel +// 由Iter & IterBuffered函数使用,在一个通道上封装两个变量, +type Tuple struct { + Key string + Val interface{} +} + +// Iter returns an iterator which could be used in a for range loop. +// 返回一个可用于for范围循环的迭代器。 +// Deprecated: using IterBuffered() will get a better performence +// 使用IterBuffered()将获得更好的性能 +func (m ConcurrentMap) Iter() <-chan Tuple { + chans := snapshot(m) + ch := make(chan Tuple) // 不带缓冲 + go fanIn(chans, ch) + return ch +} + +// IterBuffered returns a buffered iterator which could be used in a for range loop. +// 返回一个可用于for范围循环的缓冲迭代器。 +func (m ConcurrentMap) IterBuffered() <-chan Tuple { + chans := snapshot(m) + total := 0 + for _, c := range chans { + total += cap(c) + } + ch := make(chan Tuple, total) // 一次性写完到缓冲中 + go fanIn(chans, ch) + return ch +} + +// Returns a array of channels that contains elements in each shard, +// which likely takes a snapshot of `m`. +// It returns once the size of each buffered channel is determined, +// before all the channels are populated using goroutines. +// 返回一个通道数组,其中包含每个shard中的元素,它可能会获取' m '的快照。 +// 一旦确定了每个缓冲通道的大小,在使用goroutines填充所有通道之前,它将返回。 +func snapshot(m ConcurrentMap) (chans []chan Tuple) { + chans = make([]chan Tuple, ShardCount) + wg := sync.WaitGroup{} + wg.Add(ShardCount) + // Foreach shard. + for index, shard := range m { + go func(index int, shard *ConcurrentMapShared) { + shard.RLock() + chans[index] = make(chan Tuple, len(shard.items)) + wg.Done() // 只要创建了通道就不用再阻塞了 + for key, val := range shard.items { + chans[index] <- Tuple{key, val} + } + shard.RUnlock() + close(chans[index]) + }(index, shard) + } + wg.Wait() + return chans +} + +// fanIn reads elements from channels `chans` into channel `out` +// 从通道' chans '读取元素到通道' out ' +func fanIn(chans []chan Tuple, out chan Tuple) { + wg := sync.WaitGroup{} + wg.Add(len(chans)) + for _, ch := range chans { + go func(ch chan Tuple) { + for t := range ch { + out <- t + } + wg.Done() + }(ch) + } + wg.Wait() + close(out) +} + +// Items returns all items as map[string]interface{} +// 返回所有条目作为map[string]interface{} +func (m ConcurrentMap) Items() map[string]interface{} { + tmp := make(map[string]interface{}) + + // Insert items to temporary map. 向临时映射中插入项目。 + for item := range m.IterBuffered() { + tmp[item.Key] = item.Val + } + + return tmp +} + +// IterCb Iterator callback,called for every key,value found in +// maps. RLock is held for all calls for a given shard +// therefore callback sess consistent view of a shard, +// but not across the shards +// 迭代器回调函数,在map中找到的每个键和值都会被调用。 +// RLock对给定分片的所有调用都保持,因此回调获得一个分片的一致视图,但不跨分片 +type IterCb func(key string, v interface{}) + +// IterCb Callback based iterator, cheapest way to read all elements in a map. +// 基于回调的迭代器,读取映射中所有元素的最便宜方法。 +func (m ConcurrentMap) IterCb(fn IterCb) { + for idx := range m { + shard := (m)[idx] + shard.RLock() + for key, value := range shard.items { + fn(key, value) + } + shard.RUnlock() + } +} + +// Keys returns all keys as []string +// 返回所有键为[]字符串 +func (m ConcurrentMap) Keys() []string { + count := m.Count() + ch := make(chan string, count) + go func() { + // Foreach shard. + wg := sync.WaitGroup{} + wg.Add(ShardCount) + for _, shard := range m { + go func(shard *ConcurrentMapShared) { + // Foreach key, value pair. + shard.RLock() + for key := range shard.items { + ch <- key + } + shard.RUnlock() + wg.Done() + }(shard) + } + wg.Wait() + close(ch) + }() + + // Generate keys + keys := make([]string, 0, count) + for k := range ch { + keys = append(keys, k) + } + return keys +} + +// MarshalJSON Reviles ConcurrentMap "private" variables to json marshal. +// 将存储的所有数据json序列化输出 +func (m ConcurrentMap) MarshalJSON() ([]byte, error) { + tmp := make(map[string]interface{}) + for item := range m.IterBuffered() { + tmp[item.Key] = item.Val + } + return sonic.Marshal(tmp) +} diff --git a/pkg/utility/decimalhelper.go b/pkg/utility/decimalhelper.go new file mode 100644 index 0000000..93f75d8 --- /dev/null +++ b/pkg/utility/decimalhelper.go @@ -0,0 +1,89 @@ +package utility + +import ( + "math/rand" + "time" + + "github.com/shopspring/decimal" +) + +func StrToDecimal(data string) decimal.Decimal { + result, _ := decimal.NewFromString(data) + + return result +} + +// DecimalCutStr 按保留的小数点位数,去掉多余的小数 非四舍五入 +func DecimalCutStr(num decimal.Decimal, size int32) string { + if num.Cmp(decimal.Zero) == 0 { + return `0` + } + + str := num.Truncate(size) + result := str.String() + return result +} + +// Random 生成一个在 [start, end] 范围内的随机数,保留一位小数 +// start 开始数字 +// end 结束数字 +// floatNum 保留小数位数 +func DecimalRandom(start, end decimal.Decimal, floatNum int) decimal.Decimal { + // 创建一个本地随机数生成器 + r := rand.New(rand.NewSource(time.Now().UnixNano())) + + // 将 start 和 end 转换为浮点数 + startFloat, _ := start.Float64() + endFloat, _ := end.Float64() + + // 生成随机数 + randomFloat := startFloat + r.Float64()*(endFloat-startFloat) + + // 保留一位小数 + randomDecimal := decimal.NewFromFloat(randomFloat).Round(int32(floatNum)) + + return randomDecimal +} + +// DiscardDecimal 舍弃 decimal 类型的最后指定位数小数 +// value: 输入的 decimal 值 +// discardDigits: 需要舍弃的小数位数 +func DiscardDecimal(value decimal.Decimal, discardDigits int32) decimal.Decimal { + // 如果 discardDigits 小于 0,直接返回原值 + if discardDigits < 0 { + return value + } + + // 获取当前值的小数位数 + currentPrecision := value.Exponent() * -1 + + // 获取整数部分 + integerPart := value.Truncate(0) + + // 如果小数位数超过一位且小于两位 + if currentPrecision > 1 && currentPrecision < 2 { + // 如果有整数部分,截断一位小数 + if !integerPart.IsZero() { + return value.Truncate(currentPrecision - 1) + } + // 如果没有整数部分,按正常流程处理 + } + + // 如果小数位数只有一位 + if currentPrecision == 1 { + // 如果有整数部分,返回整数部分 + if !integerPart.IsZero() { + return integerPart + } + // 如果没有整数部分,保持原数据不变 + return value + } + + // 如果小数位数超过两位,按正常流程处理 + if currentPrecision > discardDigits { + precision := currentPrecision - discardDigits + return value.Truncate(precision) + } + + return value +} diff --git a/pkg/utility/floathelper.go b/pkg/utility/floathelper.go new file mode 100644 index 0000000..0d850b1 --- /dev/null +++ b/pkg/utility/floathelper.go @@ -0,0 +1,301 @@ +package utility + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/shopspring/decimal" +) + +// Float64CutString 保留size位小数 不足则填充0 +func Float64CutString(num float64, size int32) string { + de := decimal.NewFromFloat(num).Truncate(size) + return de.StringFixed(size) +} + +func FloatAddCutFixStr(num1, num2 float64, size int32) string { + result := decimal.NewFromFloat(num1).Add(decimal.NewFromFloat(num2)).Truncate(size) + return result.StringFixed(size) +} + +// FloatCut 按保留的小数点位数,去掉多余的小数 +func FloatCut(num float64, size int32) float64 { + de := decimal.NewFromFloat(num) + str := de.Truncate(size) + result, _ := str.Float64() + return result +} + +// FloatCutStr 按保留的小数点位数,去掉多余的小数 非四舍五入 +func FloatCutStr(num float64, size int32) string { + if num == 0 { + return `0` + } + de := decimal.NewFromFloat(num) + str := de.Truncate(size) + result := str.String() + return result +} + +// StringFloat64Cut 保留8为小数后边为0不截取 +func StringFloat64Cut(num string, size int32) string { + // 清理输入字符串,去除空字符和其他非数字字符 + if strings.Contains(num, "x00") { + fmt.Sprintf("打印信息", num) + } + + cleanedNum := strings.TrimRight(num, "\x00") // 去除空字符 + cleanedNum = strings.TrimSpace(cleanedNum) // 去除空格 + cleanedNum = strings.ReplaceAll(strings.ReplaceAll(cleanedNum, ",", ""), "\x00", "") // 去除逗号 + de, err := decimal.NewFromString(cleanedNum) + if err != nil { + return "" + } + return de.Truncate(size).String() +} + +// StrToFloatCut 按保留的小数点位数,去掉多余的小数 非四舍五入 +func StrToFloatCut(num string, size int32) float64 { + if num == "" { + return 0 + } + de, _ := decimal.NewFromString(num) + str := de.Truncate(size) + result, _ := str.Float64() + return result +} + +// FloatThousand 对float进行千分位处理返回字符串,比如2568965463.256545 => 2,568,965,463.256545 +func FloatThousand(num float64) string { + if num <= 1000 { + return decimal.NewFromFloat(num).String() + } + n := decimal.NewFromFloat(num).String() + dec := "" + if strings.Index(n, ".") != -1 { + dec = n[strings.Index(n, ".")+1:] + n = n[0:strings.Index(n, ".")] + } + for i := 0; i <= len(n); i = i + 4 { + a := n[0 : len(n)-i] + b := n[len(n)-i:] + n = a + "," + b + } + if n[0:1] == "," { + n = n[1:] + } + if n[len(n)-1:] == "," { + n = n[0 : len(n)-1] + } + if dec != "" { + n = n + "." + dec + } + return n +} + +// Float8ToString 按保留的小数点8位数,去掉多余的小数, return string +func Float8ToString(num float64) string { + return FloatToString(num, 8) +} + +// FloatAdd float + float +func FloatAdd(num1, num2 float64) float64 { + result := decimal.NewFromFloat(num1).Add(decimal.NewFromFloat(num2)) + f, _ := result.Float64() + return f +} + +func FloatAddCutStr(num1, num2 float64, size int32) string { + result := decimal.NewFromFloat(num1).Add(decimal.NewFromFloat(num2)) + return result.Truncate(size).String() +} + +func FloatAddCut(num1, num2 float64, size int32) float64 { + result := decimal.NewFromFloat(num1).Add(decimal.NewFromFloat(num2)) + f, _ := result.Truncate(size).Float64() + return f +} + +// FloatSub float - float +func FloatSub(num1, num2 float64) float64 { + if num2 == 0 { + return num1 + } + result := decimal.NewFromFloat(num1).Sub(decimal.NewFromFloat(num2)) + f, _ := result.Float64() + return f +} + +// FloatSubCut float - float +func FloatSubCut(num1, num2 float64, size int32) float64 { + if num2 == 0 { + return num1 + } + result := decimal.NewFromFloat(num1).Sub(decimal.NewFromFloat(num2)) + f, _ := result.Truncate(size).Float64() + return f +} + +// FloatSubCutStr float - float +func FloatSubCutStr(num1, num2 float64, size int32) string { + if num2 == 0 { + return decimal.NewFromFloat(num1).Truncate(size).String() + } + result := decimal.NewFromFloat(num1).Sub(decimal.NewFromFloat(num2)) + f := result.Truncate(size).String() + return f +} + +// FloatDiv float / float 两数相除 +func FloatDiv(num1, num2 float64) float64 { + result := decimal.NewFromFloat(num1).Div(decimal.NewFromFloat(num2)) + f, _ := result.Float64() + return f +} + +func FloatDivCutStr(num1, num2 float64, size int32) string { + result := decimal.NewFromFloat(num1).Div(decimal.NewFromFloat(num2)) + result = result.Truncate(size) + s := result.String() + return s +} + +func FloatDivCutFixStr(num1, num2 float64, size int32) string { + result := decimal.NewFromFloat(num1).Div(decimal.NewFromFloat(num2)) + return result.Truncate(size).StringFixed(size) +} + +func FloatDivCut(num1, num2 float64, size int32) float64 { + result := decimal.NewFromFloat(num1).Div(decimal.NewFromFloat(num2)) + result = result.Truncate(size) + f, _ := result.Float64() + return f +} + +// FloatMul float * float +func FloatMul(num1, num2 float64) float64 { + result := decimal.NewFromFloat(num1).Mul(decimal.NewFromFloat(num2)) + f, _ := result.Float64() + return f +} + +// FloatMulCut 两数相乘并返回小数点后size位的float64 +func FloatMulCut(num1, num2 float64, size int32) float64 { + result := decimal.NewFromFloat(num1).Mul(decimal.NewFromFloat(num2)) + result = result.Truncate(size) + f, _ := result.Float64() + return f +} + +// FloatMulCutStr float * float 两数相乘并返回指定小数位数的float64 返回字符串 +func FloatMulCutStr(num1, num2 float64, size int32) string { + result := decimal.NewFromFloat(num1).Mul(decimal.NewFromFloat(num2)) + result = result.Truncate(size) + return result.String() +} + +// FloatMulCutFixStr float * float 两数相乘并返回指定小数位数的float64 返回字符串 +func FloatMulCutFixStr(num1, num2 float64, size int32) string { + result := decimal.NewFromFloat(num1).Mul(decimal.NewFromFloat(num2)) + result = result.Truncate(size) + return result.StringFixed(size) +} + +// GetTotalAmt 计算需要冻结的币 数量*??/价格 +func GetTotalAmt(num int, price, contractVal float64, size int32) float64 { + de := decimal.NewFromInt(int64(num)). + Mul(decimal.NewFromFloat(contractVal)). + Div(decimal.NewFromFloat(price)). + Truncate(size) + result2, _ := de.Float64() + return result2 +} + +func GetNonce() string { + s := strconv.FormatInt(time.Now().UnixNano(), 10)[0:11] + return s +} + +// IsEqual 比对2个float64 是否相等 +func IsEqual(num1, num2 float64, size int32) bool { + n1 := decimal.NewFromFloat(num1).Truncate(size) + n2 := decimal.NewFromFloat(num2).Truncate(size) + return n1.Equal(n2) +} + +// GetDealAmt 根据下单张数,下单总的冻结金额,计算本次成交金额 +func GetDealAmt(num, totalNum int, totalAmt float64, size int32) float64 { + if num == totalNum { + return totalAmt + } + de := decimal.NewFromFloat(totalAmt). + Div(decimal.NewFromInt(int64(num))). + Mul(decimal.NewFromInt(int64(num))). + Truncate(size) + + result2, _ := de.Float64() + return result2 +} + +func ToFloat64(v interface{}) float64 { + if v == nil { + return 0.0 + } + + switch v.(type) { + case float64: + return v.(float64) + case string: + vStr := v.(string) + vF, _ := strconv.ParseFloat(vStr, 64) + return vF + default: + panic("to float64 error.") + } +} + +func ToInt(v interface{}) int { + if v == nil { + return 0 + } + + switch v.(type) { + case string: + vStr := v.(string) + vInt, _ := strconv.Atoi(vStr) + return vInt + case int: + return v.(int) + case float64: + vF := v.(float64) + return int(vF) + default: + panic("to int error.") + } +} + +func ToInt64(v interface{}) int64 { + if v == nil { + return 0 + } + + switch v.(type) { + case float64: + return int64(v.(float64)) + default: + vv := fmt.Sprint(v) + + if vv == "" { + return 0 + } + + vvv, err := strconv.ParseInt(vv, 0, 64) + if err != nil { + return 0 + } + + return vvv + } +} diff --git a/pkg/utility/floathelper_test.go b/pkg/utility/floathelper_test.go new file mode 100644 index 0000000..7fcc2bf --- /dev/null +++ b/pkg/utility/floathelper_test.go @@ -0,0 +1,14 @@ +package utility + +import ( + "fmt" + "testing" +) + +func Test_FloatCutStr(t *testing.T) { + fmt.Println(FloatCutStr(10, -1)) +} +func TestFloat64CutString(t *testing.T) { + xx := Float64CutString(1002277.51198900, 3) + fmt.Println(xx) +} diff --git a/pkg/utility/idhelper.go b/pkg/utility/idhelper.go new file mode 100644 index 0000000..3c986ee --- /dev/null +++ b/pkg/utility/idhelper.go @@ -0,0 +1,76 @@ +package utility + +import ( + "github.com/rs/xid" + "go.uber.org/zap" + + "fmt" + "math" + "math/rand" + "time" + + log "github.com/go-admin-team/go-admin-core/logger" +) + +// GetXid Package xid is a globally unique id generator library +// 包xid是一个全局唯一的id生成器库 +func GetXid() string { + return xid.New().String() +} + +// GetGuid 获取guid 基于时间戳和MAC地址的uuid 可以视为随机字符串 +//func GetGuid() string { +// return uuid.NewV1().String() +//} + +//var orderLock sync.Mutex + +// GetOrderNo 获取订单id 看样子已经废弃 改用采用雪花算法获取了 +//func GetOrderNo() string { +// orderLock.Lock() +// defer orderLock.Unlock() +// date := time.Now().Format("200601021504") +// code := fmt.Sprintf("%s%07d", date, time.Now().Nanosecond()/100) +// time.Sleep(30 * time.Millisecond) +// return code +//} + +// GetRandIntStr 生成len位的随机数字 +func GetRandIntStr(len int, prefix string) string { + rand.Seed(time.Now().UnixNano()) + + num := rand.Int31n(int32(math.Pow(10, float64(len)))) + x := fmt.Sprintf("%s%0*d", prefix, len, num) + return x +} + +// GenerateRandString 生成指定位数的字符串 +// 虽然繁琐 但理解之后就觉得很精妙 +func GenerateRandString(length int) string { + var chars = []byte(`ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`) // 长度:(1,256) + rand.Seed(time.Now().UnixNano()) + clen := len(chars) + maxRb := 255 - (256 % clen) // [-1,255] 255 - (256%36) = 251 避免模偏倚 为了每个字符被取到的几率相等 + b := make([]byte, length) + r := make([]byte, length+(length/4)) // storage for random bytes. 存储随机字节 + + for i := 0; ; { + // 将随机的byte值填充到byte数组中 以供使用 + if _, err := rand.Read(r); err != nil { + log.Error(`GenerateRandString`, zap.Error(err)) + return `` + } + for _, rb := range r { + c := int(rb) + if c > maxRb { + // Skip this number to avoid modulo bias.跳过这个数字以避免模偏倚 + continue + } + b[i] = chars[c%clen] + i++ + if i == length { // 直到取到合适的长度 + return string(b) + } + } + } +} diff --git a/pkg/utility/idhelper_test.go b/pkg/utility/idhelper_test.go new file mode 100644 index 0000000..5788c12 --- /dev/null +++ b/pkg/utility/idhelper_test.go @@ -0,0 +1,26 @@ +package utility + +import ( + "fmt" + "testing" + "time" +) + +func Test_GenerateRandString(t *testing.T) { + for i := 0; i < 10; i++ { + t.Log(GenerateRandString(6)) + time.Sleep(time.Microsecond) + } +} + +func TestName(t *testing.T) { + for i := 0; i < 10; i++ { + t.Log(GetRandIntStr(6, "")) + time.Sleep(time.Microsecond) + } + // t.Log(math.Pow(10, 5)) +} +func TestGetXid(t *testing.T) { + data := GetXid() + fmt.Println(data) +} diff --git a/pkg/utility/ipnet.go b/pkg/utility/ipnet.go new file mode 100644 index 0000000..e2d0aff --- /dev/null +++ b/pkg/utility/ipnet.go @@ -0,0 +1,234 @@ +package utility + +import ( + "errors" + "net" + "net/url" + "sort" + "strconv" + "strings" + + "github.com/gin-gonic/gin" +) + +func GetIp(ctx *gin.Context) string { + ip := ctx.ClientIP() + if len(ip) > 0 { + return ip + } + return "" +} + +// ExternalIP get external ip. 获取外部的ip +func ExternalIP() (res []string) { + inters, err := net.Interfaces() + if err != nil { + return + } + for _, inter := range inters { + if !strings.HasPrefix(inter.Name, "lo") { + addrs, err := inter.Addrs() + if err != nil { + continue + } + for _, addr := range addrs { + if ipNet, ok := addr.(*net.IPNet); ok { + if ipNet.IP.IsLoopback() || ipNet.IP.IsLinkLocalMulticast() || ipNet.IP.IsLinkLocalUnicast() { + continue + } + if ip4 := ipNet.IP.To4(); ip4 != nil { + switch true { + case ip4[0] == 10: + continue + case ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31: + continue + case ip4[0] == 192 && ip4[1] == 168: + continue + default: + res = append(res, ipNet.IP.String()) + } + } + } + } + } + } + return +} + +// InternalIP get internal ip. 获取内部的ip +func InternalIP() string { + inters, err := net.Interfaces() + if err != nil { + return "" + } + for _, inter := range inters { + if inter.Flags&net.FlagUp == net.FlagUp { + continue + } + if !strings.HasPrefix(inter.Name, "lo") { + addrs, err := inter.Addrs() + if err != nil { + continue + } + for _, addr := range addrs { + if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil { + return ipnet.IP.String() + } + } + } + } + } + return "" +} + +// GetFreePort gets a free port. 获得一个自由端口 +func GetFreePort() (port int, err error) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return 0, err + } + defer listener.Close() + + addr := listener.Addr().String() + _, portString, err := net.SplitHostPort(addr) + if err != nil { + return 0, err + } + + return strconv.Atoi(portString) +} + +// ParseRpcAddress parses rpc address such as tcp@127.0.0.1:8972 quic@192.168.1.1:9981 +func ParseRpcAddress(addr string) (network string, ip string, port int, err error) { + ati := strings.Index(addr, "@") + if ati <= 0 { + return "", "", 0, errors.New("invalid rpc address: " + addr) + } + + network = addr[:ati] + addr = addr[ati+1:] + + var portStr string + ip, portStr, err = net.SplitHostPort(addr) + if err != nil { + return "", "", 0, err + } + + port, err = strconv.Atoi(portStr) + return network, ip, port, err +} + +func ConvertMeta2Map(meta string) map[string]string { + var rt = make(map[string]string) + + if meta == "" { + return rt + } + + v, err := url.ParseQuery(meta) + if err != nil { + return rt + } + + for key := range v { + rt[key] = v.Get(key) + } + return rt +} + +func ConvertMap2String(meta map[string]string) string { + var buf strings.Builder + keys := make([]string, 0, len(meta)) + for k := range meta { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + vs := meta[k] + keyEscaped := url.QueryEscape(k) + if buf.Len() > 0 { + buf.WriteByte('&') + } + buf.WriteString(keyEscaped) + buf.WriteByte('=') + buf.WriteString(url.QueryEscape(vs)) + } + return buf.String() +} + +// ExternalIPV4 gets external IPv4 address of this server. +func ExternalIPV4() (string, error) { + ifaces, err := net.Interfaces() + if err != nil { + return "", err + } + for _, iface := range ifaces { + if iface.Flags&net.FlagUp == 0 { + continue // interface down + } + if iface.Flags&net.FlagLoopback != 0 { + continue // loopback interface + } + addrs, err := iface.Addrs() + if err != nil { + return "", err + } + for _, addr := range addrs { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + if ip == nil || ip.IsLoopback() { + continue + } + ip = ip.To4() + if ip == nil { + continue // not an ipv4 address + } + return ip.String(), nil + } + } + return "", errors.New("are you connected to the network?") +} + +// ExternalIPV6 gets external IPv6 address of this server. +func ExternalIPV6() (string, error) { + ifaces, err := net.Interfaces() + if err != nil { + return "", err + } + for _, iface := range ifaces { + if iface.Flags&net.FlagUp == 0 { + continue // interface down + } + if iface.Flags&net.FlagLoopback != 0 { + continue // loopback interface + } + addrs, err := iface.Addrs() + if err != nil { + return "", err + } + for _, addr := range addrs { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + if ip == nil || ip.IsLoopback() { + continue + } + ip = ip.To16() + if ip == nil { + continue // not an ipv4 address + } + return ip.String(), nil + } + } + return "", errors.New("are you connected to the network?") +} diff --git a/pkg/utility/lockkey/lockkey.go b/pkg/utility/lockkey/lockkey.go new file mode 100644 index 0000000..ebe5666 --- /dev/null +++ b/pkg/utility/lockkey/lockkey.go @@ -0,0 +1,40 @@ +package lockkey + +import ( + "fmt" + "sync" +) + +type LockByKey struct { + m map[string]*sync.RWMutex + l sync.Mutex +} + +func NewLockByKey() *LockByKey { + return &LockByKey{m: make(map[string]*sync.RWMutex)} +} + +func (lk *LockByKey) Lock(key string) { + lk.l.Lock() + defer lk.l.Unlock() + + mu, ok := lk.m[key] + if !ok { + mu = &sync.RWMutex{} + lk.m[key] = mu + } + + mu.Lock() +} + +func (lk *LockByKey) Unlock(key string) { + lk.l.Lock() + defer lk.l.Unlock() + + mu, ok := lk.m[key] + if !ok { + panic(fmt.Sprintf("unlock non-existent key: %v", key)) + } + + mu.Unlock() +} diff --git a/pkg/utility/lockkey/lockkey_test.go b/pkg/utility/lockkey/lockkey_test.go new file mode 100644 index 0000000..db1e7c2 --- /dev/null +++ b/pkg/utility/lockkey/lockkey_test.go @@ -0,0 +1,34 @@ +package lockkey + +import ( + "fmt" + "testing" + "time" +) + +func TestLock(t *testing.T) { + lk := NewLockByKey() + + // Lock and unlock the same key multiple times. + for i := 0; i < 5; i++ { + go func() { + lk.Lock("key") + fmt.Println("locked") + lk.Unlock("key") + fmt.Println("unlocked") + }() + } + + // Lock and unlock different keys. + lk.Lock("key1") + fmt.Println("locked key1") + lk.Unlock("key1") + fmt.Println("unlocked key1") + + lk.Lock("key2") + fmt.Println("locked key2") + lk.Unlock("key2") + fmt.Println("unlocked key2") + + time.Sleep(3 * time.Second) +} diff --git a/pkg/utility/maps.go b/pkg/utility/maps.go new file mode 100644 index 0000000..cc96855 --- /dev/null +++ b/pkg/utility/maps.go @@ -0,0 +1,54 @@ +package utility + +import ( + "github.com/mitchellh/mapstructure" + "reflect" + "time" +) + +// 映射; +type maps struct { +} + +// 构建; +func Maps() *maps { + return &maps{} +} + +// 转为结构体; +func (this *maps) Struct(src, dst interface{}) error { + config := &mapstructure.DecoderConfig{ + WeaklyTypedInput: true, + DecodeHook: mapstructure.ComposeDecodeHookFunc(ToTimeHookFunc()), + Result: &dst, + } + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return err + } + if err = decoder.Decode(src); err != nil { + return err + } + return nil +} +func ToTimeHookFunc() mapstructure.DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}) (interface{}, error) { + if t != reflect.TypeOf(time.Time{}) { + return data, nil + } + + switch f.Kind() { + case reflect.String: + return time.Parse(time.RFC3339, data.(string)) + case reflect.Float64: + return time.Unix(0, int64(data.(float64))*int64(time.Millisecond)), nil + case reflect.Int64: + return time.Unix(0, data.(int64)*int64(time.Millisecond)), nil + default: + return data, nil + } + } +} diff --git a/pkg/utility/ratecheck/ratecheck.go b/pkg/utility/ratecheck/ratecheck.go new file mode 100644 index 0000000..22315f9 --- /dev/null +++ b/pkg/utility/ratecheck/ratecheck.go @@ -0,0 +1,47 @@ +package ratecheck + +import ( + "github.com/juju/ratelimit" + gocache "github.com/patrickmn/go-cache" + "go-admin/pkg/utility" + "time" +) + +var ( + // Map of limiters with TTL + tokenBuckets = gocache.New(120*time.Minute, 1*time.Minute) + userDur = 1 * time.Second + userSize int64 = 1 + + orderDur = 20 * time.Second +) + +// CheckRateLimit 根据key检测,在规定时间内,是否超过访问次数,限流 +func CheckRateLimit(key string, duration time.Duration, size int64) bool { + if _, found := tokenBuckets.Get(key); !found { + tokenBuckets.Set( + key, + ratelimit.NewBucket(duration, size), + duration) + } + expiringMap, found := tokenBuckets.Get(key) + if !found { + return false + } + return expiringMap.(*ratelimit.Bucket).TakeAvailable(1) > 0 +} + +// CheckUserRateLimit 根据key检测,在规定时间内,单个用户是否超过访问次数,限流,默认5秒1次请求 +func CheckUserRateLimit(userid int, methodName string) bool { + key := methodName + "-" + utility.IntTostring(userid) + return CheckRateLimit(key, userDur, userSize) +} + +// 检测订单成交是否重复推送 +func CheckOrderIdIsExist(tradeId string) bool { + _, found := tokenBuckets.Get(tradeId) + if !found { + tokenBuckets.Set(tradeId, true, orderDur) + } + return found +} diff --git a/pkg/utility/regexhelper.go b/pkg/utility/regexhelper.go new file mode 100644 index 0000000..bb4b8b2 --- /dev/null +++ b/pkg/utility/regexhelper.go @@ -0,0 +1,112 @@ +package utility + +import ( + "regexp" +) + +var ( + emailRegexp, _ = regexp.Compile("(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$)") +) + +// ValidateEmail 验证邮箱 +func ValidateEmail(email string) bool { + // userName := "[a-zA-Z0-9][a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]{0,62}[a-zA-Z0-9]" + // domainName := "[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?" + // topLevelDomainName := "(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*" + // pattern := "^" + userName + "@" + domainName + topLevelDomainName + "$" + // reg := regexp.MustCompile(pattern) + // match := reg.MatchString(email) + match := emailRegexp.MatchString(email) + return match +} + +// ValidatePhoneNumber 手机号码验证,+91-9819882936 +// +// 1. Mobile Number +// - Starts with 6,7,8,8 +// - 10 digit +// - Prexix can be +91, 0 +// +// 2. LandLine Number +// - {area-code}-{local number} +// - 10 digit number +// - area codes range from 2-digits to 4-digits +// - ex. 02321-238200 +func ValidatePhoneNumber(num string) bool { + prefixMobileNum := `(?:(?:\+|0{0,2})91([\s-])?|[0]?)?` + mobileNum := `[6-9]\d{9}` + landLineNum := `((0)?(([1-9]\d{1}-\d{8})|([1-9]\d{2}-\d{7})|([1-9]\d{3}-\d{6})))` + pattern := "^(" + "(" + prefixMobileNum + mobileNum + ")" + "|" + landLineNum + ")$" + reg := regexp.MustCompile(pattern) + match := reg.MatchString(num) + + return match +} + +// ValidatePhoneForeign 外国手机 +func ValidatePhoneForeign(phone string) bool { + reg, err := regexp.MatchString("^[0-9]{7}$", phone) + if err != nil { + return false + } + return reg +} + +// IsMobileChina 中国手机验证 +func IsMobileChina(phone string) bool { + // reg, err := regexp.MatchString("^[0-9]{11}$", phone) + reg, err := regexp.MatchString("^(13[0-9]|14[0-9]|15[0-9]|17[0-9]|18[0-9]|19[0-9])\\d{8}$", phone) + if err != nil { + return false + } + return reg +} + +// ReturnEmail 替换邮箱中间几位为*号 +func ReturnEmail(email string) string { + if len(email) == 0 { + return "" + } + re, _ := regexp.Compile("(\\w?)(\\w+)(\\w)(@\\w+\\.[a-z]+(\\.[a-z]+)?)") + return re.ReplaceAllString(email, "$1****$3$4") +} + +// ReturnPhoneNO 替换手机号中间四位为* +func ReturnPhoneNO(phone string) string { + if len(phone) == 0 { + return "" + } + re, _ := regexp.Compile("(\\d{3})(\\d{4})(\\d{4})") + return re.ReplaceAllString(phone, "$1****$3") +} + +// CheckPasswordOk +// 密码长度minLength-maxLength位,可使用字母、数字、符号组成,区分大小写,至少包含两种 +// minLength: 指定密码的最小长度 +// maxLength:指定密码的最大长度 +// pwd:明文密码 +func CheckPasswordOk(minLength, maxLength int, pwd string) bool { + if len(pwd) < minLength { + return false // fmt.Errorf("BAD PASSWORD: The password is shorter than %d characters", minLength) + } + if len(pwd) > maxLength { + return false // fmt.Errorf("BAD PASSWORD: The password is logner than %d characters", maxLength) + } + // patternList := []string{`[0-9]+`, `[a-z]+`, `[A-Z]+`, `[~!@#$%^&*?_-]+`} + isNum, _ := regexp.MatchString(`[0-9]+`, pwd) + isLower, _ := regexp.MatchString(`[a-z]+`, pwd) + isUpper, _ := regexp.MatchString(`[A-Z]+`, pwd) + //isSpe, _ := regexp.MatchString(`[~!@#$%^&*?_-]+`, pwd) + + return isNum && isLower && isUpper +} + +// IsAccountTwo 用户账号验证 +func IsAccountTwo(phone string) bool { + // reg, err := regexp.MatchString("^[0-9]{11}$", phone) + reg, err := regexp.MatchString("^[A-Za-z\\d]{6,12}$", phone) + if err != nil { + return false + } + return reg +} diff --git a/pkg/utility/safego.go b/pkg/utility/safego.go new file mode 100644 index 0000000..4dd739e --- /dev/null +++ b/pkg/utility/safego.go @@ -0,0 +1,44 @@ +package utility + +import ( + "fmt" + "runtime" + "runtime/debug" + "strings" + + "github.com/go-admin-team/go-admin-core/logger" +) + +// SafeGo 安全地启动一个 goroutine,捕获 panic +func SafeGo(fn func()) { + go func() { + defer func() { + if r := recover(); r != nil { + // 记录 Goroutine ID、panic 信息和堆栈 + logger.Error(fmt.Sprintf("Recovered from panic in Goroutine %s: %v\nStack Trace:\n%s", getGoroutineID(), r, string(debug.Stack()))) + } + }() + fn() + }() +} + +// 获取 Goroutine ID +func getGoroutineID() string { + buf := make([]byte, 64) + n := runtime.Stack(buf, false) + stack := string(buf[:n]) + // 提取 Goroutine ID + id := strings.Split(stack, " ")[1] + return id +} + +func SafeGoParam[T any](fn func(T), param T) { + go func() { + defer func() { + if r := recover(); r != nil { + logger.Error(fmt.Sprintf(" SafeGoParam Recovered from panic in Goroutine %s: %v\nStack Trace:\n%s", getGoroutineID(), r, string(debug.Stack()))) + } + }() + fn(param) // 执行传入的函数 + }() +} diff --git a/pkg/utility/search.go b/pkg/utility/search.go new file mode 100644 index 0000000..5052072 --- /dev/null +++ b/pkg/utility/search.go @@ -0,0 +1,29 @@ +package utility + +import "sort" + +// 二分查找(会将切片 a 排为升序) +// +// 找到返回 true,未找到返回 false +func BinarySearch(a []int, x int) bool { + if !sort.IntsAreSorted(a) { + sort.Ints(a) + } + + l, r := 0, len(a)-1 + + for l <= r { + m := (l + r) / 2 + if a[m] == x { + return true + } + // x 在左边 + if x < a[m] { + r = m - 1 + } else { + l = m + 1 + } + } + + return false +} diff --git a/pkg/utility/search_test.go b/pkg/utility/search_test.go new file mode 100644 index 0000000..4cb44d9 --- /dev/null +++ b/pkg/utility/search_test.go @@ -0,0 +1,14 @@ +package utility + +import ( + "fmt" + "testing" +) + +func TestBinarySearch(t *testing.T) { + // a := []int{5, 4, 3, 2} + a := []int{7, 5, 3, 9, 2, 6} + + dst := BinarySearch(a, 106) + fmt.Println(dst) +} diff --git a/pkg/utility/seqs/rand.go b/pkg/utility/seqs/rand.go new file mode 100644 index 0000000..bd9fc66 --- /dev/null +++ b/pkg/utility/seqs/rand.go @@ -0,0 +1,68 @@ +package seqs + +import ( + cr "crypto/rand" + "math/big" + mr "math/rand" + "time" +) + +// 随机生成器; +type rand struct { +} + +// 全局随机数; +var rnd *mr.Rand + +func init() { + rnd = mr.New(mr.NewSource(time.Now().UnixNano())) +} + +// 构建; +func Rand() *rand { + return &rand{} +} + +// 随机数字; +func (rd *rand) DigitId(_len int) string { + return rd.newId([]byte("0123456789"), _len) +} + +// 随机字母; +func (rd *rand) ChrtId(_len int) string { + return rd.newId([]byte("abcdefghijklmnopqrstuvwxyz"), _len) +} + +// 随机混合(数字+字母); +func (rd *rand) BothId(_len int) string { + return rd.newId([]byte("0123456789abcdefghijklmnopqrstuvwxyz"), _len) +} + +// 随机范围(长整型); +func (rd *rand) RandI64(min, max int64) int64 { + bi, _ := cr.Int(cr.Reader, big.NewInt(max-min)) + return min + bi.Int64() +} + +// 随机范围(0 ~ max); +func (rd *rand) RandInt(max int) int { + return rnd.Intn(max) +} + +// 随机中文; +func (rd *rand) ChrtCn(_len int) string { + a := make([]rune, _len) + for i := range a { + a[i] = rune(rd.RandI64(19968, 40869)) + } + return string(a) +} + +// newId; +func (rd *rand) newId(tmpl []byte, _len int) string { + var r []byte + for i := 0; i < _len; i++ { + r = append(r, tmpl[rnd.Intn(len(tmpl))]) + } + return string(r) +} diff --git a/pkg/utility/slices.go b/pkg/utility/slices.go new file mode 100644 index 0000000..8ce5aa7 --- /dev/null +++ b/pkg/utility/slices.go @@ -0,0 +1,141 @@ +package utility + +import "strings" + +// ContainsStr []string 包含元素? +func ContainsStr(arr []string, v string) bool { + for _, a := range arr { + if a == v { + return true + } + } + return false +} + +func RemoveByValue(slice []string, value string) []string { + for i, v := range slice { + if v == value { + // 找到值,删除对应索引 + return append(slice[:i], slice[i+1:]...) + } + } + return slice // 未找到返回原切片 +} + +// ContainsInt []int 包含元素? +func ContainsInt(arr []int, v int) bool { + for _, a := range arr { + if a == v { + return true + } + } + return false +} + +func HasSuffix(data string, suffixs []string) bool { + for _, suffix := range suffixs { + if strings.HasSuffix(data, suffix) { + return true + } + } + return false +} + +// SplitSlice 将 []string 切片根据最大数量分割成二维数组 +func SplitSlice(slice []string, maxSize int) [][]string { + var result [][]string + + // 遍历切片,每次取 maxSize 个元素 + for i := 0; i < len(slice); i += maxSize { + end := i + maxSize + // 如果 end 超出切片长度,则取到切片末尾 + if end > len(slice) { + end = len(slice) + } + // 将当前段添加到结果中 + result = append(result, slice[i:end]) + } + + return result +} + +//// 切片; +//type slices struct { +//} +// +//// 构建; +//func Slices() *slices { +// return &slices{} +//} +// +//// 包含元素? +//func (this *slices) Contains(s interface{}, v interface{}) bool { +// // 相关定义; +// ss := reflect.Indirect(reflect.ValueOf(s)) +// slen := ss.Len() +// // 遍历; +// for i := 0; i < slen; i++ { +// // 定位元素; +// sv := reflect.Indirect(ss.Index(i)) +// if fmt.Sprint(sv.Interface()) == fmt.Sprint(v) { +// return true +// } +// } +// return false +//} +// +//// 转为切片; +//func (this *slices) Slice(s interface{}) []interface{} { +// // 相关定义; +// ss := reflect.Indirect(reflect.ValueOf(s)) +// slen := ss.Len() +// // 遍历; +// out := make([]interface{}, slen) +// for i := 0; i < slen; i++ { +// // 追加; +// out[i] = ss.Index(i).Interface() +// } +// return out +//} +// +//// 校验为nil? +//func (this *slices) IsNil(v interface{}) bool { +// if v == nil { +// return true +// } +// vi := reflect.ValueOf(v) +// if vi.Kind() == reflect.Ptr { +// return vi.Elem().IsNil() +// } +// return vi.IsNil() +//} +// +//// StringSliceReflectEqual 判断 string和slice 是否相等 +//// 因为使用了反射,所以效率较低,可以看benchmark结果 +//func (this *slices) StringSliceReflectEqual(a, b []string) bool { +// return reflect.DeepEqual(a, b) +//} +// +//// StringSliceEqual 判断 string和slice 是否相等 +//// 使用了传统的遍历方式 +//func (this *slices) StringSliceEqual(a, b []string) bool { +// if len(a) != len(b) { +// return false +// } +// +// // reflect.DeepEqual的结果保持一致 +// if (a == nil) != (b == nil) { +// return false +// } +// +// // bounds check 边界检查 +// // 避免越界 +// b = b[:len(a)] +// for i, v := range a { +// if v != b[i] { +// return false +// } +// } +// +// return true +//} diff --git a/pkg/utility/snowflakehelper/snowflakehelper.go b/pkg/utility/snowflakehelper/snowflakehelper.go new file mode 100644 index 0000000..af21a34 --- /dev/null +++ b/pkg/utility/snowflakehelper/snowflakehelper.go @@ -0,0 +1,30 @@ +package snowflakehelper + +// 雪花算法用于生成订单号 + +import ( + "fmt" + "go-admin/config" + + "github.com/bwmarrin/snowflake" +) + +var ( + snowNode *snowflake.Node +) + +func init() { + snowflake.Epoch = 1649212361224 // time.Now().UnixMilli() + // nodeId := utility.StringAsInt64() + node, err := snowflake.NewNode(config.ExtConfig.ServiceId) + if err != nil { + fmt.Println("snowflake.NewNode err:", err) + return + } + snowNode = node +} + +// GetOrderId 生成int64订单id +func GetOrderId() int64 { + return snowNode.Generate().Int64() +} diff --git a/pkg/utility/stringhelper.go b/pkg/utility/stringhelper.go new file mode 100644 index 0000000..c7e2b32 --- /dev/null +++ b/pkg/utility/stringhelper.go @@ -0,0 +1,485 @@ +package utility + +import ( + "fmt" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + "unicode/utf8" + "unsafe" + + "go.uber.org/zap" + + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/shopspring/decimal" +) + +// 获取不超过指定长度的字段 +func StringCut(s string, maxLength int) string { + if len(s) > maxLength { + return s[:maxLength] // 获取前 maxLength 个字符 + } + return s // 如果长度不足,返回原字符串 +} + +// FloatToStringZero 去掉多余的小数0,比如0.0245000返回0.0245,return string +func FloatToStringZero(num string) string { + de, _ := decimal.NewFromString(num) + str := de.String() + return str +} + +// IntTostring int to string +func IntTostring(num int) string { + if num == 0 { + return "0" + } + return strconv.Itoa(num) +} + +// StringLen 获取字符串真实长度 +func StringLen(s string) int { + return len([]rune(s)) +} + +// StringIsNil 检测字符串长度是否为0 +func StringIsNil(str string) bool { + if len(str) == 0 { + return true + } + return len(strings.Trim(str, " ")) == 0 +} + +// StringTrim 去前后空格 +func StringTrim(str string) string { + return strings.Trim(str, " ") +} + +// StringAsFloat tries to convert a string to float, and if it can't, just returns zero +// 尝试将字符串转换为浮点数,如果不能,则返回0 +func StringAsFloat(s string) float64 { + if len(s) == 0 { + return 0.0 + } + if f, err := strconv.ParseFloat(s, 64); err == nil { + return f + } + return 0.0 +} + +func StringAsDecimal(s string) decimal.Decimal { + if len(s) == 0 { + return decimal.Zero + } + f, err := decimal.NewFromString(s) + if err != nil { + log.Error("字符串转化decimal失败: ", zap.Error(err)) + return decimal.Zero + } + return f +} + +func StringAsFloatCut(s string, size int32) float64 { + if len(s) == 0 { + return 0 + } + f, err := decimal.NewFromString(s) + if err != nil { + log.Error("字符串转化decimal失败: ", zap.Error(err)) + return 0 + } + d, _ := f.Truncate(size).Float64() + return d +} + +// FloatToString 按保留的小数点位数,去掉多余的小数,return string +func FloatToString(num float64, size int32) string { + if num == 0 { + return getZero(size) + } + de := decimal.NewFromFloat(num) + str := de.Truncate(size).String() + return str +} + +func getZero(size int32) string { + switch size { + case 0: + return "0" + case 1: + return "0.0" + case 2: + return "0.00" + case 3: + return "0.000" + case 4: + return "0.0000" + case 5: + return "0.00000" + case 6: + return "0.000000" + } + return "0" +} + +func GetGearNumStr(size int32) string { + switch size { + case 0: + return "1" + case 1: + return "0.1" + case 2: + return "0.01" + case 3: + return "0.001" + case 4: + return "0.0001" + case 5: + return "0.00001" + case 6: + return "0.000001" + case 7: + return "0.0000001" + case 8: + return "0.00000001" + } + return "0.1" +} + +func FloatToStr(num float64) string { + de := decimal.NewFromFloat(num) + str := de.String() + return str +} + +// StringAsInteger returns the integer value extracted from string, or zero +func StringAsInteger(s string) int { + if s == "" { + return 0 + } + if i, err := strconv.ParseInt(s, 10, 32); err == nil { + return int(i) + } + return 0 +} + +// StringAsInt64 returns the int64 value extracted from string, or zero +func StringAsInt64(s string) int64 { + if s == "" { + return 0 + } + if i, err := strconv.ParseInt(s, 10, 64); err == nil { + return i + } + return 0 +} + +func StringAsInt32(s string) int32 { + if s == "" { + return 0 + } + if i, err := strconv.ParseInt(s, 10, 64); err == nil { + return int32(i) + } + return 0 +} + +// StringToDecimal String To Decimal +func StringToDecimal(val string) decimal.Decimal { + cleanedNum := strings.TrimRight(val, "\x00") // 去除空字符 + cleanedNum = strings.TrimSpace(cleanedNum) // 去除空格 + cleanedNum = strings.ReplaceAll(cleanedNum, ",", "") // 去除逗号 + d, err := decimal.NewFromString(cleanedNum) + if err != nil { + return decimal.Zero + } + return d +} + +// StringToInt String => int +func StringToInt(val string) int { + i, err := strconv.Atoi(val) + if err != nil { + return 0 + } + return i +} + +// StringToFloat64 String => Float64 +func StringToFloat64(val string) float64 { + d, err := strconv.ParseFloat(val, 64) + if err != nil { + return 0 + } + return d +} + +// ToLower 返回小写字符串 +func ToLower(str string) string { + return strings.ToLower(str) +} + +// ToUpper 返回大写字符串 +func ToUpper(str string) string { + return strings.ToUpper(str) +} + +// IntToString int to string +func IntToString(num int) string { + if num == 0 { + return "0" + } + return strconv.Itoa(num) +} + +// CheckPhone returns true if a given sequence has between 9 and 14 digits +func CheckPhone(phone string, acceptEmpty bool) bool { + phone = OnlyDigits(phone) + + return (acceptEmpty && (phone == "")) || ((len([]rune(phone)) >= 9) && (len([]rune(phone)) <= 14)) +} + +// OnlyDigits returns only the numbers from the given string, after strip all the rest ( letters, spaces, etc. ) +func OnlyDigits(sequence string) string { + if utf8.RuneCountInString(sequence) > 0 { + re, _ := regexp.Compile(`[\D]`) + + sequence = re.ReplaceAllString(sequence, "") + } + + return sequence +} + +// CheckEmail returns true if the given sequence is a valid email address +// See https://tools.ietf.org/html/rfc2822#section-3.4.1 for details about email address anatomy +func CheckEmail(email string) bool { + if email == "" { + return false + } + + re := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") + + return re.MatchString(email) +} + +// IsNumericType checks if an interface's concrete type corresponds to some of golang native numeric types +func IsNumericType(x interface{}) bool { + switch x.(type) { + case uint: + return true + case uint8: // Or byte + return true + case uint16: + return true + case uint32: + return true + case uint64: + return true + case int: + return true + case int8: + return true + case int16: + return true + case int32: + return true + case float32: + return true + case float64: + return true + case complex64: + return true + case complex128: + return true + default: + return false + } +} + +// B2S converts byte slice to a string without memory allocation. +// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ . +// +// Note it may break if string and/or slice header will change +// in the future go versions. +func B2S(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + +// S2B converts string to a byte slice without memory allocation. +// Note it may break if string and/or slice header will change +// in the future go versions. +func S2B(s string) (b []byte) { + bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + sh := *(*reflect.StringHeader)(unsafe.Pointer(&s)) + bh.Data = sh.Data + bh.Len = sh.Len + bh.Cap = sh.Len + return b +} + +// Int64ToString int64 转到string +func Int64ToString(num int64) string { + if num == 0 { + return "" + } + return strconv.FormatInt(num, 10) +} + +// Float64ToString Float64 转 string +// prec: 小数位数 -1 为全保留 与 FloatCutStr 不同的是 这里会四舍五入 +func Float64ToString(num float64, prec int32) string { + return strconv.FormatFloat(num, 'f', int(prec), 64) +} + +// SliceRemoveDuplicates 除去[]string数组重复的数据 +func SliceRemoveDuplicates(slice []string) []string { + sort.Strings(slice) + i := 0 + var j int + for { + if i >= len(slice)-1 { + break + } + + for j = i + 1; j < len(slice) && slice[i] == slice[j]; j++ { + } + + slice = append(slice[:i+1], slice[j:]...) + i++ + } + + return slice +} + +func GetSymbolIdStr(coinId, currId string) string { + return fmt.Sprintf("%v/%v", coinId, currId) +} + +func GetSymbolCoinIdAndCurrId(symbolIdStr string) (string, string) { + l := strings.Split(symbolIdStr, "/") + return l[0], l[1] +} + +// CheckIdCard 检验身份证 +func CheckIdCard(card string) bool { + // 18位身份证 ^(\d{17})([0-9]|X)$ + // 匹配规则 + // (^\d{15}$) 15位身份证 + // (^\d{18}$) 18位身份证 + // (^\d{17}(\d|X|x)$) 18位身份证 最后一位为X的用户 + regRuler := "(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x)$)" + + // 正则调用规则 + reg := regexp.MustCompile(regRuler) + + // 返回 MatchString 是否匹配 + return reg.MatchString(card) +} +func FilteredSQLInject(to_match_str string) bool { + //过滤 ‘ + //ORACLE 注解 -- /**/ + //关键字过滤 update ,delete + // 正则的字符串, 不能用 " " 因为" "里面的内容会转义 + str := `(?:')|(?:--)|(/\*(?:.|[\n\r])*?\*/)|((select|update|and|or|delete|insert|trancate|char|chr|into|substr|ascii|declare|exec|count|master|into|drop|execute))` + re, err := regexp.Compile(str) + if err != nil { + return false + } + return re.MatchString(to_match_str) +} +func TrimHtml(src string) string { + //将HTML标签全转换成小写 + re, _ := regexp.Compile("\\<[\\S\\s]+?\\>") + src = re.ReplaceAllStringFunc(src, strings.ToLower) + //去除STYLE + re, _ = regexp.Compile("\\") + src = re.ReplaceAllString(src, "") + //去除SCRIPT + re, _ = regexp.Compile("\\") + src = re.ReplaceAllString(src, "") + //去除所有尖括号内的HTML代码,并换成换行符 + re, _ = regexp.Compile("\\<[\\S\\s]+?\\>") + src = re.ReplaceAllString(src, "\n") + //去除连续的换行符 + re, _ = regexp.Compile("\\s{2,}") + src = re.ReplaceAllString(src, "\n") + return strings.TrimSpace(src) +} + +// ReplaceImages 过滤掉图片 +func ReplaceImages(htmls string) string { + result := htmls + regx := `]*src[="'s]+[^.]*/([^.]+).[^"']+["']?[^>]*>` + var imgRE = regexp.MustCompile(regx) + imgs := imgRE.FindAllStringSubmatch(htmls, -1) + for i := range imgs { + v := imgs[i] + result = strings.ReplaceAll(result, v[0], "") + } + return result +} + +// KeySubString 文章搜索专用 +func KeySubString(key, value string, leng int) string { + b := ReplaceImages(value) + pos := strings.Index(b, key) + if pos < 0 { + return "" + } + start := b[0:pos] + end := b[pos:] + out := Substr(end, leng) + return start + out +} + +// Substr 截取字符串 +func Substr(s string, l int) string { + if len(s) <= l { + return s + } + ss, sl, rl, rs := "", 0, 0, []rune(s) + for _, r := range rs { + rint := int(r) + if rint < 128 { + rl = 1 + } else { + rl = 2 + } + + if sl+rl > l { + break + } + sl += rl + ss += string(r) + } + return ss +} + +/* +根据字符串获取小数位数 +*/ +func GetPrecision(value string) int { + // 去掉末尾的多余零 + value = strings.TrimRight(value, "0") + + // 找到小数点的位置 + decimalPos := strings.Index(value, ".") + + if decimalPos == -1 { + // 如果没有小数点,说明是整数,精度为0 + return 0 + } + + // 计算小数点后的位数 + return len(value) - decimalPos - 1 +} + +// 替换字符串后缀 +func ReplaceSuffix(text, oldSuffix, newSuffix string) string { + if strings.HasSuffix(text, oldSuffix) { + return text[:len(text)-len(oldSuffix)] + newSuffix + } + return text +} diff --git a/pkg/utility/stringhelper_test.go b/pkg/utility/stringhelper_test.go new file mode 100644 index 0000000..fe2b2d5 --- /dev/null +++ b/pkg/utility/stringhelper_test.go @@ -0,0 +1,49 @@ +package utility + +import ( + "fmt" + "math/rand" + "strconv" + "testing" +) + +func Test_(t *testing.T) { + // t.Log(decimal.NewFromFloat(0)) + t.Log(strconv.FormatFloat(1.04, 'f', 1, 64)) + t.Log(strconv.FormatFloat(1.05, 'f', 1, 64)) + t.Log(strconv.FormatFloat(1.06, 'f', 1, 64)) + t.Log(strconv.FormatFloat(1.006, 'f', 2, 64)) + // t.Log(strconv.FormatFloat(`1.006`, 64)) +} + +// i := int64(32) +// s := strconv.FormatInt(i, 16) +// println(s) + +// 对比下 Float64ToString 和 FloatCutStr 的效率 +// go test -bench=_QE_ -benchmem +// -benchtime 默认为1秒 -benchmem 获得内存分配的统计数据 +func Benchmark_QE_1(b *testing.B) { + for i := 0; i < b.N; i++ { + Float64ToString(getRandData(), 2) + } +} + +func Benchmark_QE_2(b *testing.B) { + for i := 0; i < b.N; i++ { + FloatCutStr(getRandData(), 2) // 已废弃 + } +} + +func getRandData() float64 { + return float64(rand.Intn(1000000)) / 1000 +} +func TestTrimHtml(t *testing.T) { + a := `sff` // `` + x := TrimHtml(a) + fmt.Print(x) +} + +// Benchmark_QE_1-6 4902826 243.3 ns/op 31 B/op 2 allocs/op +// Benchmark_QE_2-6 1275004 940.6 ns/op 137 B/op 11 allocs/op +// 故此将优先使用 Float64ToString 并将已使用的 FloatCutStr 进行替换 diff --git a/pkg/utility/timehelper/timehelper.go b/pkg/utility/timehelper/timehelper.go new file mode 100644 index 0000000..1e3a656 --- /dev/null +++ b/pkg/utility/timehelper/timehelper.go @@ -0,0 +1,306 @@ +package timehelper + +import ( + "fmt" + "go-admin/pkg/utility" + "strings" + "time" +) + +const ( + DATEFORMAT = "2006-01-02" + TIMEFORMAT = "2006-01-02 15:04:05" + MONTHFORMAT = "2006-01" + DATETIMEFORMAT = "2006-01-02 15:04" + TimeNil = "1900-01-01 00:00:00" + DefaultUnix = -62135596800 // 时间戳默认初始值 +) + +// GetDateUnixDate 返回带毫秒的时间戳,如果需要转化为时间类型time, +// 不带毫秒的:time.Unix(1663315884651/1000,0), +// 带毫秒的time.Unix(1663315884651/1000, 1000000*(1663315884651%1000)) +func GetDateUnixDate(date time.Time) int64 { + return date.UnixMilli() +} + +func ConvertTimeLocalSec(date int64) time.Time { + d1 := time.Unix(date/1000, 1000000*(date%1000)) + d2 := time.Date(d1.Year(), d1.Month(), d1.Day(), d1.Hour(), d1.Minute(), d1.Second(), d1.Nanosecond(), time.Local) + return d2 +} + +// TimeSubDays 时间间隔天数 +func TimeSubDays(t1, t2 time.Time) int { + + if t1.Location().String() != t2.Location().String() { + return -1 + } + hours := t1.Sub(t2).Hours() + + if hours <= 0 { + return -1 + } + // sub hours less than 24 + if hours < 24 { + // may same day + t1y, t1m, t1d := t1.Date() + t2y, t2m, t2d := t2.Date() + isSameDay := (t1y == t2y && t1m == t2m && t1d == t2d) + + if isSameDay { + return 0 + } + return 1 + } + // equal or more than 24 + if (hours/24)-float64(int(hours/24)) == 0 { // just 24's times + return int(hours / 24) + } + // more than 24 hours + return int(hours/24) + 1 +} + +// ConvertTimeLocal s +func ConvertTimeLocal(d1 time.Time) time.Time { + d2 := time.Date(d1.Year(), d1.Month(), d1.Day(), d1.Hour(), d1.Minute(), d1.Second(), 0, time.Local) + return d2 +} + +// ConvertTimeLocalLong 转本地时间戳输出 +func ConvertTimeLocalLong(d1 time.Time) int64 { + d2 := time.Date(d1.Year(), d1.Month(), d1.Day(), d1.Hour(), d1.Minute(), d1.Second(), 0, time.Local) + return d2.Unix() +} + +// ConvertTimeDayLocal s +func ConvertTimeDayLocal(d1 time.Time) time.Time { + d2 := time.Date(d1.Year(), d1.Month(), d1.Day(), 0, 0, 0, 0, time.Local) + return d2 +} + +// // ToTimeHour 转为时分秒 +// func ToTimeHour(ltime int64) string { +// now := IntToTime(ltime) +// return now.Format("15:04:05") +// } + +// ParseTimeStrInLocal 从时间字符串解析时间,默认 当前时间 +func ParseTimeStrInLocal(timeStr string, defaultT ...time.Time) time.Time { + t, err := time.ParseInLocation(TIMEFORMAT, timeStr, time.Local) + if err != nil { + if len(defaultT) > 0 { + return defaultT[0] + } + + return time.Now() + } + + return t +} + +// IntToTime 时间戳转为时间类型 +func IntToTime(intime int64) time.Time { + return time.Unix(intime/1000, 0) +} + +func Int64ToTime(in int64) time.Time { + return time.Unix(in, 0) +} + +// GetPastDay 当前时间算起,过去num天内的开始日期、结束日期 +func GetPastDay(num int) (start, end time.Time) { + tn := time.Now() + // 当前时间,天数为单位 + nowday := time.Date(tn.Year(), tn.Month(), tn.Day(), 0, 0, 0, 0, time.Local) + // 过去30天 + oldTime := nowday.AddDate(0, 0, -num) + return oldTime, nowday +} + +// ConvertTimeToString 格式时间,返回前台,yyyy-mm-dd hh:mm:ss +func ConvertTimeToString(t time.Time) string { + return t.Format("2006-01-02 15:04:05") +} + +// ConvertTimeToStringMin 格式时间,返回前台,yyyy-mm-dd hh:mm:ss +func ConvertTimeToStringMin(t time.Time) string { + return t.Format("2006-01-02 15:04") +} + +// ConvertTimeToStringDay 格式时间,返回前台,yyyy-mm-dd +func ConvertTimeToStringDay(t time.Time) string { + return t.Format("2006-01-02") +} + +// ConvertTimeToString2 格式时间,返回前台,yyyy.mm.dd hh:mm 2020.06.02 17:50 +func ConvertTimeToString2(t time.Time) string { + return t.Format("2006.01.02 15:04") +} + +// ConvertTimeTostringYear 格式时间,返回前台,mm-dd yyyy +func ConvertTimeTostringYear(s time.Time) string { + return s.Format("01-02 2006") +} + +func Time2TimeStampMilli(s time.Time) string { + return utility.Int64ToString(s.UnixMilli()) +} + +// GetWeeHours 返回若干天后的凌晨时间戳 若day=0 则返回当天凌晨的时间 +// var cstSh, _ = time.LoadLocation("Asia/Shanghai") //上海时区 +func GetWeeHours(day int) time.Time { + t := time.Now() // 当前时间 + OnHour := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) + return OnHour.AddDate(0, 0, day) + + // 另一种写法 + // return uint(time.Date(t.Year(), t.Month(), t.Day()+day, 0, 0, 0, 0, t.Location()).Unix()) +} + +// GetMonths 返回若干月后的凌晨时间戳 若months=0 则返回当月凌晨的时间 +func GetMonths(months int) time.Time { + t := time.Now() // 当前时间 + OnHour := time.Date(t.Year(), t.Month(), 0, 0, 0, 0, 0, t.Location()) + return OnHour.AddDate(0, months, 0) +} +func GetTimeFromStrDate(date string) (year, month, day int) { + d, err := time.Parse(DATEFORMAT, date) + if err != nil { + fmt.Println("出生日期解析错误!") + return 0, 0, 0 + } + year = d.Year() + month = int(d.Month()) + day = d.Day() + return +} +func GetAge(year int) (age int) { + if year <= 0 { + age = -1 + } + nowyear := time.Now().Year() + age = nowyear - year + return +} + +// GetWeekDayByNum 根据输入的数字日期返回周XX,字符串隔开的 (0,1,2,3) +func GetWeekDayByNum(weeks string, lang string) []string { + var result []string + if len(weeks) == 0 { + return result + } + weeks = strings.TrimRight(weeks, ",") + arr := strings.Split(weeks, ",") + weekMap := map[string]string{} + switch lang { + case "zh-CN": + weekMap = map[string]string{ + "0": "周日", + "1": "周一", + "2": "周二", + "3": "周三", + "4": "周四", + "5": "周五", + "6": "周六", + } + case "zh-HK": + weekMap = map[string]string{ + "0": "周日", + "1": "週一", + "2": "週二", + "3": "週三", + "4": "週四", + "5": "週五", + "6": "週六", + } + case "jp": + weekMap = map[string]string{ + "0": "日曜日", + "1": "月曜日", + "2": "火曜日", + "3": "水曜日", + "4": "木曜日", + "5": "金曜日", + "6": "土曜日", + } + case "kr": + weekMap = map[string]string{ + "0": "일요일", + "1": "월요일", + "2": "화요일", + "3": "수요일", + "4": "목요일", + "5": "금요일", + "6": "토요일", + } + default: + weekMap = map[string]string{ + "0": "Sunday", + "1": "Monday", + "2": "Tuesday", + "3": "Wednesday", + "4": "Thursday", + "5": "Friday", + "6": "Saturday", + } + } + for _, a := range arr { + if value, ok := weekMap[a]; ok { + result = append(result, value) + } + } + return result +} + +// 时间差计算 +func GetDateSub(begin time.Time, end time.Time) time.Duration { + begin1 := GetDateFormat(begin) + end1 := GetDateFormat(end) + return end1.Sub(begin1) +} + +// GetDateFormat 格式化日期用来计算时间差 +func GetDateFormat(date time.Time) time.Time { + return time.Date(date.Year(), date.Month(), date.Day(), date.Hour(), date.Minute(), date.Second(), 0, time.UTC) +} + +// 本周一 +func ThisWeek1() time.Time { + // w1 = today - (Weekday+6)%7 + today := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Local) + factor := time.Duration((today.Weekday()+6)%7) * 24 * time.Hour + + return today.Add(-factor) +} + +// 本月 1号 +func ThisMonthD1() time.Time { + // d1 = today - day - 1 + today := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Local) + factor := time.Duration(today.Day()-1) * 24 * time.Hour + + return today.Add(-factor) +} + +// 近 xx 天 +func Latest7Day(day int) time.Time { + today := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Local) + + return today.Add(-(time.Duration(day)) * 24 * time.Hour) +} +func LatestMonth(num int) time.Time { + today := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Local) + + return today.AddDate(0, -num, 0) +} + +// 近 30 天 +func Latest30Day() time.Time { + today := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Local) + + return today.Add(-30 * 24 * time.Hour) +} + +func YMDUnix(t time.Time) int64 { + return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()).Unix() +} diff --git a/pkg/utility/timehelper/timehelper_test.go b/pkg/utility/timehelper/timehelper_test.go new file mode 100644 index 0000000..a7a38e2 --- /dev/null +++ b/pkg/utility/timehelper/timehelper_test.go @@ -0,0 +1,18 @@ +package timehelper + +import ( + "fmt" + "testing" + "time" +) + +func Test_GetWeeHours(t *testing.T) { + fmt.Println(time.Unix(1672129500, 0)) + + fmt.Println(GetWeeHours(-1)) +} +func TestGetTimeFromStrDate(t *testing.T) { + year, _, _ := GetTimeFromStrDate("2022-06-12") + age := GetAge(year) + fmt.Println(age) +} diff --git a/restart.sh b/restart.sh new file mode 100644 index 0000000..ef8c4c1 --- /dev/null +++ b/restart.sh @@ -0,0 +1,10 @@ +#!/bin/bash +echo "go build" +go mod tidy +go build -o go-admin main.go +chmod +x ./go-admin +echo "kill go-admin service" +killall go-admin # kill go-admin service +nohup ./go-admin server -c=config/settings.dev.yml >> access.log 2>&1 & #后台启动服务将日志写入access.log文件 +echo "run go-admin success" +ps -aux | grep go-admin diff --git a/scripts/Dockerfile b/scripts/Dockerfile new file mode 100644 index 0000000..01bdeb8 --- /dev/null +++ b/scripts/Dockerfile @@ -0,0 +1,6 @@ +FROM alpine + +COPY ./go-admin / +EXPOSE 8000 + +CMD ["/go-admin","server","-c", "/config/settings.yml"] diff --git a/scripts/k8s/deploy.yml b/scripts/k8s/deploy.yml new file mode 100644 index 0000000..4479227 --- /dev/null +++ b/scripts/k8s/deploy.yml @@ -0,0 +1,57 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: go-admin + labels: + app: go-admin + service: go-admin +spec: + ports: + - port: 8000 + name: http + protocol: TCP + selector: + app: go-admin +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: go-admin-v1 + labels: + app: go-admin + version: v1 +spec: + replicas: 1 + selector: + matchLabels: + app: go-admin + version: v1 + template: + metadata: + labels: + app: go-admin + version: v1 + spec: + containers: + - name: go-admin + image: registry.cn-shanghai.aliyuncs.com/go-admin-team/go-admin + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8000 + volumeMounts: + - name: go-admin + mountPath: /temp + - name: go-admin + mountPath: /static + - name: go-admin-config + mountPath: /config/ + readOnly: true + volumes: + - name: go-admin + persistentVolumeClaim: + claimName: go-admin + - name: go-admin-config + configMap: + name: settings-admin +--- diff --git a/scripts/k8s/prerun.sh b/scripts/k8s/prerun.sh new file mode 100644 index 0000000..f7bb046 --- /dev/null +++ b/scripts/k8s/prerun.sh @@ -0,0 +1,3 @@ +#!/bin/bash +kubectl create ns go-admin +kubectl create configmap settings-admin --from-file=../../config/settings.yml -n go-admin diff --git a/scripts/k8s/storage.yml b/scripts/k8s/storage.yml new file mode 100644 index 0000000..2866a90 --- /dev/null +++ b/scripts/k8s/storage.yml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: go-admin + namespace: go-admin +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: "1Mi" + volumeName: + storageClassName: nfs-csi \ No newline at end of file diff --git a/services/binanceservice/binancerest.go b/services/binanceservice/binancerest.go new file mode 100644 index 0000000..cea8dee --- /dev/null +++ b/services/binanceservice/binancerest.go @@ -0,0 +1,808 @@ +package binanceservice + +import ( + "context" + "errors" + "fmt" + DbModels "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/const/rediskey" + commondto "go-admin/common/dto" + "go-admin/common/global" + "go-admin/common/helper" + "go-admin/models" + "go-admin/models/spot" + "go-admin/pkg/httputils" + "go-admin/pkg/utility" + "go-admin/pkg/utility/snowflakehelper" + "strconv" + "strings" + "time" + + "github.com/go-redis/redis/v8" + "github.com/jinzhu/copier" + "github.com/shopspring/decimal" + "gorm.io/gorm" + + "github.com/bytedance/sonic" + log "github.com/go-admin-team/go-admin-core/logger" +) + +const ( + binanceRestApi = "https://api.binance.com" +) + +var ErrorMaps = map[float64]string{ + -2021: "订单已拒绝。请调整触发价并重新下订单。 对于买入/做多,止盈订单触发价应低于市场价,止损订单的触发价应高于市场价。卖出/做空则与之相反", + -4164: "下单失败。少于最小下单金额", + -4061: "持仓方向需要设置为单向持仓。", + -2019: "保证金不足", + -1111: "金额设置错误。精度错误", + -1021: "请求的时间戳在recvWindow之外", + -2011: "该交易对没有订单", + -2010: "账户余额不足", +} + +type SpotRestApi struct { +} + +/* +获取 现货交易-规范信息 + + - return @info 规范信息 + - return @err 错误信息 +*/ +func (e SpotRestApi) GetExchangeInfo() (symbols []spot.Symbol, err error) { + url := fmt.Sprintf("%s%s?permissions=SPOT", binanceRestApi, "/api/v3/exchangeInfo") + mapData, err := httputils.NewHttpRequestWithFasthttp("GET", url, "", map[string]string{}) + + if err != nil { + return + } + + if len(mapData) == 0 { + err = errors.New("获取现货交易-规范信息数量为空") + return + } + + var info spot.ExchangeInfo + err = sonic.Unmarshal(mapData, &info) + + if err == nil { + return info.Symbols, nil + } + + return +} + +/* +获取现货24h行情变更 +*/ +func (e SpotRestApi) GetSpotTicker24h(tradeSet *map[string]models.TradeSet) (deleteSymbols []string, err error) { + tickerApi := fmt.Sprintf("%s%s", binanceRestApi, "/api/v3/ticker/24hr") + mapData, err := httputils.NewHttpRequestWithFasthttp("GET", tickerApi, "", map[string]string{}) + + if err != nil { + return []string{}, err + } + deleteSymbols = make([]string, 0) + if len(mapData) == 0 { + return deleteSymbols, errors.New("获取交易对失败,或数量为空") + } + tickers := make([]spot.SpotTicker24h, 0) + err = sonic.Unmarshal([]byte(mapData), &tickers) + + if err != nil { + log.Error("反序列化json失败", err) + } + + for _, item := range tickers { + key := fmt.Sprintf("%s:%s", global.TICKER_SPOT, item.Symbol) + symbol, exits := (*tradeSet)[item.Symbol] + + if !exits { + helper.DefaultRedis.DeleteString(key) + continue + } + symbol.OpenPrice = utility.StringAsFloat(item.OpenPrice) + symbol.PriceChange = utility.StringAsFloat(item.PriceChangePercent) + symbol.LowPrice = item.LowPrice + symbol.HighPrice = item.HighPrice + symbol.Volume = item.Volume + symbol.QuoteVolume = item.QuoteVolume + symbol.LastPrice = item.LastPrice + + val, err := sonic.Marshal(symbol) + + if !strings.HasSuffix(item.Symbol, symbol.Currency) || item.Count <= 0 || utility.StringToFloat64(item.QuoteVolume) <= 0 { + helper.DefaultRedis.DeleteString(key) + deleteSymbols = append(deleteSymbols, item.Symbol) + continue + } + + if err != nil { + log.Error("序列化失败", item.Symbol) + continue + } + err = helper.DefaultRedis.SetString(key, string(val)) + + if err != nil { + log.Error("缓存交易对失败|", item.Symbol, err) + } + + helper.DefaultRedis.AddSortSet(global.COIN_PRICE_CHANGE, symbol.PriceChange, symbol.Coin) + } + return deleteSymbols, nil +} + +/* +获取单个交易对24h行情 + + - @symbol 交易对 + - @data 结果 +*/ +func (e SpotRestApi) GetSpotTicker24(symbol string, data *models.Ticker24, tradeSet *models.TradeSet) error { + key := fmt.Sprintf("%s:%s", global.TICKER_SPOT, symbol) + val, err := helper.DefaultRedis.GetString(key) + + if err != nil { + return err + } + + err = sonic.Unmarshal([]byte(val), tradeSet) + + if err != nil { + return err + } + + if tradeSet.Coin != "" { + data.HighPrice = tradeSet.HighPrice + data.ChangePercent = fmt.Sprintf("%g", tradeSet.PriceChange) + data.LastPrice = tradeSet.LastPrice + data.LowPrice = tradeSet.LowPrice + data.OpenPrice = fmt.Sprintf("%g", tradeSet.OpenPrice) + data.QuoteVolume = tradeSet.QuoteVolume + data.Volume = tradeSet.Volume + } + + return nil +} + +type Ticker struct { + Symbol string `json:"symbol"` + Price string `json:"price"` +} + +func (e SpotRestApi) Ticker() { + tickerApi := fmt.Sprintf("%s%s", binanceRestApi, "/api/v3/ticker/price") + mapData, _ := httputils.NewHttpRequestWithFasthttp("GET", tickerApi, "", map[string]string{}) + //sonic.Unmarshal(mapData, &tickerData) + helper.DefaultRedis.SetString(rediskey.SpotSymbolTicker, string(mapData)) +} + +// OrderPlace 现货下单 +func (e SpotRestApi) OrderPlace(orm *gorm.DB, params OrderPlacementService) error { + if orm == nil { + return errors.New("数据库实例为空") + } + err2 := params.CheckParams() + if err2 != nil { + return err2 + } + paramsMaps := map[string]string{ + "symbol": params.Symbol, + "side": params.Side, + "quantity": params.Quantity.String(), + "type": params.Type, + "newClientOrderId": params.NewClientOrderId, + } + if strings.ToUpper(params.Type) != "MARKET" { //市价 + paramsMaps["price"] = params.Price.String() + paramsMaps["timeInForce"] = "GTC" + + if strings.ToUpper(params.Type) == "TAKE_PROFIT_LIMIT" || strings.ToUpper(params.Type) == "STOP_LOSS_LIMIT" { + paramsMaps["stopPrice"] = params.StopPrice.String() + } + } + var apiUserInfo DbModels.LineApiUser + + err := orm.Model(&DbModels.LineApiUser{}).Where("id = ?", params.ApiId).Find(&apiUserInfo).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + log.Errorf("api用户出错 err: %+v", err) + return err + } + client := GetClient(&apiUserInfo) + resp, _, err := client.SendSpotAuth("/api/v3/order", "POST", paramsMaps) + if err != nil { + dataMap := make(map[string]interface{}) + + if err.Error() != "" { + if err := sonic.Unmarshal([]byte(err.Error()), &dataMap); err != nil { + return fmt.Errorf("api_id:%d 交易对:%s 下订单失败:%+v", apiUserInfo.Id, params.Symbol, err.Error()) + } + } + code, ok := dataMap["code"] + if ok { + errContent := ErrorMaps[code.(float64)] + paramsVal, _ := sonic.MarshalString(paramsMaps) + + log.Error("api_id:", utility.IntToString(apiUserInfo.Id), " 交易对:", params.Symbol, " 下单参数:", paramsVal) + + if errContent == "" { + errContent, _ = dataMap["msg"].(string) + } + + return fmt.Errorf("api_id:%d 交易对:%s 下订单失败:%s", apiUserInfo.Id, params.Symbol, errContent) + } + if strings.Contains(err.Error(), "Unknown order sent.") { + return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%+v", apiUserInfo.Id, params.Symbol, ErrorMaps[-2011]) + } + + return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%v", apiUserInfo.Id, params.Symbol, err) + } + var dataMap map[string]interface{} + if err := sonic.Unmarshal(resp, &dataMap); err != nil { + return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%+v", apiUserInfo.Id, params.Symbol, err.Error()) + } + //code, ok := dataMap["code"] + //if !ok { + // return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%s", apiUserInfo.Id, params.Symbol, dataMap["message"]) + // + //} + //if code.(float64) != 200 { + // return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%s", apiUserInfo.Id, params.Symbol, dataMap["message"]) + //} + return nil +} + +// CancelOpenOrders 撤销单一交易对下所有挂单 包括了来自订单列表的挂单 +func (e SpotRestApi) CancelOpenOrders(orm *gorm.DB, req CancelOpenOrdersReq) error { + if orm == nil { + return errors.New("数据库实例为空") + } + err := req.CheckParams() + if err != nil { + return err + } + params := map[string]string{ + "symbol": req.Symbol, + } + var apiUserInfo DbModels.LineApiUser + + err = orm.Model(&DbModels.LineApiUser{}).Where("id = ?", req.ApiId).Find(&apiUserInfo).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return fmt.Errorf("api_id:%d 交易对:%s api用户出错:%+v", apiUserInfo.Id, req.Symbol, err) + } + var client *helper.BinanceClient + + if apiUserInfo.UserPass == "" { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress) + } else { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress) + } + _, _, err = client.SendSpotAuth("/api/v3/openOrders", "DELETE", params) + if err != nil { + dataMap := make(map[string]interface{}) + + if err.Error() != "" { + if err := sonic.Unmarshal([]byte(err.Error()), &dataMap); err != nil { + return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%+v", apiUserInfo.Id, req.Symbol, err.Error()) + } + } + code, ok := dataMap["code"] + if ok { + return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%s", apiUserInfo.Id, req.Symbol, ErrorMaps[code.(float64)]) + } + if strings.Contains(err.Error(), "Unknown order sent.") { + return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%+v", apiUserInfo.Id, req.Symbol, ErrorMaps[-2011]) + } + return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%+v", apiUserInfo.Id, req.Symbol, err.Error()) + } + + return nil +} + +// CancelOpenOrderByOrderSn 通过单一订单号取消委托 +func (e SpotRestApi) CancelOpenOrderByOrderSn(apiUserInfo DbModels.LineApiUser, symbol string, newClientOrderId string) error { + params := map[string]string{ + "symbol": symbol, + "origClientOrderId": newClientOrderId, + "recvWindow": "10000", + } + var client *helper.BinanceClient + + if apiUserInfo.UserPass == "" { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress) + } else { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress) + } + _, code, err := client.SendSpotAuth("/api/v3/order ", "DELETE", params) + if err != nil || code != 200 { + log.Error("取消现货委托失败 参数:", params) + log.Error("取消现货委托失败 code:", code) + log.Error("取消现货委托失败 err:", err) + dataMap := make(map[string]interface{}) + if err.Error() != "" { + if err := sonic.Unmarshal([]byte(err.Error()), &dataMap); err != nil { + return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%+v", apiUserInfo.Id, symbol, err.Error()) + } + } + + code, ok := dataMap["code"] + if ok { + return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%s", apiUserInfo.Id, symbol, ErrorMaps[code.(float64)]) + } + if strings.Contains(err.Error(), "Unknown order sent.") { + return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%+v", apiUserInfo.Id, symbol, ErrorMaps[-2011]) + } + return fmt.Errorf("api_id:%d 交易对:%s 撤销订单失败:%+v", apiUserInfo.Id, symbol, err.Error()) + } + + return nil +} + +// CalcEntryCashPriceByOrder 计算现货主单均价 +func CalcEntryCashPriceByOrder(orderInfo *DbModels.LinePreOrder, orm *gorm.DB) (EntryPriceResult, error) { + //找到主单成交的记录 + var id int + if orderInfo.Pid > 0 { + id = orderInfo.Pid + } else { + id = orderInfo.Id + } + + orderLists := make([]DbModels.LinePreOrder, 0) + orm.Model(&DbModels.LinePreOrder{}).Where(" symbol = ? AND site = 'BUY' AND order_type in ('1','8') AND status ='9' AND (id = ? OR pid = ?)", orderInfo.Symbol, id, id).Find(&orderLists) + + var ( + totalNum decimal.Decimal //总成交数量 + totalMoney decimal.Decimal //总金额 + entryPrice decimal.Decimal //均价 + initPrice decimal.Decimal //主单下单价格 + firstId int //主单id + ) + for _, list := range orderLists { + num, _ := decimal.NewFromString(list.Num) + totalNum = totalNum.Add(num) + price, _ := decimal.NewFromString(list.Price) + totalMoney = totalMoney.Add(num.Mul(price)) + if list.OrderType == "1" { + firstId = list.Id + initPrice, _ = decimal.NewFromString(list.Price) + } + } + if totalNum.GreaterThan(decimal.Zero) { + entryPrice = totalMoney.Div(totalNum) + } + return EntryPriceResult{ + TotalNum: totalNum, + EntryPrice: entryPrice, + FirstPrice: initPrice, + FirstId: firstId, + TotalMoney: totalMoney, + }, nil +} + +// ClosePosition 平仓 +// symbol 交易对 +// orderSn 平仓单号 +// quantity 平仓数量 +// side 原始仓位方向 +// apiUserInfo 用户信息 +// orderType 平仓类型 限价(LIMIT) 市价() +func (e SpotRestApi) ClosePosition(symbol string, orderSn string, quantity decimal.Decimal, side string, + apiUserInfo DbModels.LineApiUser, orderType string, rate string, price decimal.Decimal) error { + endpoint := "/api/v3/order " + params := map[string]string{ + "symbol": symbol, + "type": orderType, + "quantity": quantity.String(), + "newClientOrderId": orderSn, + } + if side == "SELL" { + params["side"] = "BUY" + } else { + params["side"] = "SELL" + } + + if orderType == "LIMIT" { + key := fmt.Sprintf("%s:%s", global.TICKER_SPOT, symbol) + tradeSet, _ := helper.GetObjString[models.TradeSet](helper.DefaultRedis, key) + rateFloat, _ := decimal.NewFromString(rate) + if rateFloat.GreaterThan(decimal.Zero) { + if side == "SELL" { //仓位是空 平空的话 + price = price.Mul(decimal.NewFromInt(1).Add(rateFloat)).Truncate(int32(tradeSet.PriceDigit)) + } else { + price = price.Mul(decimal.NewFromInt(1).Sub(rateFloat)).Truncate(int32(tradeSet.PriceDigit)) + } + params["price"] = price.String() + } + } + params["timeInForce"] = "GTC" + client := GetClient(&apiUserInfo) + + resp, _, err := client.SendFuturesRequestAuth(endpoint, "POST", params) + if err != nil { + var dataMap map[string]interface{} + if err2 := sonic.Unmarshal([]byte(err.Error()), &dataMap); err2 != nil { + return fmt.Errorf("api_id:%d 交易对:%s 平仓出错:%s", apiUserInfo.Id, symbol, err.Error()) + } + code, ok := dataMap["code"] + if ok { + errContent := FutErrorMaps[code.(float64)] + + if errContent == "" { + errContent = err.Error() + } + + return fmt.Errorf("api_id:%d 交易对:%s 平仓出错:%s", apiUserInfo.Id, symbol, errContent) + } + } + var orderResp FutOrderResp + err = sonic.Unmarshal(resp, &orderResp) + if err != nil { + return fmt.Errorf("api_id:%d 交易对:%s 平仓出错:%s", apiUserInfo.Id, symbol, err.Error()) + } + if orderResp.Symbol == "" { + return fmt.Errorf("api_id:%d 交易对:%s 平仓出错:未找到订单信息", apiUserInfo.Id, symbol) + } + return nil +} + +func GetClient(apiUserInfo *DbModels.LineApiUser) *helper.BinanceClient { + var client *helper.BinanceClient + if apiUserInfo.UserPass == "" { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress) + } else { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress) + } + return client +} + +/* +重下止盈单 +*/ +func (e SpotRestApi) reTakeOrder(parentOrderInfo DbModels.LinePreOrder, orm *gorm.DB) { + price, _ := decimal.NewFromString(parentOrderInfo.Price) + num, _ := decimal.NewFromString(parentOrderInfo.Num) + parentId := parentOrderInfo.Id + + if parentOrderInfo.Pid > 0 { + parentId = parentOrderInfo.Pid + } + + var takePrice decimal.Decimal + holdeAKey := fmt.Sprintf(rediskey.HoldeA, parentId) + holdeAVal, _ := helper.DefaultRedis.GetString(holdeAKey) + holdeA := HoldeData{} + + if holdeAVal != "" { + sonic.Unmarshal([]byte(holdeAVal), &holdeA) + } + + //查询持仓失败 + if holdeA.Id == 0 { + log.Error("查询A账号持仓失败") + return + } + + //加仓次数大于0 就需要使用均价 + if holdeA.PositionIncrementCount > 0 { + price = holdeA.AveragePrice + num = holdeA.TotalQuantity + } + + if parentOrderInfo.Site == "BUY" { + takePrice = price.Mul(decimal.NewFromInt(100).Add(parentOrderInfo.ProfitRate)).Div(decimal.NewFromInt(100)) + } else { + takePrice = price.Mul(decimal.NewFromInt(100).Sub(parentOrderInfo.ProfitRate)).Div(decimal.NewFromInt(100)) + } + var takeOrder, oldTakeOrder DbModels.LinePreOrder + + if err := orm.Model(&oldTakeOrder).Where("pid= ? AND order_type ='5'", parentId).First(&oldTakeOrder).Error; err != nil { + log.Error("查询止盈单失败") + return + } + + tradeset, _ := GetTradeSet(oldTakeOrder.Symbol, 0) + + if tradeset.Coin == "" { + log.Error("查询交易对失败") + return + } + copier.Copy(&takeOrder, &oldTakeOrder) + + takeOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10) + takeOrder.Price = takePrice.Truncate(int32(tradeset.PriceDigit)).String() + takeOrder.Num = num.Mul(decimal.NewFromFloat(0.995)).Truncate(int32(tradeset.AmountDigit)).String() + takeOrder.Desc = "" + takeOrder.Status = 0 + takeOrder.Id = 0 + + if err := orm.Create(&takeOrder).Error; err != nil { + log.Error("创建新止盈单失败") + return + } + + params := OrderPlacementService{ + ApiId: parentOrderInfo.ApiId, + Symbol: parentOrderInfo.Symbol, + Price: utility.StrToDecimal(takeOrder.Price), + Quantity: utility.StringToDecimal(takeOrder.Num), + Side: "SELL", + Type: "TAKE_PROFIT_LIMIT", + TimeInForce: "GTC", + StopPrice: utility.StrToDecimal(takeOrder.Price), + NewClientOrderId: takeOrder.OrderSn, + } + + apiUserInfo, _ := GetApiInfo(parentOrderInfo.ApiId) + + if apiUserInfo.Id == 0 { + log.Error("获取用户api失败") + return + } + + if err := CancelSpotOrder(parentOrderInfo.Symbol, &apiUserInfo, "SELL"); err != nil { + log.Error("取消旧止盈失败 err:", err) + } else { + if err := orm.Model(&DbModels.LinePreOrder{}).Where("pid = ? AND order_type =5 AND status in ('0','1','5') AND order_sn !=?", parentId, takeOrder.OrderSn).Update("status", "4").Error; err != nil { + log.Error("更新旧止盈单取消状态失败 err:", err) + } + } + + var err error + for x := 1; x <= 4; x++ { + err = e.OrderPlace(orm, params) + + if err == nil { + break + } + + log.Error("下止盈单失败 第", utility.IntToString(x), "次", " err:", err) + time.Sleep(2 * time.Second * time.Duration(x)) + } + + if err != nil { + log.Error("重新下单止盈失败 err:", err) + + if err1 := orm.Model(&DbModels.LinePreOrder{}).Where("order_sn =?", takeOrder.OrderSn). + Updates(map[string]interface{}{"status": "2", "desc": err.Error()}).Error; err1 != nil { + log.Error("重新下单止盈 修改订单失败 pid:", parentId, " takePrice:", takePrice, " err:", err1) + } + } + + //修改止盈单信息 + if err := orm.Model(&DbModels.LinePreOrder{}).Where("pid =? AND order_type ='5'", parentId). + Updates(map[string]interface{}{"price": takePrice, "rate": parentOrderInfo.ProfitRate}).Error; err != nil { + log.Error("重新下单止盈 修改订单失败 pid:", parentId, " takePrice:", takePrice, " rate:", parentOrderInfo.ProfitRate, " err:", err) + } +} + +/* +判断是否触发 +*/ +func JudgeSpotPrice(trade models.TradeSet) { + preOrderVal, _ := helper.DefaultRedis.GetAllList(rediskey.PreSpotOrderList) + db := GetDBConnection() + + if len(preOrderVal) == 0 { + // log.Debug("没有现货预下单") + return + } + + spotApi := SpotRestApi{} + for _, item := range preOrderVal { + preOrder := dto.PreOrderRedisList{} + if err := sonic.Unmarshal([]byte(item), &preOrder); err != nil { + log.Error("反序列化失败") + continue + } + + if preOrder.Symbol == trade.Coin+trade.Currency { + orderPrice, _ := decimal.NewFromString(preOrder.Price) + tradePrice, _ := decimal.NewFromString(trade.LastPrice) + //买入 + if strings.ToUpper(preOrder.Site) == "BUY" && orderPrice.Cmp(tradePrice) >= 0 && orderPrice.Cmp(decimal.Zero) > 0 && tradePrice.Cmp(decimal.Zero) > 0 { + SpotOrderLock(db, &preOrder, item, spotApi) + } + } + } +} + +// 分布式锁下单 +// v 预下单信息 +// item 预下单源文本 +func SpotOrderLock(db *gorm.DB, v *dto.PreOrderRedisList, item string, spotApi SpotRestApi) { + lock := helper.NewRedisLock(fmt.Sprintf(rediskey.SpotTrigger, v.ApiId, v.Symbol), 20, 5, 100*time.Millisecond) + + if ok, err := lock.AcquireWait(context.Background()); err != nil { + log.Error("获取锁失败", err) + return + } else if ok { + defer lock.Release() + + key := fmt.Sprintf(rediskey.UserHolding, v.ApiId) + symbols, err := helper.DefaultRedis.GetAllList(key) + + if err != nil && err != redis.Nil { + log.Error("获取用户持仓失败", err) + return + } + + preOrder := DbModels.LinePreOrder{} + if err := db.Where("id = ?", v.Id).First(&preOrder).Error; err != nil { + log.Error("获取预下单失败", err) + + if errors.Is(err, gorm.ErrRecordNotFound) { + log.Error("不存在待触发主单", item) + helper.DefaultRedis.LRem(rediskey.PreSpotOrderList, item) + } + + return + } + + //获取代币 是否已有持仓 + coin := utility.ReplaceSuffix(preOrder.Symbol, preOrder.QuoteSymbol, "") + + if utility.ContainsStr(symbols, coin) { + log.Debug("已有持仓") + return + } + + hasrecord, _ := helper.DefaultRedis.IsElementInList(rediskey.PreSpotOrderList, item) + + if !hasrecord { + log.Error("不存在待触发主单", item) + return + } + + price, _ := decimal.NewFromString(v.Price) + num, _ := decimal.NewFromString(preOrder.Num) + params := OrderPlacementService{ + ApiId: v.ApiId, + Symbol: v.Symbol, + Side: v.Site, + Type: preOrder.MainOrderType, + TimeInForce: "GTC", + Price: price, + Quantity: num, + NewClientOrderId: v.OrderSn, + } + preOrderVal, _ := sonic.MarshalString(&v) + + if err := spotApi.OrderPlace(db, params); err != nil { + log.Error("下单失败", v.Symbol, " err:", err) + err := db.Model(&DbModels.LinePreOrder{}).Where("id =? AND status ='0'", preOrder.Id).Updates(map[string]interface{}{"status": "2", "desc": err.Error()}).Error + + if err != nil { + log.Error("下单失败后修改订单失败") + } + + if preOrderVal != "" { + if _, err := helper.DefaultRedis.LRem(rediskey.PreSpotOrderList, preOrderVal); err != nil { + log.Error("删除redis 预下单失败:", err) + } + } + + return + } + + if preOrderVal != "" { + if _, err := helper.DefaultRedis.LRem(rediskey.PreSpotOrderList, preOrderVal); err != nil { + log.Error("删除redis 预下单失败:", err) + } + + // spotPreOrders, _ := helper.DefaultRedis.GetAllList(rediskey.PreSpotOrderList) + // futuresPreOrders, _ := helper.DefaultRedis.GetAllList(rediskey.PreFutOrderList) + // var order dto.PreOrderRedisList + + // for _, item := range spotPreOrders { + // sonic.Unmarshal([]byte(item), &order) + + // if order.QuoteSymbol == "" { + + // } + // } + } + + if err := db.Model(&DbModels.LinePreOrder{}).Where("id =? AND status ='0'", preOrder.Id).Update("status", "1").Error; err != nil { + log.Error("更新预下单状态失败 ordersn:", v.OrderSn, " status:1") + } + + if err := helper.DefaultRedis.RPushList(key, coin); err != nil { + log.Error("写入用户持仓失败", v.Symbol) + } + + return + } else { + log.Error("获取锁失败") + return + } +} + +/* +获取api用户信息 +*/ +func GetApiInfo(apiId int) (DbModels.LineApiUser, error) { + api := DbModels.LineApiUser{} + key := fmt.Sprintf(rediskey.API_USER, apiId) + val, _ := helper.DefaultRedis.GetString(key) + + if val != "" { + if err := sonic.UnmarshalString(val, &api); err == nil { + return api, nil + } + } + + db := GetDBConnection() + + if err := db.Model(&api).Where("id =?", apiId).First(&api).Error; err != nil { + return api, err + } + + val, _ = sonic.MarshalString(&api) + + if val != "" { + helper.DefaultRedis.SetString(key, val) + } + + return api, nil +} + +/* +根据A账户获取B账号信息 +*/ +func GetChildApiInfo(apiId int) (DbModels.LineApiUser, error) { + var api DbModels.LineApiUser + childApiId := 0 + groups := GetApiGroups() + + for _, item := range groups { + if item.ApiUserId == apiId { + childApiId = item.ChildApiUserId + break + } + } + + if childApiId > 0 { + return GetApiInfo(childApiId) + } + + return api, nil +} + +func GetApiGroups() []commondto.ApiGroupDto { + apiGroups := make([]commondto.ApiGroupDto, 0) + apiGroupStr, _ := helper.DefaultRedis.GetAllKeysAndValues(rediskey.ApiGroupAll) + + if len(apiGroupStr) == 0 { + return apiGroups + } + + for _, item := range apiGroupStr { + apiGroup := commondto.ApiGroupDto{} + if err := sonic.UnmarshalString(item, &apiGroup); err != nil { + log.Error("groups 序列化失败", err) + continue + } + apiGroups = append(apiGroups, apiGroup) + } + + return apiGroups +} + +// GetSpotSymbolLastPrice 获取现货交易对最新价格 +func (e SpotRestApi) GetSpotSymbolLastPrice(targetSymbol string) (lastPrice decimal.Decimal) { + tickerSymbol := helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val() + tickerSymbolMaps := make([]dto.Ticker, 0) + sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps) + //key := fmt.Sprintf("%s:%s", global.TICKER_SPOT, targetSymbol) + //tradeSet, _ := helper.GetObjString[models.TradeSet](helper.DefaultRedis, key) + for _, symbolMap := range tickerSymbolMaps { + if symbolMap.Symbol == strings.ToUpper(targetSymbol) { + lastPrice = utility.StringToDecimal(symbolMap.Price) + } + } + return lastPrice +} diff --git a/services/binanceservice/binanceservice_test.go b/services/binanceservice/binanceservice_test.go new file mode 100644 index 0000000..dad8d14 --- /dev/null +++ b/services/binanceservice/binanceservice_test.go @@ -0,0 +1,205 @@ +package binanceservice + +import ( + "fmt" + "github.com/bytedance/sonic" + "go-admin/app/admin/models" + "go-admin/common/helper" + "go-admin/pkg/utility" + "go-admin/pkg/utility/snowflakehelper" + "testing" + + "github.com/shopspring/decimal" + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +func TestCancelFutClosePosition(t *testing.T) { + dsn := "root:root@tcp(192.168.123.216:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" + db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + var apiUserInfo models.LineApiUser + db.Model(&models.LineApiUser{}).Where("id = 21").Find(&apiUserInfo) + err := CancelFutClosePosition(apiUserInfo, "ADAUSDT", "BUY", "SHORT") + if err != nil { + t.Log("err:", err) + } + fmt.Println("成功") +} + +func TestDecimal(t *testing.T) { + + fromString, err := decimal.NewFromString("") + if err != nil { + fmt.Println("err:", err) + } + fmt.Println(fromString) +} + +func TestPositionV3(t *testing.T) { + api := FutRestApi{} + dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" + db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + var apiUserInfo models.LineApiUser + db.Model(&models.LineApiUser{}).Where("id = 21").Find(&apiUserInfo) + err := CancelFutClosePosition(apiUserInfo, "ADAUSDT", "BUY", "SHORT") + if err != nil { + t.Log("err:", err) + } + + v3, err := api.GetPositionV3(&apiUserInfo, "DOGEUSDT") + if err != nil { + t.Log("err:", err) + } + fmt.Println(v3) +} + +func TestInsertLog(t *testing.T) { + dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" + db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + //初始redis 链接 + helper.InitDefaultRedis("192.168.1.12:6379", "", 0) + helper.InitLockRedisConn("192.168.1.12:6379", "", "0") + + InsertProfitLogs(db, "367452130811838464", decimal.NewFromInt(20), decimal.NewFromFloat(0.34078000)) +} + +func TestCancelSpotOrder(t *testing.T) { + //dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" + //db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + //var apiUserInfo models.LineApiUser + //db.Model(&models.LineApiUser{}).Where("id = 21").Find(&apiUserInfo) + //api := SpotRestApi{} + //dto.CancelOpenOrderReq{ + // ApiId: 21, + // Symbol: "ADAUSDT", + // OrderSn: utility.Int64ToString(snowflakehelper.GetOrderId()), + // OrderType: 0, + //} + //api.CancelOpenOrders() +} + +func TestCancelAllFutOrder(t *testing.T) { + dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" + db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + var apiUserInfo models.LineApiUser + db.Model(&models.LineApiUser{}).Where("id = 21").Find(&apiUserInfo) + api := FutRestApi{} + api.CancelAllFutOrder(apiUserInfo, "TRUMPUSDT") +} + +//func TestName(t *testing.T) { +// api := FutRestApi{} +// dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" +// db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) +// var apiUserInfo models.LineApiUser +// db.Model(&models.LineApiUser{}).Where("id = 21").Find(&apiUserInfo) +// api.CancelBatchFutOrder(apiUserInfo, "ADAUSDT",[]{""}) +//} + +func TestFutOrderPalce(t *testing.T) { + api := FutRestApi{} + dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" + db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + api.OrderPlace(db, FutOrderPlace{ + ApiId: 21, + Symbol: "ADAUSDT", + Side: "SELL", + Quantity: decimal.NewFromFloat(7), + Price: decimal.NewFromFloat(0.9764), + SideType: "LIMIT", + OpenOrder: 0, + Profit: decimal.Zero, + StopPrice: decimal.Zero, + OrderType: "LIMIT", + NewClientOrderId: "367580922570080256", + }) +} + +func TestCancelOpenOrderByOrderSn(t *testing.T) { + api := SpotRestApi{} + dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" + db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + var apiUserInfo models.LineApiUser + db.Model(&models.LineApiUser{}).Where("id = ?", 10).Find(&apiUserInfo) + err := api.CancelOpenOrderByOrderSn(apiUserInfo, "DOGEUSDT", "367836524202426368") + if err != nil { + t.Log("err:", err) + } else { + fmt.Println("成功") + } +} + +func TestCancelOpenOrderBySymbol(t *testing.T) { + // api := SpotRestApi{} + dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" + db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + var apiUserInfo models.LineApiUser + db.Model(&models.LineApiUser{}).Where("id = ?", 10).Find(&apiUserInfo) + err := CancelSpotOrder("ADAUSDT", &apiUserInfo, "SELL") + if err != nil { + t.Log("err:", err) + } else { + fmt.Println("成功") + } +} + +func TestDelRedisKeys(t *testing.T) { + //初始redis 链接 + helper.InitDefaultRedis("192.168.1.12:6379", "", 0) + helper.InitLockRedisConn("192.168.1.12:6379", "", "0") + prefixs := []string{ + "api_user_hold", + "spot_trigger_lock", + "fut_trigger_lock", + "fut_trigger_stop_lock", + "spot_trigger_stop_lock", + "spot_addposition_trigger", + "fut_addposition_trigger", + "spot_hedge_close_position", + "futures_hedge_close_position", + "spot_callback", + "fut_callback", + "holde_a", + "holde_b", + } + helper.DefaultRedis.DeleteKeysByPrefix(prefixs...) +} + +func TestOpenOrders(t *testing.T) { + dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" + db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + var apiUserInfo models.LineApiUser + db.Model(&models.LineApiUser{}).Where("id = ?", 21).Find(&apiUserInfo) + client := GetClient(&apiUserInfo) + auth, _, err := client.SendSpotAuth("/api/v3/order", "GET", map[string]string{"symbol": "ADAUSDT", "orderId": "6001232151"}) + if err != nil { + fmt.Println("err:", err) + } + m := make(map[string]interface{}, 0) + sonic.Unmarshal(auth, &m) + fmt.Println("m:", m) +} + +func TestClosePosition(t *testing.T) { + endpoint := "/fapi/v1/order" + params := map[string]string{ + "symbol": "ADAUSDT", + "type": "LIMIT", + "quantity": "5", + "newClientOrderId": utility.Int64ToString(snowflakehelper.GetOrderId()), + "positionSide": "SHORT", + } + params["side"] = "BUY" + params["price"] = "0.98" + params["timeInForce"] = "GTC" + dsn := "root:root@tcp(192.168.1.12:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" + db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + var apiUserInfo models.LineApiUser + db.Model(&models.LineApiUser{}).Where("id = ?", 21).Find(&apiUserInfo) + + client := GetClient(&apiUserInfo) + resp, _, err := client.SendFuturesRequestAuth(endpoint, "POST", params) + fmt.Println("resp:", string(resp)) + fmt.Println("err:", err) + +} diff --git a/services/binanceservice/commonservice.go b/services/binanceservice/commonservice.go new file mode 100644 index 0000000..aa048f9 --- /dev/null +++ b/services/binanceservice/commonservice.go @@ -0,0 +1,331 @@ +package binanceservice + +import ( + "errors" + "fmt" + DbModels "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/const/rediskey" + "go-admin/common/global" + "go-admin/common/helper" + "go-admin/models" + "go-admin/pkg/utility" + "strings" + "time" + + "github.com/bytedance/sonic" + "github.com/go-admin-team/go-admin-core/logger" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/shopspring/decimal" + "gorm.io/gorm" +) + +type AddPosition struct { + Db *gorm.DB +} + +// 获取缓存交易对 +// symbolType 0-现货 1-合约 +func GetTradeSet(symbol string, symbolType int) (models.TradeSet, error) { + result := models.TradeSet{} + val := "" + + switch symbolType { + case 0: + key := fmt.Sprintf(global.TICKER_SPOT, global.EXCHANGE_BINANCE, symbol) + val, _ = helper.DefaultRedis.GetString(key) + case 1: + key := fmt.Sprintf(global.TICKER_FUTURES, global.EXCHANGE_BINANCE, symbol) + val, _ = helper.DefaultRedis.GetString(key) + } + + if val != "" { + if err := sonic.Unmarshal([]byte(val), &result); err != nil { + return result, err + } + } else { + return result, errors.New("未找到交易对信息") + } + + return result, nil +} + +func GetRisk(futApi *FutRestApi, apiInfo *DbModels.LineApiUser, symbol string) []PositionRisk { + for x := 0; x < 5; x++ { + risks, _ := futApi.GetPositionV3(apiInfo, symbol) + if len(risks) > 0 { + return risks + } + time.Sleep(time.Millisecond * 200) + } + + return []PositionRisk{} +} + +// CancelFutClosePosition 撤销交易对指定方向的平仓单 +// apiUserInfo 账户信息 +// symbol 交易对 +// side 购买反向 +// positionSide 仓位方向 +// side=BUY positionSide=SHORT 就是撤销平空委托 +// side=BUY&positionSide=LONG是开多, +// side=SELL&positionSide=LONG是平多, +// side=SELL&positionSide=SHORT是开空, +// side=BUY&positionSide=SHORT是平空。 +func CancelFutClosePosition(apiUserInfo DbModels.LineApiUser, symbol string, side string, positionSide string) error { + //查询当前用户的委托订单 + client := GetClient(&apiUserInfo) + resp, _, err := client.SendFuturesRequestAuth("/fapi/v1/openOrders", "GET", map[string]string{ + "symbol": symbol, + "recvWindow": "5000", + }) + if err != nil { + logger.Error("撤销平仓单时查询委托失败:", err) + return err + } + var openOrders []OpenOrders + sonic.Unmarshal(resp, &openOrders) + orderIdList := make([]int, 0) + for _, order := range openOrders { + if order.Side == side && order.PositionSide == positionSide { + orderIdList = append(orderIdList, order.OrderId) + } + } + // 每次取 10 个元素 + batchSize := 10 + for i := 0; i < len(orderIdList); i += batchSize { + end := i + batchSize + if end > len(orderIdList) { + end = len(orderIdList) // 避免越界 + } + // 取出当前批次的元素 + batch := orderIdList[i:end] + marshal, _ := sonic.Marshal(&batch) + _, _, err = client.SendFuturesRequestAuth("/fapi/v1/batchOrders", "DELETE", map[string]string{ + "symbol": symbol, + "orderIdList": string(marshal), + "recvWindow": "5000", + }) + if err != nil { + return err + } + } + return err +} + +// CancelSpotClosePosition 取消现货平仓单(等同于撤销卖单) +// apiUserInfo: Api用户信息 +// symbol: 交易对 +func CancelSpotClosePosition(apiUserInfo *DbModels.LineApiUser, symbol string) error { + + return CancelSpotOrder(symbol, apiUserInfo, "SELL") +} + +// 取消现货订单 +func CancelSpotOrder(symbol string, apiUserInfo *DbModels.LineApiUser, side string) error { + searchEndpoint := "/api/v3/openOrders" + cencelEndpoint := "/api/v3/order" + searchParams := map[string]interface{}{ + "symbol": symbol, + } + client := GetClient(apiUserInfo) + resp, _, err := client.SendSpotAuth(searchEndpoint, "GET", searchParams) + db := GetDBConnection() + + if err != nil { + if len(resp) > 0 { + + } else { + logger.Error("查询现货当前下单失败:", err) + } + } + + var openOrders []map[string]interface{} + err = sonic.Unmarshal(resp, &openOrders) + if err != nil { + return err + } + + for _, order := range openOrders { + if orderSymbol, ok := order["symbol"].(string); ok && orderSymbol == symbol { + if orderSide, ok := order["side"].(string); ok && orderSide == side { + orderId, ok := order["orderId"].(float64) + + if !ok { + continue + } + + orderSn, _ := order["clientOrderId"].(string) + params := map[string]string{ + "symbol": orderSymbol, + "orderId": utility.Float64CutString(orderId, 0), + // "cancelRestrictions": "ONLY_NEW", + } + _, _, err = client.SendSpotAuth(cencelEndpoint, "DELETE", params) + + if err != nil { + logger.Error("撤销指定现货平仓单失败 ordersn:", orderSn, " orderId:", orderId, " err:", err) + } else { + if err := db.Model(&DbModels.LinePreOrder{}).Where("order_sn = ? and status !='9'", orderSn).Update("status", "4").Error; err != nil { + log.Error("修改止盈单撤销状态失败:", err) + } + } + } + } + return nil + } + return nil +} + +// GetTargetSymbol 获取目标交易对信息 +func (e *AddPosition) GetTargetSymbol(symbol string, symbolType int) (string, bool, DbModels.LineSymbol, error) { + var targetSymbol string + var notUsdt bool + var symbolInfo DbModels.LineSymbol + + // 处理非 USDT 交易对 + if !strings.HasSuffix(symbol, "USDT") { + notUsdt = true + if err := e.Db.Model(&DbModels.LineSymbol{}).Where("symbol = ? AND type = ?", symbol, utility.IntToString(symbolType)).Find(&symbolInfo).Error; err != nil { + return "", false, DbModels.LineSymbol{}, err + } + if symbolInfo.Id <= 0 { + return "", false, DbModels.LineSymbol{}, fmt.Errorf("未找到交易对信息") + } + targetSymbol = symbolInfo.BaseAsset + "USDT" + } else { + targetSymbol = symbol + } + + return targetSymbol, notUsdt, symbolInfo, nil +} + +func (e *AddPosition) GetOrderInfo(req dto.ManuallyCover, symbol, orderType, site, status string) (DbModels.LinePreOrder, error) { + var orderInfo DbModels.LinePreOrder + if err := e.Db.Model(DbModels.LinePreOrder{}).Where("api_id = ? AND symbol = ? AND order_type = ? AND site = ? AND status = ?", req.ApiId, symbol, orderType, site, status).Find(&orderInfo).Error; err != nil { + return DbModels.LinePreOrder{}, err + } + if orderInfo.Id <= 0 { + return DbModels.LinePreOrder{}, fmt.Errorf("未找到主仓信息") + } + return orderInfo, nil +} + +func (e *AddPosition) GetFutOrderInfo(req dto.ManuallyCover, symbol, orderType, status string) (DbModels.LinePreOrder, error) { + var orderInfo DbModels.LinePreOrder + if err := e.Db.Model(DbModels.LinePreOrder{}).Where("api_id = ? AND symbol = ? AND order_type = ? AND status = ? AND cover_type = 2", req.ApiId, symbol, orderType, status).Find(&orderInfo).Error; err != nil { + return DbModels.LinePreOrder{}, err + } + if orderInfo.Id <= 0 { + return DbModels.LinePreOrder{}, fmt.Errorf("未找到主仓信息") + } + return orderInfo, nil +} + +// GetFutSpotOrderInfo 获取合约对现货的订单信息 +func (e *AddPosition) GetFutSpotOrderInfo(req dto.ManuallyCover, symbol, orderType, status string) (DbModels.LinePreOrder, error) { + var orderInfo DbModels.LinePreOrder + if err := e.Db.Model(DbModels.LinePreOrder{}).Where("api_id = ? AND symbol = ? AND order_type = ? AND status = ? AND cover_type = 3", req.ApiId, symbol, orderType, status).Find(&orderInfo).Error; err != nil { + return DbModels.LinePreOrder{}, err + } + if orderInfo.Id <= 0 { + return DbModels.LinePreOrder{}, fmt.Errorf("未找到主仓信息") + } + return orderInfo, nil +} + +// CalculateAmount 计算加仓数量 +func (e *AddPosition) CalculateAmount(req dto.ManuallyCover, totalNum, lastPrice decimal.Decimal, amountDigit int, notUsdt bool, symbolInfo DbModels.LineSymbol) (decimal.Decimal, error) { + var amt decimal.Decimal + if req.CoverType == 1 { + decimalValue := utility.StringToDecimal(req.Value).Div(decimal.NewFromInt(100)) + amt = totalNum.Mul(decimalValue) + } else { + decimalValue := utility.StringToDecimal(req.Value) + if notUsdt { + tickerSymbolMaps := make([]dto.Ticker, 0) + tickerSymbol := helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val() + if err := sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps); err != nil { + return decimal.Zero, err + } + + var tickerPrice decimal.Decimal + for _, symbolMap := range tickerSymbolMaps { + if symbolMap.Symbol == strings.ToUpper(symbolInfo.BaseAsset+"USDT") { + tickerPrice, _ = decimal.NewFromString(symbolMap.Price) + break + } + } + + for _, symbolMap := range tickerSymbolMaps { + if symbolMap.Symbol == strings.ToUpper(symbolInfo.QuoteAsset+"USDT") { + uTickerPrice, _ := decimal.NewFromString(symbolMap.Price) + div := tickerPrice.Div(decimal.NewFromInt(1).Div(uTickerPrice)) + amt = decimalValue.Div(div) + break + } + } + } else { + amt = decimalValue.Div(lastPrice) + } + } + return amt.Truncate(int32(amountDigit)), nil +} + +// 主单平仓删除缓存 +// mainOrderId 主单id +// coverType 1现货->合约 2->合约->合约 3合约->现货 +func MainClosePositionClearCache(mainOrderId int, coverType int) { + if coverType == 1 { + spotStopArray, _ := helper.DefaultRedis.GetAllList(rediskey.SpotStopLossList) + spotAddpositionArray, _ := helper.DefaultRedis.GetAllList(rediskey.SpotAddPositionList) + var position AddPositionList + var stop dto.StopLossRedisList + + for _, item := range spotAddpositionArray { + if err := sonic.Unmarshal([]byte(item), &position); err != nil { + log.Error("MainClosePositionClearCache Unmarshal err:", err) + } + + if position.Pid == mainOrderId { + helper.DefaultRedis.LRem(rediskey.SpotAddPositionList, item) + } + } + + for _, item := range spotStopArray { + if err := sonic.Unmarshal([]byte(item), &stop); err != nil { + log.Error("MainClosePositionClearCache Unmarshal err:", err) + } + + if stop.PId == mainOrderId { + helper.DefaultRedis.LRem(rediskey.SpotStopLossList, item) + } + } + + } else { + futAddpositionArray, _ := helper.DefaultRedis.GetAllList(rediskey.FuturesAddPositionList) + futStopArray, _ := helper.DefaultRedis.GetAllList(rediskey.FuturesStopLossList) + var position AddPositionList + var stop dto.StopLossRedisList + + for _, item := range futAddpositionArray { + if err := sonic.Unmarshal([]byte(item), &position); err != nil { + log.Error("MainClosePositionClearCache Unmarshal err:", err) + } + + if position.Pid == mainOrderId { + helper.DefaultRedis.LRem(rediskey.FuturesAddPositionList, item) + } + } + + for _, item := range futStopArray { + if err := sonic.Unmarshal([]byte(item), &stop); err != nil { + log.Error("MainClosePositionClearCache Unmarshal err:", err) + } + + if stop.PId == mainOrderId { + helper.DefaultRedis.LRem(rediskey.FuturesStopLossList, item) + } + } + } +} diff --git a/services/binanceservice/futuresbinancerest.go b/services/binanceservice/futuresbinancerest.go new file mode 100644 index 0000000..6e2e792 --- /dev/null +++ b/services/binanceservice/futuresbinancerest.go @@ -0,0 +1,1134 @@ +package binanceservice + +import ( + "context" + "errors" + "fmt" + DbModels "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/const/rediskey" + "go-admin/common/global" + "go-admin/common/helper" + "go-admin/models" + "go-admin/models/futuresdto" + "go-admin/pkg/httputils" + "go-admin/pkg/utility" + "go-admin/pkg/utility/snowflakehelper" + "strconv" + "strings" + "time" + + "github.com/jinzhu/copier" + "github.com/shopspring/decimal" + "gorm.io/gorm" + + "github.com/bytedance/sonic" + "github.com/go-redis/redis/v8" + + "github.com/go-admin-team/go-admin-core/logger" + log "github.com/go-admin-team/go-admin-core/logger" +) + +const ( + futureApi = "https://fapi.binance.com" +) + +type FutRestApi struct { +} + +var FutErrorMaps = map[float64]string{ + -2021: "订单已拒绝。请调整触发价并重新下订单。 对于买入/做多,止盈订单触发价应低于市场价,止损订单的触发价应高于市场价。卖出/做空则与之相反", + -4164: "少于最小下单金额", + -4061: "持仓方向需要设置为单向持仓。", + -2019: "保证金不足", + -1111: "金额设置错误。精度错误", + -1021: "请求的时间戳在recvWindow之外", + -1106: "发送了不需要的参数。", + -1109: "非有效账户", + -1110: "交易对不正确", + -1112: "交易对没有挂单", + -1114: "发送的TimeInForce参数不需要。", + -1115: "无效的timeInForce", + -1116: "无效订单类型。", + -1117: "无效买卖方向。", + -1121: "无效的交易对。", + -2010: "新订单被拒绝", + -2011: "取消订单被拒绝", + -2012: "批量取消失败", + -2013: "订单不存在。", + -2014: "API-key 格式无效。", + -2015: "无效的API密钥,IP或操作权限。", + -2018: "余额不足", + -2020: "无法成交", + -2022: "ReduceOnly订单被拒绝", + -2023: "用户正处于被强平模式", + -2024: "持仓不足", + -2025: "挂单量达到上限", + -4001: "价格小于0", + -4002: "价格超过最大值", + -4003: "数量小于0", + -4004: "数量小于最小值", + -4005: "数量大于最大值", + -4008: "价格精度小于0", + -4009: "最大价格小于最小价格", + -4029: "价格精度小数点位数不正确。", + -4031: "不正确的参数类型。", + -4047: "如果有挂单,仓位模式不能切换。", + -4048: "如果有仓位,仓位模式不能切换。", + -4059: "无需变更仓位方向", + -4117: "stop订单在触发中", +} + +var reduceOnlyRate = 0.95 + +/* +获取资金费率 +*/ +func GetPremiumIndex() ([]futuresdto.FundingInfo, error) { + url := fmt.Sprintf("%s%s", futureApi, "/fapi/v1/premiumIndex") + mapData, err := httputils.NewHttpRequestWithFasthttp("GET", url, "", map[string]string{}) + + if err != nil { + return nil, err + } + + if len(mapData) == 0 { + return nil, errors.New("获取交易对失败,或数量为空") + } + + var res []futuresdto.FundingInfo + err = sonic.Unmarshal([]byte(mapData), &res) + + if err != nil { + return nil, err + } + + for _, item := range res { + key := fmt.Sprintf("%s:%s", global.FUNDING_INFO_FUTURES, item.Symbol) + + if item.NextFundingTime == 0 { + helper.DefaultRedis.DeleteString(key) + continue + } + + val, err := sonic.Marshal(item) + + if err != nil { + log.Error("序列化失败:", item.Symbol, err) + continue + } + + err = helper.DefaultRedis.SetString(key, string(val)) + + if err != nil { + log.Error("保存资金费率失败:", item.Symbol, err) + continue + } + } + + return nil, nil +} + +/* +获取指定交易对资金费率 + + - @symbol 交易对名称 + - @data 结果 +*/ +func GetFundingInfoBySymbol(symbol string, data *futuresdto.FundingInfo) error { + key := fmt.Sprintf("%s:%s", global.FUNDING_INFO_FUTURES, strings.ToUpper(symbol)) + resData, err := helper.DefaultRedis.GetString(key) + + if err != nil { + return err + } + + if resData == "" { + shouldReturn, err := GetAndReloadSymbol(symbol, data) + if shouldReturn { + return err + } + } + + err = sonic.Unmarshal([]byte(resData), data) + + if err != nil { + return err + } + + if data.NextFundingTime <= time.Now().Unix() { + shouldReturn, err := GetAndReloadSymbol(symbol, data) + if shouldReturn { + return err + } + } + + return nil +} + +// 获取并更新费率 +func GetAndReloadSymbol(symbol string, data *futuresdto.FundingInfo) (bool, error) { + datas, err := GetPremiumIndex() + + if err != nil { + return true, err + } + + for _, item := range datas { + if item.Symbol == strings.ToUpper(symbol) { + *data = item + + return true, nil + } + } + return false, nil +} + +// 获取合约交易对信息 +func GetSymbols(data *futuresdto.FutExchangeInfo) error { + url := fmt.Sprintf("%s%s", futureApi, "/fapi/v1/exchangeInfo") + mapData, err := httputils.NewHttpRequestWithFasthttp("GET", url, "", map[string]string{}) + + if err != nil { + return err + } + + if len(mapData) == 0 { + return errors.New("获取合约交易对失败,或数量为空") + } + + err = sonic.Unmarshal(mapData, data) + + if err != nil { + return err + } + return nil +} + +// 获取并更新交易对 +func GetAndReloadSymbols(data *map[string]models.TradeSet) error { + var info futuresdto.FutExchangeInfo + err := GetSymbols(&info) + + if err != nil { + return err + } + + maps := make(map[string]string, 0) + if len(info.Symbols) > 0 { + for _, item := range info.Symbols { + if item.Status == "TRADING" && strings.HasSuffix(item.Symbol, "USDT") { + tradeSet := models.TradeSet{} + tradeSet.Coin = item.BaseAsset + tradeSet.Currency = item.QuoteAsset + for _, filter := range item.Filters { + switch filter.FilterType { + case "PRICE_FILTER": + tradeSet.PriceDigit = utility.GetPrecision(*filter.TickSize) + tradeSet.MinBuyVal = utility.StringAsFloat(*filter.MinPrice) + case "LOT_SIZE": + tradeSet.AmountDigit = utility.GetPrecision(*filter.StepSize) + case "MIN_NOTIONAL": + tradeSet.MinNotional = *filter.Notional + case "MAX_NOTIONAL": + tradeSet.MaxNotional = *filter.Notional + } + } + + (*data)[item.Symbol] = tradeSet + + key := fmt.Sprintf("%s:%s", global.TICKER_FUTURES, item.Symbol) + val, err := sonic.Marshal(tradeSet) + + if err != nil { + log.Error("合约交易对序列化失败", err) + } else { + maps[key] = string(val) + } + } + } + } + + if len(maps) > 0 { + if err = helper.DefaultRedis.BatchSet(&maps); err != nil { + return err + } + } + + return nil +} + +func (e FutRestApi) Ticker() { + url := fmt.Sprintf("%s%s", futureApi, "/fapi/v1/ticker/price") + mapData, err := httputils.NewHttpRequestWithFasthttp("GET", url, "", map[string]string{}) + if err != nil { + log.Error("获取合约交易对行情失败:err:", err) + } + + if len(mapData) == 0 { + log.Error("获取合约交易对行情失败,或数量为空") + } + helper.DefaultRedis.SetString(rediskey.FutSymbolTicker, string(mapData)) +} + +// 获取合约行情 +func GetTicker24h() ([]futuresdto.FutureTicker24h, error) { + url := fmt.Sprintf("%s%s", futureApi, "/fapi/v1/ticker/24hr") + + mapData, err := httputils.NewHttpRequestWithFasthttp("GET", url, "", map[string]string{}) + + if err != nil { + return nil, err + } + + if len(mapData) == 0 { + return nil, errors.New("获取合约交易对24h行情失败,或数量为空") + } + + var data []futuresdto.FutureTicker24h + err = sonic.Unmarshal(mapData, &data) + + if err != nil { + return nil, err + } + + return data, err +} + +// 初始化交易对行情 +func InitSymbolsTicker24h(maps *map[string]models.TradeSet) (deletes []string, err error) { + tickers, err := GetTicker24h() + + if err != nil { + return []string{}, err + } + deleteSymbol := make([]string, 0) + caches := make(map[string]string, 0) + priceChange := make([]*redis.Z, 0) + + for _, item := range tickers { + symbol, exits := (*maps)[item.Symbol] + + if !exits { + continue + } + + symbol.OpenPrice = utility.StringAsFloat(item.OpenPrice) + symbol.PriceChange = utility.StringAsFloat(item.PriceChangePercent) + symbol.LowPrice = item.LowPrice + symbol.HighPrice = item.HighPrice + symbol.Volume = item.Volume + symbol.QuoteVolume = item.QuoteVolume + symbol.LastPrice = item.LastPrice + + key := fmt.Sprintf("%s:%s", global.TICKER_FUTURES, item.Symbol) + if !strings.HasSuffix(item.Symbol, symbol.Currency) || item.Count <= 0 || utility.StringToFloat64(item.QuoteVolume) <= 0 { + helper.DefaultRedis.DeleteString(key) + deleteSymbol = append(deleteSymbol, item.Symbol) + continue + } + + val, err := sonic.Marshal(symbol) + + if err != nil { + log.Error("设置行情序列化报错", err) + } + + tcKey := fmt.Sprintf("%s:%s", global.TICKER_FUTURES, item.Symbol) + caches[tcKey] = string(val) + priceChange = append(priceChange, &redis.Z{ + Score: symbol.PriceChange, + Member: symbol.Coin + symbol.Currency, + }) + } + + if len(caches) > 0 { + err = helper.DefaultRedis.BatchSet(&caches) + + if err != nil { + log.Error("批量行情保存失败", err) + + return deleteSymbol, err + } + + err = helper.DefaultRedis.BatchSortSet(global.UFUTURES_PRICE_CHANGE, priceChange) + + if err != nil { + log.Error("批量涨跌幅保存失败", err) + + return deleteSymbol, err + } + } + + return deleteSymbol, nil +} + +// 获取合约 24h行情 +func GetTicker(coin string, data *futuresdto.MarketResp) error { + key := fmt.Sprintf("%s:%sUSDT", global.TICKER_FUTURES, coin) + cache, _ := helper.DefaultRedis.GetString(key) + + if cache == "" { + + } else { + var trade models.TradeSet + sonic.Unmarshal([]byte(cache), &trade) + + if trade.Coin != "" { + data.Coin = trade.Coin + data.HighPrice = trade.HighPrice + data.LowPrice = trade.LowPrice + data.NewPrice = trade.LastPrice + data.Ratio = utility.Float64ToString(trade.PriceChange, -1) + data.AmountDigit = trade.AmountDigit + data.DealCoin = trade.Volume + data.DealAmt = trade.QuoteVolume + } + + fiKey := fmt.Sprintf("%s:%sUSDT", global.FUNDING_INFO_FUTURES, coin) + fi, _ := helper.DefaultRedis.GetString(fiKey) + + if fi != "" { + var fundingInfo futuresdto.FundingInfo + sonic.Unmarshal([]byte(fi), &fundingInfo) + + if fundingInfo.NextFundingTime > 0 { + data.FundRate = strconv.FormatFloat(utility.ToFloat64(fundingInfo.LastFundingRate)*100, 'f', -1, 64) + data.MarkPrice = utility.StringFloat64Cut(fundingInfo.MarkPrice, int32(trade.PriceDigit)) + data.IndexPrice = utility.StringFloat64Cut(fundingInfo.IndexPrice, int32(trade.PriceDigit)) + + if fundingInfo.NextFundingTime > 999999999999 { + data.NextFundingSec = int(fundingInfo.NextFundingTime / 1000) + } else { + data.NextFundingSec = int(fundingInfo.NextFundingTime) + } + } + } + } + return nil +} + +// CheckKlineType 检查k线合法性 +func CheckKlineType(kLineType string) bool { + for _, v := range []string{"1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d", "3d", "1w", "1M"} { + if v == kLineType { + return true + } + } + return false +} + +// OrderPlace 合约下单 +func (e FutRestApi) OrderPlace(orm *gorm.DB, params FutOrderPlace) error { + if orm == nil { + return errors.New("数据库实例为空") + } + err2 := params.CheckParams() + if err2 != nil { + return err2 + } + var paramsMaps map[string]string + paramsMaps = map[string]string{ + "symbol": params.Symbol, + "side": params.Side, + "quantity": params.Quantity.String(), + "type": strings.ToUpper(params.OrderType), + "newClientOrderId": params.NewClientOrderId, + } + if strings.ToUpper(params.OrderType) != "MARKET" { //不是市价 + if strings.ToUpper(params.OrderType) == "LIMIT" { + paramsMaps["price"] = params.Price.String() + paramsMaps["timeInForce"] = "GTC" + } + if strings.ToUpper(params.OrderType) == "TAKE_PROFIT_MARKET" { + paramsMaps["timeInForce"] = "GTC" + paramsMaps["stopprice"] = params.Profit.String() + paramsMaps["workingType"] = "MARK_PRICE" + } + + if strings.ToUpper(params.OrderType) == "STOP_MARKET" { + paramsMaps["stopprice"] = params.StopPrice.String() + paramsMaps["workingType"] = "MARK_PRICE" + paramsMaps["timeInForce"] = "GTC" + } + } + if strings.ToUpper(params.OrderType) != "STOP_MARKET" && strings.ToUpper(params.OrderType) != "TAKE_PROFIT_MARKET" { + if strings.ToUpper(params.Side) == "BUY" { + paramsMaps["positionSide"] = "LONG" + } else { + paramsMaps["positionSide"] = "SHORT" + } + } else { + if strings.ToUpper(params.Side) == "BUY" { + paramsMaps["positionSide"] = "SHORT" + } else { + paramsMaps["positionSide"] = "LONG" + } + } + var apiUserInfo DbModels.LineApiUser + err := orm.Model(&DbModels.LineApiUser{}).Where("id = ?", params.ApiId).Find(&apiUserInfo).Error + if err != nil { + return err + } + var client *helper.BinanceClient + + if apiUserInfo.UserPass == "" { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress) + } else { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress) + } + _, statusCode, err := client.SendFuturesRequestAuth("/fapi/v1/order", "POST", paramsMaps) + if err != nil { + var dataMap map[string]interface{} + + if err2 := sonic.Unmarshal([]byte(err.Error()), &dataMap); err2 != nil { + return fmt.Errorf("api_id:%d 交易对:%s statusCode:%v 下单失败:%s", apiUserInfo.Id, params.Symbol, statusCode, err.Error()) + } + code, ok := dataMap["code"] + if ok { + paramsVal, _ := sonic.MarshalString(¶msMaps) + log.Error("下单失败 参数:", paramsVal) + errContent := FutErrorMaps[code.(float64)] + + if errContent == "" { + errContent = err.Error() + } + + return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%s", apiUserInfo.Id, params.Symbol, errContent) + } + if strings.Contains(err.Error(), "Margin is insufficient.") { + return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%s", apiUserInfo.Id, params.Symbol, FutErrorMaps[-2019]) + } + return fmt.Errorf("api_id:%d 交易对:%s 下单失败:%s", apiUserInfo.Id, params.Symbol, err.Error()) + } + return nil +} + +// ClosePositionB 平仓B对应的交易对 +// bApiUserInfo B 账户api-user信息 +// symbol 需要平仓的交易对 +// closeType 平仓模式 ALL = 全平 reduceOnly = 只减仓 +// func (e FutRestApi) ClosePositionB(orm *gorm.DB, bApiUserInfo *DbModels.LineApiUser, symbol string, closeType string) error { +// if bApiUserInfo == nil { +// return errors.New("缺失平仓账户信息") +// } +// if symbol == "" { +// return errors.New("缺失平仓交易对信息") +// } +// risks, err := e.GetPositionV3(bApiUserInfo, symbol) +// if err != nil { +// return err +// } + +// for _, risk := range risks { +// if risk.Symbol == strings.ToUpper(symbol) { +// //持仓数量 +// positionAmt, _ := decimal.NewFromString(risk.PositionAmt) +// side := "BUY" +// if positionAmt.GreaterThan(decimal.Zero) { +// side = "SELL" +// } +// var closeAmt decimal.Decimal +// if strings.ToUpper(closeType) == "ALL" { //全部平仓 +// closeAmt = positionAmt +// } else { +// //如果是只减仓的话 数量=仓位数量*比例 +// closeAmt = positionAmt.Mul(decimal.NewFromFloat(reduceOnlyRate)) +// } + +// err = e.ClosePosition(symbol, closeAmt, side, *bApiUserInfo, "MARKET", "", decimal.Zero) +// if err != nil { +// return err +// } +// orm.Model(&DbModels.LinePreOrder{}).Where("api_id = ? AND symbol = ?", bApiUserInfo.Id, symbol).Updates(map[string]interface{}{ +// "status": "6", +// }) +// } +// } +// return nil +// } + +// 获取合约 持仓价格、数量 +// symbol:交易对 +// side:方向 +// holdeData:持仓数据 +func (e FutRestApi) GetHoldeData(apiInfo *DbModels.LineApiUser, symbol, side string, holdeData *HoldeData) error { + var holdes []PositionRisk + var err error + + for x := 0; x < 3; x++ { + holdes, err = getSymbolHolde(e, apiInfo, symbol, side, holdeData) + if err != nil { + return err + } + + if len(holdes) == 0 { + logger.Error("获取持仓信息,未查询到持仓信息") + time.Sleep(time.Second * 1) + } else { + break + } + } + + //side=SELL&positionSide=LONG是平多, + //side=SELL&positionSide=SHORT是开空, + for _, item := range holdes { + positionAmount, _ := decimal.NewFromString(item.PositionAmt) + if side == "BUY" && (item.PositionSide == "BOTH" || item.PositionSide == "LONG") { //多 + holdeData.AveragePrice, _ = decimal.NewFromString(item.EntryPrice) + holdeData.TotalQuantity = positionAmount.Abs() + } else if side == "SELL" && (item.PositionSide == "BOTH" || item.PositionSide == "SHORT") { //空 + holdeData.AveragePrice, _ = decimal.NewFromString(item.EntryPrice) + holdeData.TotalQuantity = positionAmount.Abs() + } + } + + if holdeData.AveragePrice.Cmp(decimal.Zero) == 0 { + holdesVal, _ := sonic.MarshalString(&holdes) + log.Error("均价错误 symbol:", symbol, " 数据:", holdesVal) + } + + return nil +} + +// 获取代币持仓信息 +func getSymbolHolde(e FutRestApi, apiInfo *DbModels.LineApiUser, symbol string, side string, holdeData *HoldeData) ([]PositionRisk, error) { + holdes, err := e.GetPositionV3(apiInfo, symbol) + + if err != nil { + log.Error("订单回调-获取合约持仓信息失败:", err) + + if strings.Contains(err.Error(), " EOF") || strings.Contains(err.Error(), "无效的API密钥,IP或操作权限") { + return nil, e.GetHoldeData(apiInfo, symbol, side, holdeData) + } else { + return nil, err + } + } + return holdes, nil +} + +func (e FutRestApi) GetPositionV3(apiUserInfo *DbModels.LineApiUser, symbol string) ([]PositionRisk, error) { + var client *helper.BinanceClient + if apiUserInfo.UserPass == "" { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress) + } else { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress) + } + params := map[string]string{ + "symbol": symbol, + "recvWindow": "5000", + } + + resp, _, err := client.SendFuturesRequestAuth("/fapi/v3/positionRisk", "GET", params) + if err != nil { + var dataMap map[string]interface{} + if err2 := sonic.Unmarshal([]byte(err.Error()), &dataMap); err2 != nil { + return []PositionRisk{}, fmt.Errorf("api_id:%d 交易对:%s 获取仓位:%s", apiUserInfo.Id, symbol, err.Error()) + } + code, ok := dataMap["code"] + if ok { + errContent := FutErrorMaps[code.(float64)] + + if errContent == "" { + errContent = err.Error() + } + + return []PositionRisk{}, fmt.Errorf("api_id:%d 交易对:%s 获取仓位:%s", apiUserInfo.Id, symbol, errContent) + } + } + risks := make([]PositionRisk, 0) + err = sonic.Unmarshal(resp, &risks) + if err != nil { + return []PositionRisk{}, fmt.Errorf("api_id:%d 交易对:%s 解析用户持仓信息失败:%s", apiUserInfo.Id, symbol, err.Error()) + } + return risks, nil +} + +// ClosePosition 合约平仓 +// symbol 交易对 +// orderSn 系统订单号 +// quantity 平仓数量 +// side 仓位方向 做多==BUY 做空 == SELL +// apiUserInfo 用户信息 +// orderType 平仓是限价平 还是市价平 MARKET = 市价 LIMIT = 限价 +// rate 限价平的价格比例 没有除100 的百分比 +func (e FutRestApi) ClosePosition(symbol string, orderSn string, quantity decimal.Decimal, side string, positionSide string, + apiUserInfo DbModels.LineApiUser, orderType string, rate string, price decimal.Decimal) error { + endpoint := "/fapi/v1/order" + params := map[string]string{ + "symbol": symbol, + "side": side, + "positionSide": positionSide, + "type": orderType, + "quantity": quantity.String(), + "newClientOrderId": orderSn, + } + + if orderType == "LIMIT" { + key := fmt.Sprintf("%s:%s", global.TICKER_FUTURES, symbol) + tradeSet, _ := helper.GetObjString[models.TradeSet](helper.DefaultRedis, key) + rateFloat, _ := decimal.NewFromString(rate) + if rateFloat.GreaterThanOrEqual(decimal.Zero) { + if side == "SELL" { //仓位是空 平空的话 + price = price.Mul(decimal.NewFromInt(1).Add(rateFloat)).Truncate(int32(tradeSet.PriceDigit)) + } else { + price = price.Mul(decimal.NewFromInt(1).Sub(rateFloat)).Truncate(int32(tradeSet.PriceDigit)) + } + params["price"] = price.String() + } + } + + if orderType == "LIMIT" { + params["timeInForce"] = "GTC" + } + + var client *helper.BinanceClient + if apiUserInfo.UserPass == "" { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress) + } else { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress) + } + + resp, _, err := client.SendFuturesRequestAuth(endpoint, "POST", params) + if err != nil { + var dataMap map[string]interface{} + + if err2 := sonic.Unmarshal([]byte(err.Error()), &dataMap); err2 != nil { + return fmt.Errorf("api_id:%d 交易对:%s 平仓出错:%s", apiUserInfo.Id, symbol, err.Error()) + } + code, ok := dataMap["code"] + if ok { + errContent := FutErrorMaps[code.(float64)] + + if errContent == "" { + errContent = err.Error() + } + + return fmt.Errorf("api_id:%d 交易对:%s 平仓出错:%s", apiUserInfo.Id, symbol, errContent) + } + } + var orderResp FutOrderResp + err = sonic.Unmarshal(resp, &orderResp) + if err != nil { + return fmt.Errorf("api_id:%d 交易对:%s 平仓出错:%s", apiUserInfo.Id, symbol, err.Error()) + } + if orderResp.Symbol == "" { + return fmt.Errorf("api_id:%d 交易对:%s 平仓出错:未找到订单信息", apiUserInfo.Id, symbol) + } + return nil +} + +/* +判断合约触发 +*/ +func JudgeFuturesPrice(tradeSet models.TradeSet) { + preOrderVal, _ := helper.DefaultRedis.GetAllList(rediskey.PreFutOrderList) + db := GetDBConnection() + + if len(preOrderVal) == 0 { + // log.Debug("没有合约预下单") + return + } + futApi := FutRestApi{} + + for _, item := range preOrderVal { + preOrder := dto.PreOrderRedisList{} + if err := sonic.Unmarshal([]byte(item), &preOrder); err != nil { + log.Error("反序列化失败") + continue + } + + if preOrder.Symbol != tradeSet.Coin+tradeSet.Currency { + continue + } + + orderPrice, _ := decimal.NewFromString(preOrder.Price) + tradePrice, _ := decimal.NewFromString(tradeSet.LastPrice) + + if orderPrice.Cmp(decimal.Zero) == 0 || tradePrice.Cmp(decimal.Zero) == 0 { + continue + } + + //多 + if (strings.ToUpper(preOrder.Site) == "BUY" && orderPrice.Cmp(tradePrice) >= 0) || + (strings.ToUpper(preOrder.Site) == "SELL" && orderPrice.Cmp(tradePrice) <= 0) { + futTriggerOrder(db, &preOrder, item, futApi) + } + } +} + +// 分布式锁下单 +func futTriggerOrder(db *gorm.DB, v *dto.PreOrderRedisList, item string, futApi FutRestApi) { + lock := helper.NewRedisLock(fmt.Sprintf(rediskey.SpotTrigger, v.ApiId, v.Symbol), 200, 5, 100*time.Millisecond) + + if ok, err := lock.AcquireWait(context.Background()); err != nil { + log.Debug("获取锁失败", err) + return + } else if ok { + defer lock.Release() + preOrder := DbModels.LinePreOrder{} + key := fmt.Sprintf(rediskey.UserHolding, v.ApiId) + symbols, err := helper.DefaultRedis.GetAllList(key) + + if err != nil && err != redis.Nil { + log.Error("获取用户持仓失败", err) + return + } + + if err := db.Where("id = ?", v.Id).First(&preOrder).Error; err != nil { + log.Error("获取预下单失败", err) + + if errors.Is(err, gorm.ErrRecordNotFound) { + log.Error("不存在待触发主单", item) + helper.DefaultRedis.LRem(rediskey.PreFutOrderList, item) + } + + return + } + + coin := utility.ReplaceSuffix(v.Symbol, preOrder.QuoteSymbol, "") + if utility.ContainsStr(symbols, coin) { + log.Info("用户已经持有", coin) + return + } + hasrecord, _ := helper.DefaultRedis.IsElementInList(rediskey.PreFutOrderList, item) + + if !hasrecord { + log.Debug("预下单缓存中不存在", item) + return + } + + price, _ := decimal.NewFromString(v.Price) + num, _ := decimal.NewFromString(preOrder.Num) + + if price.Cmp(decimal.Zero) == 0 { + log.Error("价格不能为0") + return + } + + params := FutOrderPlace{ + ApiId: v.ApiId, + Symbol: v.Symbol, + Side: v.Site, + OrderType: preOrder.MainOrderType, + SideType: preOrder.MainOrderType, + Price: price, + Quantity: num, + NewClientOrderId: v.OrderSn, + } + preOrderVal, _ := sonic.MarshalString(&v) + + if err := futApi.OrderPlace(db, params); err != nil { + log.Error("下单失败", v.Symbol, " err:", err) + err := db.Model(&DbModels.LinePreOrder{}).Where("id =? and status='0'", preOrder.Id).Updates(map[string]interface{}{"status": "2", "desc": err.Error()}).Error + + if err != nil { + log.Error("更新预下单状态失败") + } + + if preOrderVal != "" { + if _, err := helper.DefaultRedis.LRem(rediskey.PreFutOrderList, preOrderVal); err != nil { + log.Error("删除redis 预下单失败:", err) + } + } + + return + } + + if preOrderVal != "" { + if _, err := helper.DefaultRedis.LRem(rediskey.PreFutOrderList, preOrderVal); err != nil { + log.Error("删除redis 预下单失败:", err) + } + } + + if err := db.Model(&DbModels.LinePreOrder{}).Where("id =? AND status ='0'", preOrder.Id).Update("status", "1").Error; err != nil { + log.Error("更新预下单状态失败 ordersn:", v.OrderSn, " status:1") + } + + if err := helper.DefaultRedis.RPushList(key, coin); err != nil { + log.Error("写入用户持仓失败", v.Symbol) + } + + return + } else { + log.Error("获取锁失败") + + return + } +} + +// CancelFutOrder 通过单个订单号取消合约委托 +// symbol 交易对 +// newClientOrderId 系统自定义订单号 +func (e FutRestApi) CancelFutOrder(apiUserInfo DbModels.LineApiUser, symbol string, newClientOrderId string) error { + endpoint := "/fapi/v1/order" + params := map[string]string{ + "symbol": symbol, //交易对 + "origClientOrderId": newClientOrderId, //用户自定义订单号 + } + var client *helper.BinanceClient + if apiUserInfo.UserPass == "" { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress) + } else { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress) + } + _, _, err := client.SendFuturesRequestAuth(endpoint, "DELETE", params) + if err != nil { + var dataMap map[string]interface{} + if err2 := sonic.Unmarshal([]byte(err.Error()), &dataMap); err2 != nil { + return errors.New(fmt.Sprintf("api_id:%d 交易对:%s 撤销合约委托出错:%s", apiUserInfo.Id, symbol, err.Error())) + } + code, ok := dataMap["code"] + if ok { + errContent := FutErrorMaps[code.(float64)] + + if errContent == "" { + errContent = err.Error() + } + + return errors.New(fmt.Sprintf("api_id:%d 交易对:%s 撤销合约委托出错:%s", apiUserInfo.Id, symbol, errContent)) + } + } + return nil +} + +// CancelAllFutOrder 通过交易对取消合约委托 +// symbol 交易对 +func (e FutRestApi) CancelAllFutOrder(apiUserInfo DbModels.LineApiUser, symbol string) error { + endpoint := "/fapi/v1/allOpenOrders" + params := map[string]string{ + "symbol": symbol, //交易对 + "recvWindow": "5000", + } + var client *helper.BinanceClient + if apiUserInfo.UserPass == "" { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress) + } else { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress) + } + _, _, err := client.SendFuturesRequestAuth(endpoint, "DELETE", params) + if err != nil { + var dataMap map[string]interface{} + if err2 := sonic.Unmarshal([]byte(err.Error()), &dataMap); err2 != nil { + return fmt.Errorf("api_id:%d 交易对:%s 撤销全部合约委托出错:%s", apiUserInfo.Id, symbol, err.Error()) + } + code, ok := dataMap["code"] + if ok { + errContent := FutErrorMaps[code.(float64)] + + if errContent == "" { + errContent = err.Error() + } + + return fmt.Errorf("api_id:%d 交易对:%s 撤销全部合约委托出错:%s", apiUserInfo.Id, symbol, errContent) + } + } + return nil +} + +// CancelBatchFutOrder 批量撤销订单 +// symbol 交易对 +// newClientOrderIdList 系统自定义的订单号, 最多支持10个订单 +func (e FutRestApi) CancelBatchFutOrder(apiUserInfo DbModels.LineApiUser, symbol string, newClientOrderIdList []string) error { + if len(newClientOrderIdList) > 10 { + return errors.New(fmt.Sprintf("api_id:%d 交易对:%s 撤销批量合约委托出错:%s", apiUserInfo.Id, symbol, "最多支持10个订单")) + } + endpoint := "/fapi/v1/batchOrders" + marshal, _ := sonic.Marshal(newClientOrderIdList) + params := map[string]string{ + "symbol": symbol, //交易对 + "origClientOrderIdList": string(marshal), + } + var client *helper.BinanceClient + if apiUserInfo.UserPass == "" { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "", apiUserInfo.IpAddress) + } else { + client, _ = helper.NewBinanceClient(apiUserInfo.ApiKey, apiUserInfo.ApiSecret, "socks5", apiUserInfo.UserPass+"@"+apiUserInfo.IpAddress) + } + _, code, err := client.SendFuturesRequestAuth(endpoint, "DELETE", params) + if err != nil { + log.Error("取消合约委托失败 参数:", params) + log.Error("取消合约委托失败 code:", code) + log.Error("取消合约委托失败 err:", err) + var dataMap map[string]interface{} + if err.Error() != "" { + if err2 := sonic.Unmarshal([]byte(err.Error()), &dataMap); err2 != nil { + return errors.New(fmt.Sprintf("api_id:%d 交易对:%s 撤销批量合约委托出错:%s", apiUserInfo.Id, symbol, err.Error())) + } + } + code, ok := dataMap["code"] + if ok { + errContent := FutErrorMaps[code.(float64)] + + if errContent == "" { + errContent = err.Error() + } + + return fmt.Errorf("api_id:%d 交易对:%s 撤销批量合约委托出错:%s", apiUserInfo.Id, symbol, errContent) + } + } + return nil +} + +// CalcSymbolExchangeAmt 计算兑换成USDT后的交易对数量 +// symbol 需要兑换的交易对 例如 ETHBTC +// quoteSymbol 计价货币 ETHBTC -> 计价货币就是BTC +// totalMoney 兑换的总金额 +func (e FutRestApi) CalcSymbolExchangeAmt(symbol string, quoteSymbol string, totalMoney decimal.Decimal) decimal.Decimal { + tickerSymbol := helper.DefaultRedis.Get(rediskey.FutSymbolTicker).Val() + tickerSymbolMaps := make([]dto.Ticker, 0) + sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps) + var targetSymbol string + targetSymbol = strings.Replace(symbol, quoteSymbol, "USDT", 1) //ETHBTC -》 ETHUSDT + key := fmt.Sprintf("%s:%s", global.TICKER_FUTURES, targetSymbol) + tradeSet, _ := helper.GetObjString[models.TradeSet](helper.DefaultRedis, key) + var targetPrice decimal.Decimal + quantity := decimal.Zero + for _, symbolMap := range tickerSymbolMaps { + if symbolMap.Symbol == strings.ToUpper(targetSymbol) { + targetPrice, _ = decimal.NewFromString(symbolMap.Price) + quantity = totalMoney.Div(targetPrice).Truncate(int32(tradeSet.AmountDigit)) + } + } + return quantity +} + +// 加仓主账号 +func (e FutRestApi) CoverAccountA(apiUserInfo DbModels.LineApiUser, symbol string) { + +} + +// GetFutSymbolLastPrice 获取现货交易对最新价格 +func (e FutRestApi) GetFutSymbolLastPrice(targetSymbol string) (lastPrice decimal.Decimal) { + tickerSymbol := helper.DefaultRedis.Get(rediskey.FutSymbolTicker).Val() + tickerSymbolMaps := make([]dto.Ticker, 0) + sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps) + for _, symbolMap := range tickerSymbolMaps { + if symbolMap.Symbol == strings.ToUpper(targetSymbol) { + lastPrice = utility.StringToDecimal(symbolMap.Price) + } + } + return lastPrice +} + +/* +重下止盈单 +*/ +func (e FutRestApi) reTakeOrder(parentOrderInfo DbModels.LinePreOrder, orm *gorm.DB) { + parentId := parentOrderInfo.Id + + if parentOrderInfo.Pid > 0 { + parentId = parentOrderInfo.Pid + } + + price, _ := decimal.NewFromString(parentOrderInfo.Price) + num, _ := decimal.NewFromString(parentOrderInfo.Num) + takePrice := decimal.Zero + holdeAKey := fmt.Sprintf(rediskey.HoldeA, parentId) + holdeAVal, _ := helper.DefaultRedis.GetString(holdeAKey) + holdeA := HoldeData{} + + if holdeAVal != "" { + sonic.Unmarshal([]byte(holdeAVal), &holdeA) + } + + //查询持仓失败 + if holdeA.Id == 0 { + log.Error("查询A账号持仓失败") + return + } + + takeOrder := DbModels.LinePreOrder{} + if err := orm.Model(&DbModels.LinePreOrder{}).Where("pid =? AND order_type ='3'").First(&takeOrder).Error; err != nil { + log.Error("查询止盈单失败", err) + return + } + + apiUserInfo, _ := GetApiInfo(takeOrder.ApiId) + if apiUserInfo.Id == 0 { + log.Error("重下止盈 查询api信息失败") + return + } + + newTakeOrder := DbModels.LinePreOrder{} + copier.Copy(&newTakeOrder, &takeOrder) + newTakeOrder.Id = 0 + newTakeOrder.OrderType = 1 + newTakeOrder.Status = 0 + newTakeOrder.OrderSn = strconv.FormatInt(snowflakehelper.GetOrderId(), 10) + + //加仓次数大于0 就需要使用均价 + if holdeA.PositionIncrementCount > 0 { + price = holdeA.AveragePrice + } + + if parentOrderInfo.Site == "BUY" { + takePrice = price.Mul(decimal.NewFromInt(100).Add(parentOrderInfo.ProfitRate)).Div(decimal.NewFromInt(100)) + } else { + takePrice = price.Mul(decimal.NewFromInt(100).Sub(parentOrderInfo.ProfitRate)).Div(decimal.NewFromInt(100)) + } + + tradeset, _ := GetTradeSet(takeOrder.Symbol, 1) + + if tradeset.Coin == "" { + log.Error("查询交易对失败") + return + } + + newTakeOrder.Price = takePrice.Truncate(int32(tradeset.PriceDigit)).String() + newTakeOrder.Num = num.Mul(decimal.NewFromFloat(0.995)).Truncate(int32(tradeset.AmountDigit)).String() + + if err := orm.Create(&newTakeOrder).Error; err != nil { + log.Error("重新止盈单创建失败", err) + return + } + + params := FutOrderPlace{ + ApiId: newTakeOrder.ApiId, + Symbol: newTakeOrder.Symbol, + Price: utility.StringToDecimal(newTakeOrder.Price), + Quantity: utility.StringToDecimal(newTakeOrder.Num), + Side: newTakeOrder.Site, + OrderType: "TAKE_PROFIT_MARKET", + Profit: utility.StringToDecimal(newTakeOrder.Price), + NewClientOrderId: newTakeOrder.OrderSn, + } + + if err := e.CancelFutOrder(apiUserInfo, takeOrder.Symbol, takeOrder.OrderSn); err != nil { + log.Error("取消老止盈单失败:", err) + + orm.Model(&newTakeOrder).Updates(map[string]interface{}{"status": "2", "desc": "取消老止盈单失败," + err.Error()}) + return + } else { + if err := orm.Model(&DbModels.LinePreOrder{}).Where("pid = ? AND order_type =5 AND status in ('0','1','5') AND order_sn !=?", parentId, newTakeOrder.OrderSn).Update("status", "4").Error; err != nil { + log.Error("更新旧止盈单取消状态失败 err:", err) + } + } + + var err error + for x := 0; x < 3; x++ { + err = e.OrderPlace(orm, params) + + if err == nil { + break + } + + log.Error("下止盈单失败 第", utility.IntToString(x), "次", " err:", err) + time.Sleep(2 * time.Second * time.Duration(x)) + } + + if err != nil { + log.Error("重新下单止盈失败 err:", err) + + if err1 := orm.Model(&DbModels.LinePreOrder{}).Where("id =?", newTakeOrder.Id). + Updates(map[string]interface{}{"status": "2", "desc": err.Error()}).Error; err1 != nil { + log.Error("重新下单止盈 修改止盈订单失败 id:", newTakeOrder.Id, " takePrice:", takePrice, " err:", err1) + } + } + + //修改止盈单信息 + if err := orm.Model(&DbModels.LinePreOrder{}).Where("id =?", newTakeOrder.Id). + Updates(map[string]interface{}{"price": takePrice, "rate": parentOrderInfo.ProfitRate}).Error; err != nil { + log.Error("重新下单止盈 修改订单失败 id:", newTakeOrder.Id, " takePrice:", takePrice, " rate:", parentOrderInfo.ProfitRate, " err:", err) + } +} diff --git a/services/binanceservice/futuresrest.go b/services/binanceservice/futuresrest.go new file mode 100644 index 0000000..f00b3c7 --- /dev/null +++ b/services/binanceservice/futuresrest.go @@ -0,0 +1,62 @@ +package binanceservice + +import ( + "context" + "fmt" + "go-admin/common/const/rediskey" + "go-admin/common/helper" + "time" + + "github.com/bytedance/sonic" + "github.com/go-admin-team/go-admin-core/logger" +) + +/* +修改订单信息 +*/ +func ChangeFutureOrder(mapData map[string]interface{}) { + // 检查订单号是否存在 + orderSn, ok := mapData["c"] + if !ok { + logger.Error("合约订单回调失败,没有订单号") + return + } + + // 获取数据库连接 + db := GetDBConnection() + if db == nil { + logger.Error("合约订单回调失败,无法获取数据库连接") + return + } + + // 获取 Redis 锁 + lock := helper.NewRedisLock(fmt.Sprintf(rediskey.SpotCallBack, orderSn), 10, 5, 500*time.Millisecond) + acquired, err := lock.AcquireWait(context.Background()) + if err != nil { + logger.Error("合约订单回调失败,获取锁失败:", orderSn, " err:", err) + return + } + if !acquired { + logger.Error("合约订单回调失败,获取锁失败:", orderSn) + return + } + defer lock.Release() + + // 查询订单 + preOrder, err := getPreOrder(db, orderSn) + if err != nil { + logger.Error("合约订单回调失败,查询订单失败:", orderSn, " err:", err) + return + } + + // 解析订单状态 + status, ok := mapData["X"].(string) + if !ok { + mapStr, _ := sonic.Marshal(&mapData) + logger.Error("订单回调失败,没有状态:", string(mapStr)) + return + } + + //todo + +} diff --git a/services/binanceservice/models.go b/services/binanceservice/models.go new file mode 100644 index 0000000..1e483f4 --- /dev/null +++ b/services/binanceservice/models.go @@ -0,0 +1,228 @@ +package binanceservice + +import ( + "errors" + "time" + + "github.com/shopspring/decimal" +) + +// OrderPlacementService 币安现货下单 +type OrderPlacementService struct { + ApiId int `json:"api_id"` //api_id + Symbol string `json:"symbol"` //交易对 + Side string `json:"side"` //购买方向 + Type string `json:"type"` //下单类型 MARKET=市价 LIMIT=限价 TAKE_PROFIT_LIMIT=限价止盈 STOP_LOSS_LIMIT=限价止损 + TimeInForce string `json:"timeInForce"` // 订单有效期,默认为 GTC (Good Till Cancelled) + Price decimal.Decimal `json:"price"` //限价单价 + Quantity decimal.Decimal `json:"quantity"` //下单数量 + NewClientOrderId string `json:"newClientOrderId"` //系统生成的订单号 + StopPrice decimal.Decimal `json:"stopprice"` //止盈止损时需要 + Rate string `json:"rate"` //下单百分比 +} + +func (s *OrderPlacementService) CheckParams() error { + + if s.ApiId == 0 || s.Symbol == "" || s.Type == "" || s.NewClientOrderId == "" || s.Side == "" || s.Quantity.LessThan(decimal.Zero) { + return errors.New("缺失下单必要参数") + } + + if s.Type == "LIMIT" && s.Price.LessThanOrEqual(decimal.Zero) { + return errors.New("缺失限价单参数price") + } + if s.Type == "TAKE_PROFIT_LIMIT" || s.Type == "STOP_LOSS_LIMIT" { + if s.StopPrice.LessThanOrEqual(decimal.Zero) { + return errors.New("缺失止盈止损订单参数stopprice") + } + } + return nil +} + +// CancelOpenOrdersReq 撤销单一交易对的所有挂单 +type CancelOpenOrdersReq struct { + ApiId int `json:"api_id"` //api_id + Symbol string `json:"symbol"` //交易对 +} + +func (r *CancelOpenOrdersReq) CheckParams() error { + if r.Symbol == "" { + return errors.New("缺失下单必要参数") + } + return nil +} + +// EntryPriceResult 计算均价结果 +type EntryPriceResult struct { + TotalNum decimal.Decimal `json:"total_num"` //总数量 + EntryPrice decimal.Decimal `json:"entry_price"` //均价 + FirstPrice decimal.Decimal `json:"first_price"` //主单的下单价格 + TotalMoney decimal.Decimal `json:"total_money"` //总金额(U) + FirstId int `json:"first_id"` //主单id +} + +type FutOrderPlace struct { + ApiId int `json:"api_id"` //api用户id + Symbol string `json:"symbol"` //合约交易对 + Side string `json:"side"` //购买方向 + Quantity decimal.Decimal `json:"quantity"` //数量 + Price decimal.Decimal `json:"price"` //限价单价 + SideType string `json:"side_type"` //现价或者市价 + OpenOrder int `json:"open_order"` //是否开启限价单止盈止损 + Profit decimal.Decimal `json:"profit"` //止盈价格 + StopPrice decimal.Decimal `json:"stopprice"` //止损价格 + OrderType string `json:"order_type"` //订单类型,市价或限价MARKET(市价单) TAKE_PROFIT_MARKET(止盈) STOP_MARKET(止损) + NewClientOrderId string `json:"newClientOrderId"` +} + +func (s FutOrderPlace) CheckParams() error { + if s.ApiId == 0 || s.Symbol == "" || s.OrderType == "" || s.NewClientOrderId == "" || s.Side == "" || s.Quantity.LessThan(decimal.Zero) { + return errors.New("缺失下单必要参数") + } + if s.OrderType == "LIMIT" && s.Price.LessThan(decimal.Zero) { + return errors.New("缺失限价单参数price") + } + if s.OrderType == "TAKE_PROFIT_MARKET" || s.OrderType == "STOP_MARKET" { + if s.StopPrice.LessThanOrEqual(decimal.Zero) || s.Profit.LessThanOrEqual(decimal.Zero) { + return errors.New("缺失止盈止损订单参数stopprice") + } + } + return nil +} + +// PositionRisk 用户持仓风险 +type PositionRisk struct { + Symbol string `json:"symbol"` + PositionSide string `json:"positionSide"` + PositionAmt string `json:"positionAmt"` + EntryPrice string `json:"entryPrice"` + BreakEvenPrice string `json:"breakEvenPrice"` + MarkPrice string `json:"markPrice"` + UnRealizedProfit string `json:"unRealizedProfit"` + LiquidationPrice string `json:"liquidationPrice"` + IsolatedMargin string `json:"isolatedMargin"` + Notional string `json:"notional"` + MarginAsset string `json:"marginAsset"` + IsolatedWallet string `json:"isolatedWallet"` + InitialMargin string `json:"initialMargin"` + MaintMargin string `json:"maintMargin"` + PositionInitialMargin string `json:"positionInitialMargin"` + OpenOrderInitialMargin string `json:"openOrderInitialMargin"` + Adl int `json:"adl"` + BidNotional string `json:"bidNotional"` + AskNotional string `json:"askNotional"` + UpdateTime int64 `json:"updateTime"` +} +type FutOrderResp struct { + ClientOrderId string `json:"clientOrderId"` + CumQty string `json:"cumQty"` + CumQuote string `json:"cumQuote"` + ExecutedQty string `json:"executedQty"` + OrderId int `json:"orderId"` + AvgPrice string `json:"avgPrice"` + OrigQty string `json:"origQty"` + Price string `json:"price"` + ReduceOnly bool `json:"reduceOnly"` + Side string `json:"side"` + PositionSide string `json:"positionSide"` + Status string `json:"status"` + StopPrice string `json:"stopPrice"` + ClosePosition bool `json:"closePosition"` + Symbol string `json:"symbol"` + TimeInForce string `json:"timeInForce"` + Type string `json:"type"` + OrigType string `json:"origType"` + ActivatePrice string `json:"activatePrice"` + PriceRate string `json:"priceRate"` + UpdateTime int64 `json:"updateTime"` + WorkingType string `json:"workingType"` + PriceProtect bool `json:"priceProtect"` + PriceMatch string `json:"priceMatch"` + SelfTradePreventionMode string `json:"selfTradePreventionMode"` + GoodTillDate int64 `json:"goodTillDate"` +} + +type HoldeData struct { + Id int `json:"id"` //主单id + UpdateTime time.Time `json:"updateTime" comment:"最后更新时间"` + Type string `json:"type" comment:"1-现货 2-合约"` + Symbol string `json:"symbol"` + AveragePrice decimal.Decimal `json:"averagePrice" comment:"均价"` + Side string `json:"side" comment:"持仓方向"` + TotalQuantity decimal.Decimal `json:"totalQuantity" comment:"持仓数量"` + TotalBuyPrice decimal.Decimal `json:"totalBuyPrice" comment:"总购买金额"` + PositionIncrementCount int `json:"positionIncrementCount"` //加仓次数 + HedgeCloseCount int `json:"hedgeCloseCount" comment:"对冲平仓数量"` + TriggerStatus int `json:"triggerStatus" comment:"触发状态 平仓之后重置 0-未触发 1-触发中 2-触发完成"` + PositionStatus int `json:"positionStatus" comment:"加仓状态 0-未开始 1-未完成 2-已完成 3-失败"` +} + +// OpenOrders 挂单信息 +type OpenOrders struct { + AvgPrice string `json:"avgPrice"` // 平均成交价 + ClientOrderId string `json:"clientOrderId"` // 用户自定义的订单号 + CumQuote string `json:"cumQuote"` // 成交金额 + ExecutedQty string `json:"executedQty"` // 成交量 + OrderId int `json:"orderId"` // 系统订单号 + OrigQty string `json:"origQty"` // 原始委托数量 + OrigType string `json:"origType"` // 触发前订单类型 + Price string `json:"price"` // 委托价格 + ReduceOnly bool `json:"reduceOnly"` // 是否仅减仓 + Side string `json:"side"` // 买卖方向 + PositionSide string `json:"positionSide"` // 持仓方向 + Status string `json:"status"` // 订单状态 + StopPrice string `json:"stopPrice"` // 触发价,对`TRAILING_STOP_MARKET`无效 + ClosePosition bool `json:"closePosition"` // 是否条件全平仓 + Symbol string `json:"symbol"` // 交易对 + Time int64 `json:"time"` // 订单时间 + TimeInForce string `json:"timeInForce"` // 有效方法 + Type string `json:"type"` // 订单类型 + ActivatePrice string `json:"activatePrice"` // 跟踪止损激活价格, 仅`TRAILING_STOP_MARKET` 订单返回此字段 + PriceRate string `json:"priceRate"` // 跟踪止损回调比例, 仅`TRAILING_STOP_MARKET` 订单返回此字段 + UpdateTime int64 `json:"updateTime"` // 更新时间 + WorkingType string `json:"workingType"` // 条件价格触发类型 + PriceProtect bool `json:"priceProtect"` // 是否开启条件单触发保护 + PriceMatch string `json:"priceMatch"` //price match mode + SelfTradePreventionMode string `json:"selfTradePreventionMode"` //self trading preventation mode + GoodTillDate int `json:"goodTillDate"` //order pre-set auot cancel time for TIF GTD order +} + +// 待触发加仓单 +type AddPositionList struct { + Pid int `json:"pid"` //主单id + ApiId int `json:"apiId"` //触发账户id + Symbol string `json:"symbol"` //交易对 + Price decimal.Decimal `json:"price"` //触发价 + Side string `json:"side"` //买卖方向 + AddPositionMainType string `json:"addPositionType"` //A账号加仓类型 + AddPositionHedgeType string `json:"addPositionHedgeType"` //B账号加仓类型 + SymbolType int `json:"type" comment:"交易对类别 1-现货 2-合约"` +} + +// SpotAccountInfo 现货账户信息 +type SpotAccountInfo struct { + MakerCommission int `json:"makerCommission"` + TakerCommission int `json:"takerCommission"` + BuyerCommission int `json:"buyerCommission"` + SellerCommission int `json:"sellerCommission"` + CommissionRates struct { + Maker string `json:"maker"` + Taker string `json:"taker"` + Buyer string `json:"buyer"` + Seller string `json:"seller"` + } `json:"commissionRates"` + CanTrade bool `json:"canTrade"` + CanWithdraw bool `json:"canWithdraw"` + CanDeposit bool `json:"canDeposit"` + Brokered bool `json:"brokered"` + RequireSelfTradePrevention bool `json:"requireSelfTradePrevention"` + PreventSor bool `json:"preventSor"` + UpdateTime int `json:"updateTime"` + AccountType string `json:"accountType"` + Balances []struct { + Asset string `json:"asset"` + Free string `json:"free"` + Locked string `json:"locked"` + } `json:"balances"` + Permissions []string `json:"permissions"` + Uid int `json:"uid"` +} diff --git a/services/binanceservice/orderservice.go b/services/binanceservice/orderservice.go new file mode 100644 index 0000000..ea18798 --- /dev/null +++ b/services/binanceservice/orderservice.go @@ -0,0 +1,103 @@ +package binanceservice + +import ( + "go-admin/app/admin/models" + DbModels "go-admin/app/admin/models" + + "github.com/shopspring/decimal" + "gorm.io/gorm" +) + +// 获取订单明细 +func GetOrderById(db *gorm.DB, id int) (DbModels.LinePreOrder, error) { + result := DbModels.LinePreOrder{} + + if err := db.Model(&result).Where("id =?", id).First(&result).Error; err != nil { + return result, err + } + + return result, nil +} + +// 获取已开仓的对冲单、对冲加仓单id +// pid:主单id +// coverType:对冲类型 1-现货对合约 2-合约对合约 3-合约对现货 +func GetHedgeOpenOrderIds(db *gorm.DB, pid int, coverType int) ([]int, error) { + result := make([]DbModels.LinePreOrder, 0) + resultIds := make([]int, 0) + query := db.Model(&result) + + switch coverType { + case 3: + query = query.Where("pid =? AND order_type in ('7','8','10','11') AND operate_type =1 AND status in ('9','13')", pid) + case 2, 1: + query = query.Where("pid =? AND order_type in ('10','11') AND operate_type =1 AND status ='13'", pid) + } + + if err := query.Select("id").Find(&result).Error; err != nil { + return resultIds, err + } + + for _, v := range result { + resultIds = append(resultIds, v.Id) + } + + return resultIds, nil +} + +// 获得对冲单 +func GetHedgeOpenOrder(db *gorm.DB, pid int, coverType int) (DbModels.LinePreOrder, error) { + result := DbModels.LinePreOrder{} + orderType := "" + + switch coverType { + case 1: + orderType = "7" + case 2, 3: + orderType = "10" + } + + if err := db.Model(&result).Where("pid =? AND order_type =? AND operate_type =1", pid, orderType).First(&result).Error; err != nil { + return result, err + } + + return result, nil +} + +// 获取止损单 +func GetStopOrder(db *gorm.DB, pid int) (DbModels.LinePreOrder, error) { + result := DbModels.LinePreOrder{} + + if err := db.Model(&result).Where("pid =? AND order_type in ('4','6')", pid).First(&result).Error; err != nil { + return result, err + } + + return result, nil +} + +// 获取最后一条对冲的下单百分比 +func GetStopOrderRate(db *gorm.DB, pid int) (decimal.Decimal, error) { + var result decimal.Decimal + + if err := db.Model(&DbModels.LinePreOrder{}).Where("pid =? AND order_type in ('7','10')", pid). + Select("rate"). + Order("id DESC"). + First(&result).Error; err != nil { + return result, err + } + return result, nil +} + +// 获取最后一条对冲 +func GetLastStop(db *gorm.DB, pid int) (DbModels.LinePreOrder, error) { + result := models.LinePreOrder{} + + if err := db.Model(&result). + Joins("JOIN line_pre_order as o ON o.id = line_pre_order.pid AND o.status in ('9','13')"). + Where("line_pre_order.pid =? AND line_pre_order.order_type in ('7','10')", pid). + Order("line_pre_order.id DESC").Select("line_pre_order.*").First(&result).Error; err != nil { + return result, err + } + + return result, nil +} diff --git a/services/binanceservice/spotreset.go b/services/binanceservice/spotreset.go new file mode 100644 index 0000000..6b3bb12 --- /dev/null +++ b/services/binanceservice/spotreset.go @@ -0,0 +1,432 @@ +package binanceservice + +import ( + "context" + "fmt" + "go-admin/app/admin/models" + DbModels "go-admin/app/admin/models" + "go-admin/common/const/rediskey" + "go-admin/common/helper" + "go-admin/pkg/utility" + "strconv" + "strings" + "time" + + "github.com/bytedance/sonic" + "github.com/go-admin-team/go-admin-core/logger" + "github.com/shopspring/decimal" + "gorm.io/gorm" +) + +/* +订单回调 +*/ +func ChangeSpotOrder(mapData map[string]interface{}) { + // 检查订单号是否存在 + orderSn, ok := mapData["c"] + if !ok { + logger.Error("订单回调失败, 没有订单号", mapData) + return + } + + // 获取数据库连接 + db := GetDBConnection() + if db == nil { + logger.Error("订单回调失败, 无法获取数据库连接") + return + } + + // 获取 Redis 锁 + lockKey := fmt.Sprintf(rediskey.SpotCallBack, orderSn) + lock := helper.NewRedisLock(lockKey, 200, 5, 100*time.Millisecond) + if err := acquireLock(lock, orderSn); err != nil { + return + } + defer lock.Release() + + // 查询订单 + preOrder, err := getPreOrder(db, orderSn) + if err != nil { + logger.Error("订单回调失败, 查询订单失败:", orderSn, " err:", err) + return + } + + // 解析订单状态 + status, ok := mapData["X"] + if !ok { + logMapData(mapData) + return + } + + // 更新订单状态 + orderStatus, reason := parseOrderStatus(preOrder, status, mapData) + + if orderStatus == 0 { + logger.Error("订单回调失败,状态错误:", orderSn, " status:", status, " reason:", reason) + return + } + + if err := updateOrderStatus(db, preOrder, orderStatus, reason, true, mapData); err != nil { + logger.Error("修改订单状态失败:", orderSn, " err:", err) + return + } + + // 根据订单类型和状态处理逻辑 + handleOrderByType(db, preOrder, orderStatus) +} + +// 获取 Redis 锁 +func acquireLock(lock *helper.RedisLock, orderSn interface{}) error { + acquired, err := lock.AcquireWait(context.Background()) + if err != nil { + logger.Error("订单回调失败, 获取锁失败:", orderSn, " err:", err) + return err + } + if !acquired { + logger.Error("订单回调失败, 获取锁失败:", orderSn) + return fmt.Errorf("failed to acquire lock") + } + return nil +} + +// 记录 mapData 数据 +func logMapData(mapData map[string]interface{}) { + mapStr, _ := sonic.Marshal(&mapData) + logger.Error("订单回调失败, 没有状态:", string(mapStr)) +} + +// 根据订单类型和状态处理逻辑 +func handleOrderByType(db *gorm.DB, preOrder *DbModels.LinePreOrder, orderStatus int) { + switch { + // 主单成交 + case preOrder.OrderType == 0 && (orderStatus == 9 || orderStatus == 6): + handleMainOrderFilled(db, preOrder) + + //主单取消 + case preOrder.OrderType == 0 && preOrder.Pid == 0 && orderStatus == 4: + coin := utility.ReplaceSuffix(preOrder.Symbol, preOrder.QuoteSymbol, "") + removeHoldingCache(preOrder.ApiId, coin, preOrder.Id) + + // 止盈成交 + case preOrder.OrderType == 1 && (orderStatus == 9 || orderStatus == 6): + handleSpotTakeProfitFilled(db, preOrder) + + //主单平仓 + case preOrder.OrderType == 3 && orderStatus == 9: + handleMainOrderClosePosition(db, preOrder) + } +} + +func handleMainOrderClosePosition(db *gorm.DB, preOrder *DbModels.LinePreOrder) { + panic("unimplemented") +} + +func handleSpotTakeProfitFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { + panic("unimplemented") +} + +func removeHoldingCache(i1 int, coin string, i2 int) { + panic("unimplemented") +} + +func handleMainOrderFilled(db *gorm.DB, preOrder *DbModels.LinePreOrder) { + panic("unimplemented") +} + +// 解析订单状态 +// 5:委托中 9:完全成交 4:取消 +func parseOrderStatus(preOrder *DbModels.LinePreOrder, status interface{}, mapData map[string]interface{}) (int, string) { + reason, _ := mapData["r"].(string) + + if strings.ToLower(reason) == "none" { + reason = "" + } + + switch status { + case "NEW": // 未成交 + return 5, reason + case "FILLED": // 完全成交 + if preOrder.OrderType < 3 { + return 6, reason + } + + return 9, reason + case "CANCELED", "EXPIRED": // 取消 + return 4, reason + default: + return 0, reason + } +} + +// 更新订单状态 +func updateOrderStatus(db *gorm.DB, preOrder *models.LinePreOrder, status int, reason string, isSpot bool, mapData map[string]interface{}) error { + params := map[string]interface{}{"status": strconv.Itoa(status), "desc": reason} + + switch isSpot { + case true: + total := decimal.Zero + totalAmount := decimal.Zero + if totalStr, ok := mapData["Z"].(string); ok { + total, _ = decimal.NewFromString(totalStr) + } + if totalAmountStr, ok := mapData["z"].(string); ok { + totalAmount, _ = decimal.NewFromString(totalAmountStr) + } + + //主单 修改单价 和成交数量 + if total.Cmp(decimal.Zero) > 0 && totalAmount.Cmp(decimal.Zero) > 0 { + num := totalAmount.Div(decimal.NewFromFloat(100)).Mul(decimal.NewFromFloat(99.8)) + params["num"] = num + params["price"] = total.Div(totalAmount) + preOrder.Num = num.String() + } + case false: + status, _ := mapData["X"].(string) + + if status == "FILLED" { + num, _ := decimal.NewFromString(mapData["z"].(string)) + params["num"] = num.Mul(decimal.NewFromFloat(0.998)).String() + params["price"], _ = mapData["ap"].(string) + + preOrder.Num = num.String() + } + } + + return db.Model(&DbModels.LinePreOrder{}).Where("order_sn = ? AND status < 6", preOrder.OrderSn). + Updates(params).Error +} + +// 主单成交 处理止盈止损订单 +func processTakeProfitAndStopLossOrders(db *gorm.DB, preOrder *models.LinePreOrder) { + orders := []models.LinePreOrder{} + if err := db.Model(&DbModels.LinePreOrder{}).Where("pid = ? AND order_type >0 AND status = '0' ", preOrder.Id).Find(&orders).Error; err != nil { + logger.Error("订单回调查询止盈止损单失败:", err) + return + } + + spotApi := SpotRestApi{} + num, _ := decimal.NewFromString(preOrder.Num) + + for i, order := range orders { + if i >= 2 { // 最多处理 2 个订单 + break + } + + switch order.OrderType { + case 1: // 止盈 + processTakeProfitOrder(db, spotApi, order, num) + case 2: // 止损 + processStopLossOrder(db, order) + } + } +} + +// 处理止盈订单 +func processTakeProfitOrder(db *gorm.DB, spotApi SpotRestApi, order models.LinePreOrder, num decimal.Decimal) { + tradeSet, _ := GetTradeSet(order.Symbol, 0) + + if tradeSet.Coin == "" { + logger.Error("获取交易对失败") + return + } + + price, _ := decimal.NewFromString(order.Price) + // num, _ := decimal.NewFromString(order.Num) + + params := OrderPlacementService{ + ApiId: order.ApiId, + Symbol: order.Symbol, + Side: order.Site, + Price: price.Truncate(int32(tradeSet.PriceDigit)), + Quantity: num.Truncate(int32(tradeSet.AmountDigit)), + Type: "TAKE_PROFIT_LIMIT", + TimeInForce: "GTC", + StopPrice: price.Truncate(int32(tradeSet.PriceDigit)), + NewClientOrderId: order.OrderSn, + } + + err := spotApi.OrderPlace(db, params) + + if err != nil { + for x := 0; x < 5; x++ { + if strings.Contains(err.Error(), "LOT_SIZE") { + break + } + + err = spotApi.OrderPlace(db, params) + + if err == nil { + break + } + } + } + + if err != nil { + logger.Error("现货止盈下单失败:", order.OrderSn, " err:", err) + if err := db.Model(&DbModels.LinePreOrder{}).Where("id = ?", order.Id). + Updates(map[string]interface{}{"status": "2", "desc": err.Error()}).Error; err != nil { + logger.Error("现货止盈下单失败,更新状态失败:", order.OrderSn, " err:", err) + } + } else { + if err := db.Model(&DbModels.LinePreOrder{}).Where("id = ? and status ='0'", order.Id). + Updates(map[string]interface{}{"status": "1", "num": num.String()}).Error; err != nil { + logger.Error("现货止盈下单成功,更新状态失败:", order.OrderSn, " err:", err) + } + } +} + +// 处理止损订单 +// order 止损单 +func processStopLossOrder(db *gorm.DB, order models.LinePreOrder) error { + // var stopOrder models.LinePreOrder + // orderTypes := []string{"4", "6", "9", "12"} + // parentId := order.Id + + // if order.Pid > 0 { + // parentId = order.Pid + // } + + // if utility.ContainsStr(orderTypes, order.OrderType) { + // var err error + // stopOrder, err = GetStopOrder(db, order.Pid) + + // if err != nil { + // logger.Error("查询止损单失败:", err) + // return err + // } + // } + + // price, _ := decimal.NewFromString(stopOrder.Price) + // stoploss, _ := decimal.NewFromString(order.Rate) + + // if holdeB.Id > 0 { + // _, holdeA := GetHoldeA(stopOrder.Pid) + // var percent decimal.Decimal + // lastPercent, _ := GetStopOrderRate(db, stopOrder.Pid) + + // if stopOrder.Site == "BUY" { + // //平仓次数>=最大次数 且余数为0 重新计算触发对冲百分比 + // // if holdeA.Id > 0 && holdeB.HedgeCloseCount >= stopOrder.HedgeCloseCount && (holdeB.HedgeCloseCount%stopOrder.HedgeCloseCount == 0) { + // rand := getRand(stopOrder.HedgeTriggerPercent, stopOrder.HedgeTriggerPercentMax, lastPercent, 1) + + // percent = decimal.NewFromInt(100).Add(rand).Div(decimal.NewFromInt(100)) + // // } else { + // // rate, _ := decimal.NewFromString(stopOrder.Rate) + // // percent = decimal.NewFromInt(100).Add(rate).Div(decimal.NewFromInt(100)) + // // } + // } else { + // // if holdeA.Id > 0 && holdeB.HedgeCloseCount >= stopOrder.HedgeCloseCount { + // rand := getRand(stopOrder.HedgeTriggerPercent, stopOrder.HedgeTriggerPercentMax, lastPercent, 1) + + // percent = decimal.NewFromInt(100).Sub(rand).Div(decimal.NewFromInt(100)) + // // } else { + // // rate, _ := decimal.NewFromString(stopOrder.Rate) + // // percent = decimal.NewFromInt(100).Sub(rate).Div(decimal.NewFromInt(100)) + // // } + // } + // stoploss = decimal.NewFromInt(100).Sub(percent.Mul(decimal.NewFromInt(100))).Truncate(2) + // price = holdeA.AveragePrice.Mul(percent) + // } + + // tradeset, _ := GetTradeSet(stopOrder.Symbol, 1) + // if tradeset.PriceDigit > 0 { + // price = price.Truncate(int32(tradeset.PriceDigit)) + // } + + // cache := dto.StopLossRedisList{ + // PId: stopOrder.Pid, + // ApiId: stopOrder.ApiId, + // Price: price, + // OrderTye: stopOrder.OrderType, + // Site: stopOrder.Site, + // Symbol: stopOrder.Symbol, + // Stoploss: stoploss, + // } + + // stoplossKey := fmt.Sprintf(rediskey.SpotStopLossList) + // cacheVal, _ := sonic.MarshalString(&cache) + + // if stopOrder.OrderType == "4" { + // stoplossKey = rediskey.FuturesStopLossList + // } + + // stopLossVal, _ := helper.DefaultRedis.GetAllList(stoplossKey) + // for _, itemVal := range stopLossVal { + // if strings.Contains(itemVal, fmt.Sprintf("\"pid\":%v,", stopOrder.Pid)) { + // helper.DefaultRedis.LRem(stoplossKey, itemVal) + // break + // } + // } + + // //重新保存待触发对冲单 + // if err := helper.DefaultRedis.RPushList(stoplossKey, cacheVal); err != nil { + // logger.Error("B单平仓回调,redis添加止损单失败", err) + // } + + return nil +} + +// 生成随机数 且不重复 +// lastPercent 上一次的百分比 +// floatNum 小数点后几位 +func getRand(start, end, lastPercent decimal.Decimal, floatNum int) decimal.Decimal { + var rand decimal.Decimal + + for x := 0; x < 10; x++ { + rand = utility.DecimalRandom(start, end, floatNum) + + if rand.Cmp(lastPercent) != 0 { + break + } + } + + return rand +} + +func GetSystemSetting(db *gorm.DB) (models.LineSystemSetting, error) { + key := fmt.Sprintf(rediskey.SystemSetting) + val, _ := helper.DefaultRedis.GetString(key) + setting := models.LineSystemSetting{} + + if val != "" { + sonic.UnmarshalString(val, &setting) + } + + if setting.Id > 0 { + return setting, nil + } + + var err error + setting, err = ResetSystemSetting(db) + if err != nil { + return setting, err + } + + return setting, nil +} + +func ResetSystemSetting(db *gorm.DB) (DbModels.LineSystemSetting, error) { + setting := DbModels.LineSystemSetting{} + if err := db.Model(&setting).First(&setting).Error; err != nil { + return setting, err + } + + settVal, _ := sonic.MarshalString(&setting) + + if settVal != "" { + if err := helper.DefaultRedis.SetString(rediskey.SystemSetting, settVal); err != nil { + logger.Error("redis添加系统设置失败", err) + } + } + return DbModels.LineSystemSetting{}, nil +} + +// NEW +// PENDING_NEW +// PARTIALLY_FILLED +// FILLED +// CANCELED +// PENDING_CANCEL +// REJECTED +// EXPIRED +// EXPIRED_IN_MATCH diff --git a/services/binanceservice/spotsymbolservice.go b/services/binanceservice/spotsymbolservice.go new file mode 100644 index 0000000..f3a0dec --- /dev/null +++ b/services/binanceservice/spotsymbolservice.go @@ -0,0 +1,68 @@ +package binanceservice + +import ( + "go-admin/models" + "go-admin/models/spot" + "go-admin/pkg/utility" + "sync" + + log "github.com/go-admin-team/go-admin-core/logger" +) + +var quoteAssetSymbols = []string{"USDT", "ETH", "BTC", "SOL", "BNB", "DOGE"} + +func GetSpotSymbols() (map[string]models.TradeSet, []string, error) { + spotApi := SpotRestApi{} + symbols, err := spotApi.GetExchangeInfo() + tradeSets := make(map[string]models.TradeSet, len(symbols)) + + if err != nil { + log.Error("获取规范信息失败", err) + return tradeSets, []string{}, err + } + + var wg sync.WaitGroup + var mu sync.Mutex // 用于保护 tradeSets 的并发写入 + + for _, item := range symbols { + if utility.ContainsStr(quoteAssetSymbols, item.QuoteAsset) && item.Status == "TRADING" && item.IsSpotTradingAllowed { + wg.Add(1) + go func(item spot.Symbol) { + defer wg.Done() + tradeSet := models.TradeSet{ + Coin: item.BaseAsset, + Currency: item.QuoteAsset, + } + + for _, filter := range item.Filters { + switch filter.FilterType { + case "PRICE_FILTER": + tradeSet.PriceDigit = utility.GetPrecision(filter.TickSize) + tradeSet.MinBuyVal = utility.StringAsFloat(filter.MinPrice) + case "LOT_SIZE": + tradeSet.AmountDigit = utility.GetPrecision(filter.StepSize) + tradeSet.MinQty = utility.StringAsFloat(filter.MinQty) + tradeSet.MaxQty = utility.StringAsFloat(filter.MaxQty) + } + } + + mu.Lock() + tradeSets[item.Symbol] = tradeSet + mu.Unlock() + }(item) + } + } + + wg.Wait() // 等待所有 goroutine 完成 + log.Info("初始化交易对") + + deleteSymbols, err := spotApi.GetSpotTicker24h(&tradeSets) + + if err != nil { + log.Error("初始化币安现货交易对失败", err) + return map[string]models.TradeSet{}, deleteSymbols, err + } else { + log.Info("初始化现货交易对完毕") + return tradeSets, deleteSymbols, err + } +} diff --git a/services/excservice/apibuild.go b/services/excservice/apibuild.go new file mode 100644 index 0000000..3db19fc --- /dev/null +++ b/services/excservice/apibuild.go @@ -0,0 +1,94 @@ +package excservice + +import ( + "go-admin/pkg/httputils" + + "github.com/bytedance/sonic" + "go.uber.org/zap" + + log "github.com/go-admin-team/go-admin-core/logger" +) + +var ( +// DefaultHttpClientConfig = &HttpClientConfig{ +// Proxy: nil, +// HttpTimeout: 5 * time.Second, +// MaxIdleConns: 10} +) +var ( + timeOffset int64 = 0 +) + +//var INERNAL_KLINE_PERIOD_CONVERTER = map[int]string{ +// models.KLINE_1MIN: "1m", +// models.KLINE_3MIN: "3m", +// models.KLINE_5MIN: "5m", +// models.KLINE_15MIN: "15m", +// models.KLINE_30MIN: "30m", +// models.KLINE_60MIN: "1h", +// //models.KLINE_1H: "1h", +// models.KLINE_2H: "2h", +// models.KLINE_4H: "4h", +// models.KLINE_6H: "6h", +// models.KLINE_8H: "8h", +// models.KLINE_12H: "12h", +// models.KLINE_1DAY: "1d", +// models.KLINE_3DAY: "3d", +// models.KLINE_1WEEK: "1w", +// models.KLINE_1MONTH: "1M", +//} + +type Filter struct { + FilterType string `json:"filterType"` + MaxPrice string `json:"maxPrice"` + MinPrice string `json:"minPrice"` + TickSize string `json:"tickSize"` + MultiplierUp string `json:"multiplierUp,string"` + MultiplierDown string `json:"multiplierDown,string"` + MinQty string `json:"minQty"` + MaxQty string `json:"maxQty"` + StepSize string `json:"stepSize"` + MinNotional string `json:"minNotional"` +} + +// +//type RateLimit struct { +// Interval string `json:"interval"` +// IntervalNum int64 `json:"intervalNum"` +// Limit int64 `json:"limit"` +// RateLimitType string `json:"rateLimitType"` +//} + +type TradeSymbol struct { + Symbol string `json:"symbol"` + Status string `json:"status"` + BaseAsset string `json:"baseAsset"` //基础币种 + QuoteAsset string `json:"quoteAsset"` //计价币种 + BaseAssetPrecision int `json:"baseAssetPrecision"` //基础币种小数点位数 + QuotePrecision int `json:"quotePrecision"` //价格小数点位数 添加新字段 quoteAssetPrecision。此字段和 quotePrecision 重复。在未来的版本(v4)中 quotePrecision 会被移除 + QuoteAssetPrecision int `json:"quoteAssetPrecision"` // + BaseCommissionPrecision int `json:"baseCommissionPrecision"` + QuoteCommissionPrecision int `json:"quoteCommissionPrecision"` + OrderTypes []string `json:"orderTypes"` + Filters []Filter `json:"filters"` +} +type ExchangeInfo struct { + Timezone string `json:"timezone"` + ServerTime int `json:"serverTime"` + //ExchangeFilters []interface{} `json:"exchangeFilters,omitempty"` + //RateLimits []RateLimit `json:"rateLimits"` + Symbols []TradeSymbol `json:"symbols"` +} + +// 获取exchangeInfo +func GetExchangeInfoPro() ([]TradeSymbol, error) { + respData, err := httputils.NewHttpRequestWithFasthttp("GET", apiUrl+"/api/v3/exchangeInfo", "", nil) + if err != nil { + log.Error("获取exchangeInfo", zap.Error(err)) + return nil, err + } + var info ExchangeInfo + sonic.Unmarshal(respData, &info) + + return info.Symbols, nil +} diff --git a/services/excservice/biance.go b/services/excservice/biance.go new file mode 100644 index 0000000..3bf181d --- /dev/null +++ b/services/excservice/biance.go @@ -0,0 +1,299 @@ +package excservice + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "net/url" + "strconv" + "strings" + "time" + + "go.uber.org/zap" + + "go-admin/common/global" + "go-admin/common/helper" + "go-admin/models" + "go-admin/pkg/httputils" + "go-admin/pkg/utility" + + "github.com/bytedance/sonic" + log "github.com/go-admin-team/go-admin-core/logger" +) + +const ( + TICKER_URI = "/api/v3/ticker/24hr?symbol=%s" + TICKERS_URI = "ticker/allBookTickers" + DEPTH_URI = "/api/v3/depth?symbol=%s&limit=%d" + ACCOUNT_URI = "/api/v3/account?" + ORDER_URI = "/api/v3/order" + UNFINISHED_ORDERS_INFO = "openOrders?" + KLINE_URI = "klines" + SERVER_TIME_URL = "/api/v3/time" +) + +var ( + apiUrl = "https://api.binance.com" + //如果上面的baseURL访问有性能问题,请访问下面的API集群: + //https://api1.binance.com + //https://api2.binance.com + //https://api3.binance.com +) + +func init() { + //err := setTimeOffsetPro() + //if err != nil { + // fmt.Println("setTimeOffsetPro,err:", err) + //} +} + +func buildParamsSigned(postForm *url.Values, secretKey string) { + postForm.Set("recvWindow", "60000") + tonce := strconv.FormatInt(time.Now().UnixNano()+timeOffset, 10)[0:13] + postForm.Set("timestamp", tonce) + payload := postForm.Encode() + sign, _ := GetHmacSHA256Sign(secretKey, payload) + postForm.Set("signature", sign) +} +func GetHmacSHA256Sign(secret, params string) (string, error) { + mac := hmac.New(sha256.New, []byte(secret)) + _, err := mac.Write([]byte(params)) + if err != nil { + return "", err + } + return hex.EncodeToString(mac.Sum(nil)), nil +} + +// 获取服务器时间 +func setTimeOffsetPro() error { + respData, err := httputils.NewHttpRequestWithFasthttp("GET", apiUrl+SERVER_TIME_URL, "", nil) + if err != nil { + return err + } + var bodyDataMap map[string]interface{} + err = json.Unmarshal(respData, &bodyDataMap) + if err != nil { + log.Error(string(respData)) + return err + } + stime := int64(utility.ToInt(bodyDataMap["serverTime"])) + st := time.Unix(stime/1000, 1000000*(stime%1000)) + lt := time.Now() + offset := st.Sub(lt).Nanoseconds() + timeOffset = offset + return nil +} + +// GetTicker 获取24小时行情 +func GetTicker(coin, currency string) (models.Ticker24, error) { + par := strings.ToUpper(coin + currency) + tickerUri := apiUrl + fmt.Sprintf(TICKER_URI, par) + var ticker models.Ticker24 + respData, err := httputils.NewHttpRequestWithFasthttp("GET", tickerUri, "", nil) + if err != nil { + log.Error("GetTicker", zap.Error(err)) + return ticker, err + } + var tickerMap map[string]interface{} + err = json.Unmarshal(respData, &tickerMap) + if err != nil { + log.Error("GetTicker", zap.ByteString("respData", respData), zap.Error(err)) + return ticker, err + } + + ticker.LastPrice = tickerMap["lastPrice"].(string) + ticker.LowPrice = tickerMap["lowPrice"].(string) + ticker.HighPrice = tickerMap["highPrice"].(string) + ticker.Volume = tickerMap["volume"].(string) + ticker.QuoteVolume = tickerMap["quoteVolume"].(string) + ticker.ChangePercent = tickerMap["priceChangePercent"].(string) + ticker.OpenPrice = tickerMap["openPrice"].(string) + return ticker, nil +} + +// GetTickerBySymbols 获取24小时行情 symbols symbols参数可接受的格式: ["BTCUSDT","BNBUSDT"] +func GetTickerBySymbols(symbols string) ([]models.Ticker24, error) { + tickerUri := apiUrl + "/api/v3/ticker/24hr" + + respData, err := httputils.NewHttpRequestWithFasthttp("GET", tickerUri, "", nil) + if err != nil { + log.Error("GetTicker", zap.Error(err)) + return nil, err + } + var tickerList []interface{} + err = json.Unmarshal(respData, &tickerList) + if err != nil { + log.Error("GetTickerBySymbols", zap.ByteString("respData", respData), zap.Error(err)) + return nil, err + } + list := make([]models.Ticker24, 0, len(tickerList)) + for _, t := range tickerList { + tickerMap := t.(map[string]interface{}) + if tickerMap == nil { + continue + } + var ticker models.Ticker24 + ticker.LastPrice = tickerMap["lastPrice"].(string) + ticker.LowPrice = tickerMap["lowPrice"].(string) + ticker.HighPrice = tickerMap["highPrice"].(string) + ticker.Volume = tickerMap["volume"].(string) + ticker.QuoteVolume = tickerMap["quoteVolume"].(string) + ticker.ChangePercent = tickerMap["priceChangePercent"].(string) + ticker.OpenPrice = tickerMap["openPrice"].(string) + ticker.Symbol = tickerMap["symbol"].(string) + list = append(list, ticker) + } + + return list, nil +} + +// GetKlinePro 获取k线--现货行情接口 +func GetKlinePro(coin, currency string, period string, size int) ([]models.Kline, error) { + par := strings.ToUpper(coin + currency) + periodS := period //, isOk := INERNAL_KLINE_PERIOD_CONVERTER[period] + //if isOk != true { + // periodS = "M1" + //} + key := fmt.Sprintf("%s:%s:%s", global.K_SPOT, par, period) + + //获取缓存 + klineStrs, err := helper.DefaultRedis.GetAllSortSet(key) + + if err != nil { + return nil, err + } + + if len(klineStrs) > 0 && len(klineStrs) >= 500 { + klines := make([]models.Kline, 0) + + for _, item := range klineStrs { + var kline models.Kline + err := sonic.Unmarshal([]byte(item), &kline) + + if err == nil { + klines = append(klines, kline) + } + } + + return klines, nil + } + + //没有缓存 重新获取 + + urlKline := apiUrl + "/api/v3/klines?symbol=" + par + "&interval=" + periodS + "&limit=" + utility.IntTostring(size) + respData, err := httputils.NewHttpRequestWithFasthttp("GET", urlKline, "", nil) + if err != nil { + return nil, err + } + var bodyDataMap []interface{} + err = json.Unmarshal(respData, &bodyDataMap) + if err != nil { + log.Error("GetKlinePro", zap.ByteString("respData", respData), zap.Error(err)) + return nil, err + } + var klines []models.Kline + for _, _record := range bodyDataMap { + r := models.Kline{} + record := _record.([]interface{}) + times := utility.ToFloat64(record[0]) //to unix timestramp + + // 超出10位的 处理为 + if times > 9999999999 { + r.Timestamp = int64(times) / 1000 + } else { + r.Timestamp = int64(times) + } + + r.Open = record[1].(string) + r.High = record[2].(string) + r.Low = record[3].(string) + r.Close = record[4].(string) + r.Vol = record[5].(string) + r.QuoteVolume = record[7].(string) + + klines = append(klines, r) + + member, err := sonic.Marshal(r) + + if err == nil { + err = helper.DefaultRedis.SignelAdd(key, float64(r.Timestamp), string(member)) + + if err != nil { + log.Error("保存k线数据失败:", key, err) + } + } + } + return klines, nil +} + +// GetTrades 非个人,整个交易所的交易记录 +// 注意:since is fromId +func GetTrades(coin, currency string) ([]models.NewDealPush, error) { + param := url.Values{} + param.Set("symbol", strings.ToUpper(coin+currency)) + param.Set("limit", "50") + //if since > 0 { + // param.Set("fromId", strconv.Itoa(int(since))) + //} + urlTrade := apiUrl + "/api/v3/trades?" + param.Encode() + resp, err := httputils.NewHttpRequestWithFasthttp("GET", urlTrade, "", nil) + if err != nil { + return nil, err + } + var bodyDataMap []interface{} + err = json.Unmarshal(resp, &bodyDataMap) + if err != nil { + log.Error("GetTrades", zap.ByteString("respData", resp), zap.Error(err)) + return nil, err + } + var trades []models.NewDealPush + for _, v := range bodyDataMap { + m := v.(map[string]interface{}) + ty := 2 + if m["isBuyerMaker"].(bool) { + ty = 1 + } + trades = append(trades, models.NewDealPush{ + DealId: utility.ToInt64(m["id"]), + Type: ty, + Num: utility.ToFloat64(m["qty"]), + Price: utility.ToFloat64(m["price"]), + CreateTime: utility.ToInt64(m["time"]), + }) + } + return trades, nil +} + +// GetDepth 获取深度 +func GetDepth(size int, coin, currency string) (models.DepthBin, error) { + if size <= 5 { + size = 5 + } else if size <= 10 { + size = 10 + } else if size <= 20 { + size = 20 + } else if size <= 50 { + size = 50 + } else if size <= 100 { + size = 100 + } else if size <= 500 { + size = 500 + } else { + size = 1000 + } + urlDep := fmt.Sprintf(apiUrl+DEPTH_URI, strings.ToUpper(coin+currency), size) + respFive, err := httputils.NewHttpRequestWithFasthttp("GET", urlDep, "", nil) + if err != nil { + return models.DepthBin{}, err + } + d := models.DepthBin{} + err = sonic.Unmarshal(respFive, &d) + if err != nil { + fmt.Println("GetDepth json unmarshal error for ", string(respFive), zap.Error(err)) + return models.DepthBin{}, err + } + return d, nil + +} diff --git a/services/excservice/binancereceive.go b/services/excservice/binancereceive.go new file mode 100644 index 0000000..24d3cba --- /dev/null +++ b/services/excservice/binancereceive.go @@ -0,0 +1,111 @@ +package excservice + +import ( + "go-admin/models/futuresdto" + "go-admin/pkg/utility" + "go-admin/services/binanceservice" + "strconv" + + "github.com/bytedance/sonic" + log "github.com/go-admin-team/go-admin-core/logger" +) + +/* +用户订单订阅处理 + - @msg 消息内容 + - @listenType 订阅类型 0-现货 1-合约 +*/ +func ReceiveListen(msg []byte, listenType int) (reconnect bool, err error) { + var dataMap map[string]interface{} + err = sonic.Unmarshal(msg, &dataMap) + + if err != nil { + log.Error("接收ws 反序列化失败:", err) + return + } + + event, exits := dataMap["e"] + + if !exits { + log.Error("不存在event") + return + } + + switch event { + //listenKey过期 + case "listenKeyExpired": + log.Info("listenKey过期", string(msg)) + return true, nil + + //订单变更 + case "ORDER_TRADE_UPDATE": + log.Info("ORDER_TRADE_UPDATE 推送:", string(msg)) + + //现货 + if listenType == 0 { + var mapData map[string]interface{} + err = sonic.Unmarshal(msg, &mapData) + + if err != nil { + log.Error("订单变更处理失败", err) + break + } + + utility.SafeGo(func() { + binanceservice.ChangeSpotOrder(mapData) + }) + } else { + var data futuresdto.OrderTradeUpdate + err = sonic.Unmarshal(msg, &data) + + if err != nil { + log.Error("订单变更处理失败", err) + break + } + + utility.SafeGo(func() { + binanceservice.ChangeFutureOrder(data.OrderDetails) + }) + } + //订单更新 + case "executionReport": + log.Info("executionReport 推送:", string(msg)) + + if listenType == 0 { //现货 + binanceservice.ChangeSpotOrder(dataMap) + } else if listenType == 1 { //合约 + binanceservice.ChangeFutureOrder(dataMap) + } else { + log.Error("executionReport 不支持的订阅类型", strconv.Itoa(listenType)) + } + //杠杆倍数等账户配置 更新推送 + case "ACCOUNT_CONFIG_UPDATE": + log.Info(string(msg)) + //追加保证金 + case "MARGIN_CALL": + log.Info(string(msg)) + + //条件订单(TP/SL)触发后拒绝更新推送 + case "CONDITIONAL_ORDER_TRIGGER_REJECT": + or, exits := dataMap["or"].(string) + + if exits { + var data futuresdto.OrderTriggerReject + + sonic.UnmarshalString(or, &data) + + if data.OrderNo > 0 { + log.Info("订单号【%v】止盈止损触发后被拒绝:%s", data.OrderNo, data.Reason) + } + } + + case "eventStreamTerminated": + log.Info("账户数据流被终止 type:", getWsTypeName(listenType)) + default: + log.Info("未知事件 内容:", string(msg)) + log.Info("未知事件", event) + return false, nil + } + + return false, nil +} diff --git a/services/excservice/binancesocketmanager.go b/services/excservice/binancesocketmanager.go new file mode 100644 index 0000000..b7c8c9d --- /dev/null +++ b/services/excservice/binancesocketmanager.go @@ -0,0 +1,600 @@ +package excservice + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "go-admin/common/global" + "go-admin/common/helper" + "go-admin/models/binancedto" + "go-admin/models/commondto" + "go-admin/pkg/jsonhelper" + "go-admin/pkg/utility" + "math/rand" + "net" + "net/http" + "net/url" + "os" + "os/signal" + "strconv" + "strings" + "sync" + "time" + + "github.com/bytedance/sonic" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/gorilla/websocket" + "golang.org/x/net/proxy" +) + +type BinanceWebSocketManager struct { + ws *websocket.Conn + stopChannel chan struct{} + url string + /* 0-现货 1-合约 */ + wsType int + apiKey string + apiSecret string + proxyType string + proxyAddress string + reconnect chan struct{} + isStopped bool // 标记 WebSocket 是否已主动停止 + mu sync.Mutex // 用于控制并发访问 isStopped + cancelFunc context.CancelFunc + listenKey string // 新增字段 +} + +// 已有连接 +var SpotSockets = map[string]*BinanceWebSocketManager{} +var FutureSockets = map[string]*BinanceWebSocketManager{} + +func NewBinanceWebSocketManager(wsType int, apiKey, apiSecret, proxyType, proxyAddress string) *BinanceWebSocketManager { + url := "" + + switch wsType { + case 0: + url = "wss://stream.binance.com:9443/ws" + case 1: + url = "wss://fstream.binance.com/ws" + } + + return &BinanceWebSocketManager{ + stopChannel: make(chan struct{}, 10), + reconnect: make(chan struct{}, 10), + isStopped: false, + url: url, + wsType: wsType, + apiKey: apiKey, + apiSecret: apiSecret, + proxyType: proxyType, + proxyAddress: proxyAddress, + } +} + +func (wm *BinanceWebSocketManager) Start() { + utility.SafeGo(wm.run) + // wm.run() +} + +// 重启连接 +func (wm *BinanceWebSocketManager) Restart(apiKey, apiSecret, proxyType, proxyAddress string) *BinanceWebSocketManager { + wm.apiKey = apiKey + wm.apiSecret = apiSecret + wm.proxyType = proxyType + wm.proxyAddress = proxyAddress + + wm.reconnect <- struct{}{} + return wm +} + +func Restart(wm *BinanceWebSocketManager) { + wm.reconnect <- struct{}{} +} +func (wm *BinanceWebSocketManager) run() { + ctx, cancel := context.WithCancel(context.Background()) + wm.cancelFunc = cancel + + utility.SafeGo(wm.handleSignal) + + // 计算错误记录键 + errKey := fmt.Sprintf(global.API_WEBSOCKET_ERR, wm.apiKey) + errMessage := commondto.WebSocketErr{Time: time.Now()} + helper.DefaultRedis.SetString(errKey, jsonhelper.ToJsonString(errMessage)) + + for { + select { + case <-ctx.Done(): + return + default: + if err := wm.connect(ctx); err != nil { + wm.handleConnectionError(errKey, err) + + if wm.isErrorCountExceeded(errKey) { + log.Error("连接 %s WebSocket 时出错次数过多,停止 WebSocket 管理器: %v", wm.wsType, wm.apiKey) + wm.Stop() + return + } + + time.Sleep(5 * time.Second) + continue + } + + <-wm.stopChannel + log.Info("停止 %s WebSocket 管理器...", getWsTypeName(wm.wsType)) + wm.Stop() + return + } + } +} + +// handleConnectionError 处理 WebSocket 连接错误 +func (wm *BinanceWebSocketManager) handleConnectionError(errKey string, err error) { + // 从 Redis 获取错误记录 + var errMessage commondto.WebSocketErr + val, _ := helper.DefaultRedis.GetString(errKey) + if val != "" { + sonic.UnmarshalString(val, &errMessage) + } + + // 更新错误记录 + errMessage.Count++ + errMessage.Time = time.Now() + errMessage.ErrorMessage = err.Error() + + // 将错误记录保存到 Redis + if data, err := sonic.MarshalString(errMessage); err == nil { + helper.DefaultRedis.SetString(errKey, data) + } + + // 记录错误日志 + log.Error("连接 %s WebSocket 时出错: %v, 错误: %v", wm.wsType, wm.apiKey, err) +} + +// isErrorCountExceeded 检查错误次数是否超过阈值 +func (wm *BinanceWebSocketManager) isErrorCountExceeded(errKey string) bool { + val, _ := helper.DefaultRedis.GetString(errKey) + if val == "" { + return false + } + + var errMessage commondto.WebSocketErr + if err := sonic.UnmarshalString(val, &errMessage); err != nil { + return false + } + + return errMessage.Count >= 5 +} + +// 处理终止信号 +func (wm *BinanceWebSocketManager) handleSignal() { + ch := make(chan os.Signal) + signal.Notify(ch, os.Interrupt) + <-ch + wm.Stop() +} + +func (wm *BinanceWebSocketManager) connect(ctx context.Context) error { + dialer, err := wm.getDialer() + if err != nil { + return err + } + + listenKey, err := wm.getListenKey() + if err != nil { + return err + } + + wm.listenKey = listenKey + url := fmt.Sprintf("%s/%s", wm.url, listenKey) + wm.ws, _, err = dialer.Dial(url, nil) + if err != nil { + return err + } + + log.Info(fmt.Sprintf("已连接到 Binance %s WebSocket【%s】 key:%s", getWsTypeName(wm.wsType), wm.apiKey, listenKey)) + + // Ping处理 + wm.ws.SetPingHandler(func(appData string) error { + log.Info(fmt.Sprintf("收到 wstype: %v key:%s Ping 消息【%s】", wm.wsType, wm.apiKey, appData)) + + for x := 0; x < 5; x++ { + if err := wm.ws.WriteControl(websocket.PongMessage, []byte(appData), time.Now().Add(time.Second*10)); err != nil { + log.Error("binance 回应pong失败 次数:", strconv.Itoa(x), " err:", err) + + time.Sleep(time.Second * 1) + continue + } + + break + } + + setLastTime(wm) + return nil + }) + + // utility.SafeGoParam(wm.restartConnect, ctx) + utility.SafeGo(func() { wm.startListenKeyRenewal2(ctx) }) + utility.SafeGo(func() { wm.readMessages(ctx) }) + utility.SafeGo(func() { wm.handleReconnect(ctx) }) + + return nil +} + +// 更新最后通信时间 +func setLastTime(wm *BinanceWebSocketManager) { + subKey := fmt.Sprintf(global.USER_SUBSCRIBE, wm.apiKey) + val, _ := helper.DefaultRedis.GetString(subKey) + now := time.Now() + var data binancedto.UserSubscribeState + if val != "" { + sonic.Unmarshal([]byte(val), &data) + } + + if wm.wsType == 0 { + data.SpotLastTime = &now + } else { + data.FuturesLastTime = &now + } + + val, _ = sonic.MarshalString(&data) + + if val != "" { + helper.DefaultRedis.SetString(subKey, val) + } +} + +func (wm *BinanceWebSocketManager) getDialer() (*websocket.Dialer, error) { + if wm.proxyAddress == "" { + return &websocket.Dialer{}, nil + } + + if !strings.HasPrefix(wm.proxyAddress, "http://") && !strings.HasPrefix(wm.proxyAddress, "https://") && !strings.HasPrefix(wm.proxyAddress, "socks5://") { + wm.proxyAddress = wm.proxyType + "://" + wm.proxyAddress + } + + proxyURL, err := url.Parse(wm.proxyAddress) + if err != nil { + return nil, fmt.Errorf("failed to parse proxy URL: %v", err) + } + + transport := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: false}, + } + + switch proxyURL.Scheme { + case "socks5": + return wm.createSocks5Dialer(proxyURL) + case "http", "https": + transport.Proxy = http.ProxyURL(proxyURL) + return &websocket.Dialer{Proxy: transport.Proxy, TLSClientConfig: transport.TLSClientConfig}, nil + default: + return nil, fmt.Errorf("unsupported proxy scheme: %s", proxyURL.Scheme) + } +} + +func (wm *BinanceWebSocketManager) createSocks5Dialer(proxyURL *url.URL) (*websocket.Dialer, error) { + auth := &proxy.Auth{} + if proxyURL.User != nil { + auth.User = proxyURL.User.Username() + auth.Password, _ = proxyURL.User.Password() + } + + socksDialer, err := proxy.SOCKS5("tcp", proxyURL.Host, auth, proxy.Direct) + if err != nil { + return nil, fmt.Errorf("failed to create SOCKS5 proxy dialer: %v", err) + } + + return &websocket.Dialer{ + NetDialContext: func(ctx context.Context, network, address string) (net.Conn, error) { + return socksDialer.Dial(network, address) + }, + }, nil +} + +// 复用创建HTTP客户端的逻辑 +func (wm *BinanceWebSocketManager) createBinanceClient() (*helper.BinanceClient, error) { + return helper.NewBinanceClient(wm.apiKey, wm.apiSecret, wm.proxyType, wm.proxyAddress) +} + +// 获取listenKey +func (wm *BinanceWebSocketManager) getListenKey() (string, error) { + client, err := wm.createBinanceClient() + if err != nil { + return "", err + } + + var resp []byte + switch wm.wsType { + case 0: + resp, _, err = client.SendSpotRequestByKey("/api/v3/userDataStream", "POST", nil) + case 1: + resp, _, err = client.SendFuturesRequestByKey("/fapi/v1/listenKey", "POST", nil) + + default: + log.Error("链接类型错误") + return "", errors.New("链接类型错误") + } + + if err != nil { + return "", err + } + + var dataMap map[string]interface{} + if err := sonic.Unmarshal(resp, &dataMap); err != nil { + return "", err + } + + if listenKey, ok := dataMap["listenKey"]; ok { + return listenKey.(string), nil + } + + return "", errors.New("listenKey 不存在") +} + +// 接收消息 +func (wm *BinanceWebSocketManager) readMessages(ctx context.Context) { + defer wm.ws.Close() + + for { + select { + case <-ctx.Done(): + return + default: + if wm.isStopped { + return + } + + _, msg, err := wm.ws.ReadMessage() + if err != nil && strings.Contains(err.Error(), "websocket: close") { + if !wm.isStopped { + wm.reconnect <- struct{}{} + } + + log.Error("websocket 关闭") + return + } else if err != nil { + log.Error("读取消息时出错: %v", err) + return + } + wm.handleOrderUpdate(msg) + + } + } +} + +func (wm *BinanceWebSocketManager) handleOrderUpdate(msg []byte) { + setLastTime(wm) + + if reconnect, _ := ReceiveListen(msg, wm.wsType); reconnect { + wm.reconnect <- struct{}{} + } +} + +func (wm *BinanceWebSocketManager) Stop() { + wm.mu.Lock() + defer wm.mu.Unlock() + + if wm.isStopped { + return + } + + wm.isStopped = true + close(wm.stopChannel) + + if wm.cancelFunc != nil { + wm.cancelFunc() + } + + if wm.ws != nil { + if err := wm.ws.Close(); err != nil { + log.Error(fmt.Sprintf("key【%s】close失败", wm.apiKey), err) + } else { + log.Info(fmt.Sprintf("key【%s】close", wm.apiKey)) + } + } +} + +// 重连机制 +func (wm *BinanceWebSocketManager) handleReconnect(ctx context.Context) { + maxRetries := 5 // 最大重试次数 + retryCount := 0 + + for { + select { + case <-ctx.Done(): + return + case <-wm.reconnect: + if wm.isStopped { + return + } + + log.Warn("WebSocket 连接断开,尝试重连...") + + if wm.ws != nil { + wm.ws.Close() + } + + // 取消旧的上下文 + if wm.cancelFunc != nil { + wm.cancelFunc() + } + + for { + newCtx, cancel := context.WithCancel(context.Background()) + wm.cancelFunc = cancel // 更新 cancelFunc + + if err := wm.connect(newCtx); err != nil { + log.Errorf("重连失败: %v", err) + cancel() + retryCount++ + + if retryCount >= maxRetries { + log.Error("重连失败次数过多,退出重连逻辑") + return + } + + time.Sleep(5 * time.Second) + continue + } + + return + } + } + } +} + +// 定期删除listenkey 并重启ws +func (wm *BinanceWebSocketManager) startListenKeyRenewal(ctx context.Context, listenKey string) { + time.Sleep(30 * time.Minute) + + select { + case <-ctx.Done(): + return + default: + if err := wm.deleteListenKey(listenKey); err != nil { + log.Error("Failed to renew listenKey: ,type:%v key: %s", wm.wsType, wm.apiKey, err) + } else { + log.Debug("Successfully delete listenKey") + wm.reconnect <- struct{}{} + } + } + + // ticker := time.NewTicker(5 * time.Minute) + // defer ticker.Stop() + + // for { + // select { + // case <-ticker.C: + // if wm.isStopped { + // return + // } + + // if err := wm.deleteListenKey(listenKey); err != nil { + // log.Error("Failed to renew listenKey: ,type:%v key: %s", wm.wsType, wm.apiKey, err) + // } else { + // log.Debug("Successfully delete listenKey") + // wm.reconnect <- struct{}{} + // return + // } + // case <-ctx.Done(): + // return + // } + // } +} + +// 定时续期 +func (wm *BinanceWebSocketManager) startListenKeyRenewal2(ctx context.Context) { + ticker := time.NewTicker(30 * time.Minute) + defer func() { + log.Debug("定时续期任务退出 key:", wm.apiKey) + ticker.Stop() + }() + + for { + select { + case <-ticker.C: + if wm.isStopped { + return + } + + if err := wm.renewListenKey(wm.listenKey); err != nil { + log.Error("Failed to renew listenKey: ,type:%v key: %s", wm.wsType, wm.apiKey, err) + } + case <-ctx.Done(): + return + } + } +} + +/* +删除listenkey +*/ +func (wm *BinanceWebSocketManager) deleteListenKey(listenKey string) error { + client, err := wm.createBinanceClient() + if err != nil { + return err + } + + var resp []byte + + switch wm.wsType { + case 0: + path := fmt.Sprintf("/api/v3/userDataStream") + params := map[string]interface{}{ + "listenKey": listenKey, + } + resp, _, err = client.SendSpotRequestByKey(path, "DELETE", params) + + log.Debug(fmt.Sprintf("deleteListenKey resp: %s", string(resp))) + case 1: + resp, _, err = client.SendFuturesRequestByKey("/fapi/v1/listenKey", "DELETE", nil) + log.Debug(fmt.Sprintf("deleteListenKey resp: %s", string(resp))) + default: + return errors.New("unknown ws type") + } + + return err +} + +func (wm *BinanceWebSocketManager) renewListenKey(listenKey string) error { + // payloadParam := map[string]interface{}{ + // "listenKey": listenKey, + // "apiKey": wm.apiKey, + // } + + // params := map[string]interface{}{ + // "id": getUUID(), + // "method": "userDataStream.ping", + // "params": payloadParam, + // } + + // if err := wm.ws.WriteJSON(params); err != nil { + // return err + // } + // wm.ws.WriteJSON() + client, err := wm.createBinanceClient() + if err != nil { + return err + } + + var resp []byte + + switch wm.wsType { + case 0: + path := fmt.Sprintf("/api/v3/userDataStream?listenKey=%s", listenKey) + resp, _, err = client.SendSpotRequestByKey(path, "PUT", nil) + log.Debug(fmt.Sprintf("renewListenKey resp: %s", string(resp))) + case 1: + // path := fmt.Sprintf("/fapi/v1/listenKey", listenKey) + resp, _, err = client.SendFuturesRequestByKey("/fapi/v1/listenKey", "PUT", nil) + log.Debug(fmt.Sprintf("renewListenKey resp: %s", string(resp))) + default: + return errors.New("unknown ws type") + } + return nil +} + +func getWsTypeName(wsType int) string { + switch wsType { + case 0: + return "spot" + case 1: + return "futures" + default: + return "unknown" + } +} +func getUUID() string { + return fmt.Sprintf("%s-%s-%s-%s-%s", randomHex(8), randomHex(4), randomHex(4), randomHex(4), randomHex(12)) +} + +func randomHex(n int) string { + rand.New(rand.NewSource(time.Now().UnixNano())) + hexChars := "0123456789abcdef" + bytes := make([]byte, n) + for i := 0; i < n; i++ { + bytes[i] = hexChars[rand.Intn(len(hexChars))] + } + return string(bytes) +} diff --git a/services/excservice/binancews.go b/services/excservice/binancews.go new file mode 100644 index 0000000..a0547a4 --- /dev/null +++ b/services/excservice/binancews.go @@ -0,0 +1,175 @@ +package excservice + +import ( + "errors" + "fmt" + "strconv" + "time" + + "go-admin/models" + "go-admin/pkg/timehelper" + "go-admin/pkg/utility" + + "github.com/bytedance/sonic" +) + +type BinanceWs struct { + baseURL string + combinedBaseURL string + proxyUrl string + WorkType string + wsConns []*WsConn + tickerCallback func(models.Ticker24, string, string) + forceCallback func(models.ForceOrder, string, string) + depthCallback func(models.DepthBin, string, string) + tradeCallback func(models.NewDealPush, string, string) + klineCallback func(models.Kline, int, string, string) + allBack func(msg []byte) + allBackKline func(msg []byte, tradeSet models.TradeSet) +} + +func NewBinanceWs(wsbaseURL, proxyUrl string) *BinanceWs { + return &BinanceWs{ + baseURL: wsbaseURL, + combinedBaseURL: "wss://stream.binance.com:9443/stream?streams=", + proxyUrl: proxyUrl, + } +} + +func (bnWs *BinanceWs) SetProxyUrl(proxyUrl string) { + bnWs.proxyUrl = proxyUrl +} + +func (bnWs *BinanceWs) SetBaseUrl(baseURL string) { + bnWs.baseURL = baseURL +} + +func (bnWs *BinanceWs) SetCombinedBaseURL(combinedBaseURL string) { + bnWs.combinedBaseURL = combinedBaseURL +} + +func (bnWs *BinanceWs) SetAllCallbacks(allBack func(msg []byte), allBackKline func(msg []byte, tradeSet models.TradeSet)) { + if bnWs.allBack == nil { + bnWs.allBack = allBack + } + + if bnWs.allBackKline == nil { + bnWs.allBackKline = allBackKline + } +} + +// 订阅通用函数 +func (bnWs *BinanceWs) subscribe(endpoint string, handle func(msg []byte) error) { + wsConn := NewWsBuilder(). + WsUrl(endpoint). + AutoReconnect(). + ProtoHandleFunc(handle). + ProxyUrl(bnWs.proxyUrl). + ReconnectInterval(time.Millisecond * 5). + Build() + if wsConn == nil { + return + } + bnWs.wsConns = append(bnWs.wsConns, wsConn) + go bnWs.exitHandler(wsConn) +} + +func (bnWs *BinanceWs) Close() { + for _, con := range bnWs.wsConns { + con.CloseWs() + } +} + +func (bnWs *BinanceWs) Subscribe(streamName string, tradeSet models.TradeSet, callback func(msg []byte, tradeSet models.TradeSet)) error { + endpoint := bnWs.baseURL + streamName + handle := func(msg []byte) error { + callback(msg, tradeSet) + return nil + } + bnWs.subscribe(endpoint, handle) + return nil +} + +func (bnWs *BinanceWs) exitHandler(c *WsConn) { + pingTicker := time.NewTicker(1 * time.Minute) + pongTicker := time.NewTicker(30 * time.Second) + defer func() { + pingTicker.Stop() + pongTicker.Stop() + c.CloseWs() + + if err := recover(); err != nil { + fmt.Printf("CloseWs, panic: %s\r\n", err) + } + }() + + for { + select { + case t := <-pingTicker.C: + c.SendPingMessage([]byte(strconv.Itoa(int(t.UnixNano() / int64(time.Millisecond))))) + case t := <-pongTicker.C: + c.SendPongMessage([]byte(strconv.Itoa(int(t.UnixNano() / int64(time.Millisecond))))) + } + } +} + +func parseJsonToMap(msg []byte) (map[string]interface{}, error) { + datamap := make(map[string]interface{}) + err := sonic.Unmarshal(msg, &datamap) + return datamap, err +} + +func handleForceOrder(msg []byte, tradeSet models.TradeSet, callback func(models.ForceOrder, string, string)) error { + datamap, err := parseJsonToMap(msg) + if err != nil { + return fmt.Errorf("json unmarshal error: %v", err) + } + + msgType, ok := datamap["e"].(string) + if !ok || msgType != "forceOrder" { + return errors.New("unknown message type") + } + + datamapo := datamap["o"].(map[string]interface{}) + order := models.ForceOrder{ + Side: datamapo["S"].(string), + Symbol: datamapo["s"].(string), + Ordertype: datamapo["o"].(string), + TimeInForce: datamapo["f"].(string), + Num: utility.ToFloat64(datamapo["q"]), + Price: utility.ToFloat64(datamapo["p"]), + AvgPrice: utility.ToFloat64(datamapo["ap"]), + State: datamapo["X"].(string), + CreateTime: timehelper.IntToTime(utility.ToInt64(datamapo["T"])), + } + callback(order, tradeSet.Coin, tradeSet.Currency) + return nil +} + +// SubscribeAll 订阅 组合streams的URL格式为 /stream?streams=// +// 订阅组合streams时,事件payload会以这样的格式封装: {"stream":"","data":} +// 单一原始 streams 格式为 /ws/ +func (bnWs *BinanceWs) SubscribeAll(streamName string) error { + endpoint := bnWs.baseURL + streamName + + handle := func(msg []byte) error { + bnWs.allBack(msg) + return nil + } + + bnWs.subscribe(endpoint, handle) + return nil +} + +// SubscribeAllKline 订阅kline推送 组合streams的URL格式为 /stream?streams=// +func (bnWs *BinanceWs) SubscribeAllKline(streamName string, tradeSet models.TradeSet) error { + endpoint := bnWs.baseURL + streamName + + handle := func(msg []byte) error { + bnWs.allBackKline(msg, tradeSet) + return nil + } + + bnWs.subscribe(endpoint, handle) + return nil +} diff --git a/services/excservice/websocket.go b/services/excservice/websocket.go new file mode 100644 index 0000000..2601bdb --- /dev/null +++ b/services/excservice/websocket.go @@ -0,0 +1,442 @@ +package excservice + +import ( + "errors" + "fmt" + "net/http" + "net/http/httputil" + "net/url" + "sync" + "time" + + "go.uber.org/zap" + + "go-admin/pkg/utility" + + "github.com/bytedance/sonic" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/gorilla/websocket" +) + +type WsConfig struct { + WsUrl string + ProxyUrl string + ReqHeaders map[string][]string //连接的时候加入的头部信息 + HeartbeatIntervalTime time.Duration // + HeartbeatData func() []byte //心跳数据2 + IsAutoReconnect bool + ProtoHandleFunc func([]byte) error //协议处理函数 + DecompressFunc func([]byte) ([]byte, error) //解压函数 + ErrorHandleFunc func(err error) + ConnectSuccessAfterSendMessage func() []byte //for reconnect + IsDump bool + readDeadLineTime time.Duration + reconnectInterval time.Duration +} + +var dialer = &websocket.Dialer{ + Proxy: http.ProxyFromEnvironment, + HandshakeTimeout: 30 * time.Second, + EnableCompression: true, +} + +type WsConn struct { + c *websocket.Conn + WsConfig + writeBufferChan chan []byte + pingMessageBufferChan chan []byte + pongMessageBufferChan chan []byte + closeMessageBufferChan chan []byte + subs [][]byte + close chan bool + reConnectLock *sync.Mutex +} + +type WsBuilder struct { + wsConfig *WsConfig +} + +func NewWsBuilder() *WsBuilder { + return &WsBuilder{&WsConfig{ + ReqHeaders: make(map[string][]string, 1), + reconnectInterval: time.Second * 10, + }} +} + +func (b *WsBuilder) WsUrl(wsUrl string) *WsBuilder { + b.wsConfig.WsUrl = wsUrl + return b +} + +func (b *WsBuilder) ProxyUrl(proxyUrl string) *WsBuilder { + b.wsConfig.ProxyUrl = proxyUrl + return b +} + +func (b *WsBuilder) ReqHeader(key, value string) *WsBuilder { + b.wsConfig.ReqHeaders[key] = append(b.wsConfig.ReqHeaders[key], value) + return b +} + +func (b *WsBuilder) AutoReconnect() *WsBuilder { + b.wsConfig.IsAutoReconnect = true + return b +} + +func (b *WsBuilder) Dump() *WsBuilder { + b.wsConfig.IsDump = true + return b +} + +func (b *WsBuilder) Heartbeat(heartbeat func() []byte, t time.Duration) *WsBuilder { + b.wsConfig.HeartbeatIntervalTime = t + b.wsConfig.HeartbeatData = heartbeat + return b +} + +func (b *WsBuilder) ReconnectInterval(t time.Duration) *WsBuilder { + b.wsConfig.reconnectInterval = t + return b +} + +func (b *WsBuilder) ProtoHandleFunc(f func([]byte) error) *WsBuilder { + b.wsConfig.ProtoHandleFunc = f + return b +} + +func (b *WsBuilder) DecompressFunc(f func([]byte) ([]byte, error)) *WsBuilder { + b.wsConfig.DecompressFunc = f + return b +} + +func (b *WsBuilder) ErrorHandleFunc(f func(err error)) *WsBuilder { + b.wsConfig.ErrorHandleFunc = f + return b +} + +func (b *WsBuilder) ConnectSuccessAfterSendMessage(msg func() []byte) *WsBuilder { + b.wsConfig.ConnectSuccessAfterSendMessage = msg + return b +} + +func (b *WsBuilder) Build() *WsConn { + wsConn := &WsConn{WsConfig: *b.wsConfig} + return wsConn.NewWs() +} + +func (ws *WsConn) NewWs() *WsConn { + if ws.HeartbeatIntervalTime == 0 { + ws.readDeadLineTime = time.Minute + } else { + ws.readDeadLineTime = ws.HeartbeatIntervalTime * 2 + } + + if err := ws.connect(); err != nil { + log.Error("[" + ws.WsUrl + "] " + err.Error()) + return nil + } + + ws.close = make(chan bool, 1) + ws.pingMessageBufferChan = make(chan []byte, 10) + ws.pongMessageBufferChan = make(chan []byte, 10) + ws.closeMessageBufferChan = make(chan []byte, 10) + ws.writeBufferChan = make(chan []byte, 10) + ws.reConnectLock = new(sync.Mutex) + + go ws.writeRequest() + go ws.receiveMessage() + + //if ws.ConnectSuccessAfterSendMessage != nil { + // msg := ws.ConnectSuccessAfterSendMessage() + // if msg != nil{ + // ws.SendMessage(msg) + // log.ErrorLogMsg("[ws] " + ws.WsUrl + " execute the connect success after send message=" + string(msg)) + // }else { + // log.ErrorLogMsg("执行重新连接后执行的登入函数[ws] " + ws.WsUrl + " ,send message=" + string(msg)) + // } + //} + + return ws +} + +func (ws *WsConn) connect() error { + const maxRetries = 5 // 最大重试次数 + const retryDelay = 2 * time.Second // 每次重试的延迟时间 + + var wsConn *websocket.Conn + var resp *http.Response + var err error + + // 重试机制 + for attempt := 1; attempt <= maxRetries; attempt++ { + if ws.ProxyUrl != "" { + proxy, err := url.Parse(ws.ProxyUrl) + if err == nil { + // log.Info("[ws][%s] proxy url:%s", zap.String("ws.WsUrl", ws.WsUrl)) + dialer.Proxy = http.ProxyURL(proxy) + } else { + log.Error("[ws][" + ws.WsUrl + "] parse proxy url [" + ws.ProxyUrl + "] err: " + err.Error()) + } + } + + // 尝试连接 + wsConn, resp, err = dialer.Dial(ws.WsUrl, http.Header(ws.ReqHeaders)) + if err != nil { + log.Error(fmt.Sprintf("[ws][%s] Dial attempt %d failed: %s", ws.WsUrl, attempt, err.Error())) + + // 如果开启了请求数据转储,打印响应信息 + if ws.IsDump && resp != nil { + dumpData, _ := httputil.DumpResponse(resp, true) + log.Info(fmt.Sprintf("[ws][%s] Response dump: %s", ws.WsUrl, string(dumpData))) + } + + // 达到最大重试次数,返回错误 + if attempt == maxRetries { + return fmt.Errorf("达到最大重试次数 [ws][%s]: %v", ws.WsUrl, err) + } + + // 等待一段时间后重试 + time.Sleep(retryDelay) + } else { + // 连接成功,退出循环 + break + } + } + + // 设置读取超时时间 + wsConn.SetReadDeadline(time.Now().Add(ws.readDeadLineTime)) + + // 如果开启了请求数据转储,打印响应信息 + if ws.IsDump && resp != nil { + dumpData, _ := httputil.DumpResponse(resp, true) + log.Info(fmt.Sprintf("[ws][%s] Response dump: %s", ws.WsUrl, string(dumpData))) + } + + // 记录连接成功的日志 + log.Info("[ws][" + ws.WsUrl + "] connected") + ws.c = wsConn + return nil +} + +func (ws *WsConn) reconnect() { + ws.reConnectLock.Lock() + defer ws.reConnectLock.Unlock() + + ws.c.Close() //主动关闭一次 + var err error + for retry := 1; retry <= 100; retry++ { + err = ws.connect() + if err != nil { + log.Error("[ws] [" + ws.WsUrl + "] websocket reconnect fail , " + err.Error()) + } else { + break + } + time.Sleep(ws.WsConfig.reconnectInterval * time.Duration(retry)) + } + + if err != nil { + log.Error("[ws] [" + ws.WsUrl + "] retry connect 100 count fail , begin exiting. ") + ws.CloseWs() + if ws.ErrorHandleFunc != nil { + ws.ErrorHandleFunc(errors.New("retry reconnect fail")) + } + } else { + //re subscribe + if ws.ConnectSuccessAfterSendMessage != nil { + msg := ws.ConnectSuccessAfterSendMessage() + if msg != nil { + ws.SendMessage(msg) + //log.ErrorLogMsg("[ws] " + ws.WsUrl + " execute the connect success after send message=" + string(msg)) + } else { + log.Error("执行重新连接后执行的登入函数[ws] " + ws.WsUrl + " ,send message=" + string(msg)) + } + //ws.SendMessage(msg) + //log.InfoLog("[ws] [" + ws.WsUrl + "] execute the connect success after send message=" + string(msg)) + time.Sleep(time.Second) //wait response + } + + for _, sub := range ws.subs { + log.Info("[ws] re subscribe: " + string(sub)) + ws.SendMessage(sub) + } + } +} + +func (ws *WsConn) writeRequest() { + var ( + heartTimer *time.Timer + err error + ) + + if ws.HeartbeatIntervalTime == 0 { + heartTimer = time.NewTimer(time.Hour) + } else { + heartTimer = time.NewTimer(ws.HeartbeatIntervalTime) + } + + for { + select { + case <-ws.close: + log.Info("[ws][" + ws.WsUrl + "] close websocket , exiting write message goroutine.") + return + case d := <-ws.writeBufferChan: + err = ws.c.WriteMessage(websocket.TextMessage, d) + case d := <-ws.pingMessageBufferChan: + err = ws.c.WriteMessage(websocket.PingMessage, d) + case d := <-ws.pongMessageBufferChan: + err = ws.c.WriteMessage(websocket.PongMessage, d) + case d := <-ws.closeMessageBufferChan: + err = ws.c.WriteMessage(websocket.CloseMessage, d) + case <-heartTimer.C: + if ws.HeartbeatIntervalTime > 0 { + err = ws.c.WriteMessage(websocket.TextMessage, ws.HeartbeatData()) + heartTimer.Reset(ws.HeartbeatIntervalTime) + } + } + + if err != nil { + log.Info("[ws][" + ws.WsUrl + "] write message " + err.Error()) + //time.Sleep(time.Second) + } + } +} + +func (ws *WsConn) Subscribe(subEvent interface{}) error { + data, err := sonic.Marshal(subEvent) + if err != nil { + log.Error("[ws]["+ws.WsUrl+"] json encode error , ", zap.Error(err)) + return err + } + //ws.writeBufferChan <- data + ws.SendMessage(data) + ws.subs = append(ws.subs, data) + return nil +} + +func (ws *WsConn) SendMessage(msg []byte) { + defer func() { + //打印panic的错误信息 + if err := recover(); err != nil { //产生了panic异常 + fmt.Printf("SendMessage,panic: %s\r\n", err) + } + }() + ws.writeBufferChan <- msg +} + +func (ws *WsConn) SendPingMessage(msg []byte) { + ws.pingMessageBufferChan <- msg +} + +func (ws *WsConn) SendPongMessage(msg []byte) { + ws.pongMessageBufferChan <- msg +} + +func (ws *WsConn) SendCloseMessage(msg []byte) { + ws.closeMessageBufferChan <- msg +} + +func (ws *WsConn) SendJsonMessage(m interface{}) error { + data, err := sonic.Marshal(m) + if err != nil { + return err + } + //ws.writeBufferChan <- data + ws.SendMessage(data) + return nil +} + +func (ws *WsConn) receiveMessage() { + //exit + ws.c.SetCloseHandler(func(code int, text string) error { + log.Info("[ws][" + ws.WsUrl + "] websocket exiting [code=" + utility.IntTostring(code) + " , text=" + text + "]") + //ws.CloseWs() + return nil + }) + + ws.c.SetPongHandler(func(pong string) error { + // log.Info("[" + ws.WsUrl + "] received [pong] " + pong) + ws.c.SetReadDeadline(time.Now().Add(ws.readDeadLineTime)) + return nil + }) + + ws.c.SetPingHandler(func(ping string) error { + // log.Info("[" + ws.WsUrl + "] received [ping] " + ping) + ws.c.SetReadDeadline(time.Now().Add(ws.readDeadLineTime)) + return nil + }) + + for { + select { + case <-ws.close: + log.Info("[ws][" + ws.WsUrl + "] close websocket , exiting receive message goroutine.") + return + default: + t, msg, err := ws.c.ReadMessage() + if err != nil { + log.Info("ws.c.ReadMessage[ws][" + ws.WsUrl + "] " + err.Error()) + if ws.IsAutoReconnect { + log.Info("[ws][" + ws.WsUrl + "] Unexpected Closed , Begin Retry Connect.") + ws.reconnect() + continue + } + + if ws.ErrorHandleFunc != nil { + ws.ErrorHandleFunc(err) + } + + return + } + // Log.Debug(string(msg)) + ws.c.SetReadDeadline(time.Now().Add(ws.readDeadLineTime)) + switch t { + case websocket.TextMessage: + ws.ProtoHandleFunc(msg) + case websocket.BinaryMessage: + if ws.DecompressFunc == nil { + ws.ProtoHandleFunc(msg) + } else { + msg2, err := ws.DecompressFunc(msg) + if err != nil { + log.Error("[ws] decompress error " + ws.WsUrl + err.Error()) + } else { + ws.ProtoHandleFunc(msg2) + } + } + // case websocket.CloseMessage: + // ws.CloseWs() + default: + log.Error("[ws][" + ws.WsUrl + "] error websocket message type , content is " + string(msg)) + } + } + } +} + +func (ws *WsConn) CloseWs() { + defer func() { + //打印panic的错误信息 + if err := recover(); err != nil { //产生了panic异常 + fmt.Printf("CloseWs,panic: %s\r\n", err) + } + }() + //ws.close <- true + close(ws.close) + close(ws.writeBufferChan) + close(ws.closeMessageBufferChan) + close(ws.pingMessageBufferChan) + close(ws.pongMessageBufferChan) + + err := ws.c.Close() + if err != nil { + log.Error("CloseWs[ws]["+ws.WsUrl+"] close websocket error ,", zap.Error(err)) + } +} + +func (ws *WsConn) clearChannel(c chan struct{}) { + for { + if len(c) > 0 { + <-c + } else { + break + } + } +} diff --git a/services/fileservice/clearlogs.go b/services/fileservice/clearlogs.go new file mode 100644 index 0000000..7887567 --- /dev/null +++ b/services/fileservice/clearlogs.go @@ -0,0 +1,73 @@ +package fileservice + +import ( + "fmt" + "go-admin/app/admin/models" + "os" + "path/filepath" + "strconv" + "time" + + "github.com/go-admin-team/go-admin-core/sdk/config" + "gorm.io/gorm" +) + +func ClearLogs(orm *gorm.DB) { + dir := config.LoggerConfig.Path + + if dir == "" { + dir = "temp/logs" + } + + // 检查文件夹是否存在 + if _, err := os.Stat(dir); os.IsNotExist(err) { + fmt.Printf("Directory %s does not exist, skipping cleanup.\n", dir) + return + } + + // 获取当前时间 + now := time.Now() + expirateDay := 7 + var sysConfig models.SysConfig + + orm.Model(&sysConfig).Where("config_key = ?", "log_expirate_date").First(&sysConfig) + + if sysConfig.ConfigValue != "" { + day, _ := strconv.Atoi(sysConfig.ConfigValue) + + if day > 0 { + expirateDay = day + } + } + + // 遍历指定文件夹中的所有文件 + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + // 只处理普通文件 + if !info.IsDir() { + // 获取文件的修改时间 + modTime := info.ModTime() + + // 计算文件修改时间与当前时间的差值 + duration := now.Sub(modTime) + + // 如果文件超过7天,则删除 + if duration > time.Duration(expirateDay)*24*time.Hour { + fmt.Printf("Deleting file: %s (Last modified: %s)\n", path, modTime) + err := os.Remove(path) + if err != nil { + return err + } + } + } + + return nil + }) + + if err != nil { + fmt.Printf("Error walking the path %v: %v\n", dir, err) + } +} diff --git a/services/futureservice/binancemarket.go b/services/futureservice/binancemarket.go new file mode 100644 index 0000000..5b719f1 --- /dev/null +++ b/services/futureservice/binancemarket.go @@ -0,0 +1,234 @@ +package futureservice + +import ( + "bytes" + "errors" + "fmt" + "go-admin/common/const/rediskey" + "go-admin/common/global" + "go-admin/common/helper" + "go-admin/config" + "go-admin/pkg/utility" + "go-admin/services/binanceservice" + "go-admin/services/excservice" + "sync" + + "go-admin/models" + + log "github.com/go-admin-team/go-admin-core/logger" + + "github.com/bytedance/sonic" + "go.uber.org/zap" +) + +var ( + baseBinanceWsUrlAll = "wss://fstream.binance.com/stream?streams=" + wsBin *excservice.BinanceWs + binSetKey = make(map[string]bool) + binSetKeyMu sync.RWMutex + + quoteAssetSymbols = []string{"USDT"} +) + +type BaseWsDepthStream struct { + Stream string `json:"stream"` // + Data models.UFuturesDepthBin `json:"data"` //数据 +} + +// StartBinanceProWs 启动币安现货市场推送 +// workType: normal-常规任务 trigger-主动触发任务 +func StartBinanceProWs(workType string) { + if wsBin == nil { + wsBin = excservice.NewBinanceWs(baseBinanceWsUrlAll, "") + } + + if wsBin == nil { + log.Error("实例化wsBin失败") + return + } + + if wsBin != nil && config.ExtConfig.ProxyUrl != "" { + wsBin.SetProxyUrl(config.ExtConfig.ProxyUrl) + } + wsBin.WorkType = workType + + wsBin.SetAllCallbacks(HandleWsAll, HandleWsAllKline) + //订阅所有行情 + subscribeAll(wsBin, "!miniTicker@arr") + +} + +func subscribeAll(ws *excservice.BinanceWs, subscribe string) { + err := ws.SubscribeAll(subscribe) + + if err != nil { + log.Error("订阅流失败", zap.String("streams", subscribe), zap.Error(err)) + } else { + log.Info("发起订阅", subscribe) + } +} + +// HandleWsAll 处理从WebSocket接收到的消息 +func HandleWsAll(msg []byte) { + + if bytes.Contains(msg, []byte("miniTicker@arr")) { + handleTickerAllMessage(msg) + } +} + +/* +根据ws 推送值获取 symbol 和交易对信息 + - @dataMap 数据源 dataMap和symbol二选一 + - @symbol 交易对 dataMap和symbol二选一 +*/ +func getWsRespTradeSet(dataMap map[string]interface{}, symbol string, tradeSet *models.TradeSet) (string, error) { + if symbol == "" { + symbol = dataMap["s"].(string) + + if symbol == "" { + return symbol, errors.New("交易对为空") + } + } + cacheStr, err := helper.DefaultRedis.GetString(fmt.Sprintf(global.TICKER_FUTURES, global.EXCHANGE_BINANCE, symbol)) + + if err != nil { + // log.Error("获取缓存失败", symbol, err) + return symbol, errors.New("获取缓存失败 " + err.Error()) + } + + err = sonic.Unmarshal([]byte(cacheStr), tradeSet) + + if err != nil { + return symbol, errors.New("对象转换失败 " + err.Error()) + } + + return symbol, nil +} + +// handleTickerMessage 处理ticker@all消息 +func handleTickerAllMessage(msg []byte) { + dataAll := tickerAllMessage{} + if err := sonic.Unmarshal(msg, &dataAll); err != nil { + log.Error("解码ticker@all消息失败", zap.Error(err)) + return + } + // dataMap, ok := dataAll["data"].([]map[string]interface{}) + if len(dataAll.Data) <= 0 { + log.Error("ticker消息不包含有效数据字段") + return + } + + pairs := make([]map[string]interface{}, 0) + trades := make([]models.TradeSet, 0) + pairVal, _ := helper.DefaultRedis.GetString(rediskey.FutSymbolTicker) + + if pairVal != "" { + err := sonic.Unmarshal([]byte(pairVal), &pairs) + if err != nil { + log.Error("获取redis数据失败", zap.Error(err)) + } + } + + for _, data := range dataAll.Data { + symbol := data["s"].(string) + + if symbol == "" || !utility.HasSuffix(symbol, quoteAssetSymbols) { + continue + } + + tradeSet := models.TradeSet{} + symbol, err := getWsRespTradeSet(data, "", &tradeSet) + + if err != nil { + // log.Debug(symbol, "ticker@all ws处理失败", err) + continue + } + tradeSet.LastPrice = utility.StringFloat64Cut(data["c"].(string), int32(tradeSet.PriceDigit)) + tradeSet.OpenPrice = utility.StrToFloatCut(data["o"].(string), int32(tradeSet.PriceDigit)) + tradeSet.HighPrice = utility.StringFloat64Cut(data["h"].(string), int32(tradeSet.PriceDigit)) + tradeSet.LowPrice = utility.StringFloat64Cut(data["l"].(string), int32(tradeSet.PriceDigit)) + tradeSet.Volume = utility.StringFloat64Cut(data["v"].(string), int32(tradeSet.AmountDigit)) + tradeSet.QuoteVolume = utility.StringFloat64Cut(data["q"].(string), 5) + hasData := false + trades = append(trades, tradeSet) + + tradeSetVal, _ := sonic.MarshalString(&tradeSet) + if tradeSetVal != "" { + if err := helper.DefaultRedis.SetString(fmt.Sprintf(global.TICKER_FUTURES, global.EXCHANGE_BINANCE, symbol), tradeSetVal); err != nil { + log.Error(symbol, "ticker@all ws处理失败", err) + } + } + + for index := range pairs { + if cacheSymbol, ok := pairs[index]["symbol"].(string); ok { + if cacheSymbol == symbol { + pairs[index]["price"] = tradeSet.LastPrice + hasData = true + break + } + } + } + + if !hasData { + pairs = append(pairs, map[string]interface{}{ + "symbol": symbol, + "price": tradeSet.LastPrice, + }) + } + } + + if len(trades) > 0 { + + for index := range trades { + if wsBin.WorkType == "normal" { + //主单触发 + utility.SafeGoParam(binanceservice.JudgeFuturesPrice, trades[index]) + //止损信息 + utility.SafeGoParam(binanceservice.JudgeFuturesStoplossPrice, trades[index]) + //加仓信息 + utility.SafeGoParam(binanceservice.JudgeFuturesAddPositionPrice, trades[index]) + //对冲平仓 + utility.SafeGoParam(binanceservice.JudgeFuturesHedgeClosePosition, trades[index]) + //保险对冲 + utility.SafeGoParam(binanceservice.JudgeFuturesProtectHedge, trades[index]) + } else { + //合约对冲主动平仓 + // utility.SafeGoParam(binanceservice.FutureClosePositionTrigger, trades[index]) + binanceservice.FutureClosePositionTrigger(trades[index]) + } + } + } + + if wsBin.WorkType == "normal" { + if len(pairs) > 0 { + pairVal, _ = sonic.MarshalString(&pairs) + + if pairVal != "" { + if err := helper.DefaultRedis.SetString(rediskey.FutSymbolTicker, pairVal); err != nil { + log.Error("pair@all ws处理合约价格失败", err) + } + } + } + } +} + +// HandleWsAllKline 处理kline推送结果 +func HandleWsAllKline(msg []byte, tradeSet models.TradeSet) { + +} + +type WskLineData struct { + Line string `json:"i"` //"1m",K线间隔 + Timestamp int64 `json:"t"` // 这根K线的起始时间 + Open string `json:"o"` // 这根K线期间第一笔成交价 + Close string `json:"c"` // 这根K线期间末一笔成交价 + High string `json:"h"` // 这根K线期间最高成交价 + Low string `json:"l"` // 这根K线期间最低成交价 + Vol string `json:"v"` // 这根K线期间成交量 + QuoteVolume string `json:"q"` // 这根K线期间成交额 +} + +type tickerAllMessage struct { + Stream string `json:"stream"` + Data []map[string]interface{} `json:"data"` +} diff --git a/services/scriptservice/order.go b/services/scriptservice/order.go new file mode 100644 index 0000000..0b8edd5 --- /dev/null +++ b/services/scriptservice/order.go @@ -0,0 +1,78 @@ +package scriptservice + +import ( + "github.com/bytedance/sonic" + log "github.com/go-admin-team/go-admin-core/logger" + sysservice "github.com/go-admin-team/go-admin-core/sdk/service" + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/const/rediskey" + "go-admin/common/helper" + "gorm.io/gorm" + "strings" + "sync" +) + +type PreOrder struct { +} + +const GoroutineNum = 5 + +func (receiver *PreOrder) AddOrder(orm *gorm.DB) { + var wg sync.WaitGroup + for i := 1; i <= GoroutineNum; i++ { + wg.Add(1) + go workerWithLock(orm, &wg) + + } + wg.Wait() +} + +func workerWithLock(orm *gorm.DB, wg *sync.WaitGroup) { + defer func() { + wg.Done() + }() + scriptId, err := helper.DefaultRedis.LPopList(rediskey.PreOrderScriptList) + if err != nil { + return + } + var scriptInfo models.LinePreScript + err = orm.Model(&models.LinePreScript{}).Where("id = ? AND status = '0'", scriptId).Find(&scriptInfo).Error + if err != nil { + log.Error("获取脚本记录失败mysql err:", err) + return + } + if scriptInfo.Id > 0 { + orm.Model(&models.LinePreScript{}).Where("id = ?", scriptId).Update("status", "1") + } + + params := scriptInfo.ScriptParams + var batchReq dto.LineBatchAddPreOrderReq + sonic.Unmarshal([]byte(params), &batchReq) + errs := make([]error, 0) + errStr := make([]string, 0) + order := service.LinePreOrder{ + Service: sysservice.Service{Orm: orm}, + } + order.AddBatchPreOrder(&batchReq, nil, &errs) + + if len(errs) > 0 { + //e.Logger.Error(err) + for _, err2 := range errs { + errStr = append(errStr, err2.Error()) + } + //e.Error(500, nil, strings.Join(errStr, ",")) + orm.Model(&models.LinePreScript{}).Where("id = ?", scriptId).Updates(map[string]interface{}{ + "status": "2", + "desc": strings.Join(errStr, ","), + }) + return + } else { + orm.Model(&models.LinePreScript{}).Where("id = ?", scriptId).Updates(map[string]interface{}{ + "status": "2", + "desc": "执行成功", + }) + } + return +} diff --git a/services/spotservice/binancemarket.go b/services/spotservice/binancemarket.go new file mode 100644 index 0000000..cccdc25 --- /dev/null +++ b/services/spotservice/binancemarket.go @@ -0,0 +1,316 @@ +package spotservice + +import ( + "bytes" + "fmt" + "go-admin/common/const/rediskey" + "go-admin/common/global" + "go-admin/common/helper" + "go-admin/config" + "go-admin/pkg/utility" + "go-admin/services/binanceservice" + "go-admin/services/excservice" + "strings" + "sync" + + "go-admin/models" + + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/shopspring/decimal" + + "github.com/bytedance/sonic" + "go.uber.org/zap" +) + +var ( + baseBinanceWsUrlAll = "wss://stream.binance.com:9443/stream?streams=" + wsBin *excservice.BinanceWs + binSetKey = make(map[string]bool) + binSetKeyMu sync.RWMutex + quoteAssetSymbols = []string{"USDT", "ETH", "BTC", "SOL", "BNB", "DOGE"} +) + +type BaseWsDepthStream struct { + Stream string `json:"stream"` // + Data models.DepthBin `json:"data"` //数据 +} + +// GetBinance24hr 获取币安24小时价格变动信息 +func GetBinance24hr(coin, curr string) (models.Ticker24, error) { + ticker, err := excservice.GetTicker(coin, curr) + if err != nil { + log.Error("获取ticker失败", zap.String("coin", coin), zap.Error(err)) + return models.Ticker24{}, err + } + return ticker, nil +} + +// GetProLastDeal 获取最新交易记录 +func GetProLastDeal(coin, currency string) ([]models.NewDealPush, error) { + resp, err := excservice.GetTrades(coin, currency) + if err != nil { + log.Error("获取交易记录失败", zap.Error(err)) + return nil, err + } + return resp, nil +} + +// GetProDepth 获取交易对的市场深度 +func GetProDepth(coin, currency string) (models.FiveItem, error) { + depth, err := excservice.GetDepth(50, coin, currency) + if err != nil { + log.Error("获取市场深度失败", zap.String("coin", coin), zap.String("currency", currency), zap.Error(err)) + return models.FiveItem{}, err + } + return createFive2(depth), nil +} + +// 组合买卖5结果 +func createFive2(result models.DepthBin) models.FiveItem { + five := models.FiveItem{} + //卖5单 + slice1 := make([][]string, 0, 20) + var sum1 float64 + + for _, item := range result.Asks { + if len(item) > 1 { + num0 := utility.FloatToStringZero(item[0]) + num1 := utility.FloatToStringZero(item[1]) + slice1 = append(slice1, []string{num0, num1}) + } + + if len(item) == 2 { + sum1 = utility.FloatAdd(sum1, utility.StringAsFloat(item[1])) + } + } + + tempNumAsk := 0.0 + for k := 0; k < len(slice1); k++ { + //(前数量总值+当前数量值)/总数量值 + nowNum := utility.StringAsFloat(slice1[k][1]) + add := utility.FloatAdd(tempNumAsk, nowNum) + sc1 := utility.FloatDiv(add, sum1) + tempNumAsk = add + + sc2 := decimal.NewFromFloat(sc1).Truncate(3).String() //.Float64() + slice1[k] = append(slice1[k], sc2) + } + five.Sell = slice1 + five.SellNum = sum1 + + //买5 + slice2 := make([][]string, 0, 20) + var sum2 float64 + for _, item := range result.Bids { + if len(item) > 1 { + num0 := utility.FloatToStringZero(item[0]) + num1 := utility.FloatToStringZero(item[1]) + slice2 = append(slice2, []string{num0, num1}) + } + //slice2 = append(slice2, item) //item.Price, item.Amount}) + if len(item) == 2 { + sum2 = utility.FloatAdd(sum2, utility.StringAsFloat(item[1])) //sum2 + item.Amount + } + } + tempNumBid := 0.0 + for k := 0; k < len(slice2); k++ { + //(前数量总值+当前数量值)/总数量值 + nowNum := utility.StringAsFloat(slice2[k][1]) + add := utility.FloatAdd(tempNumBid, nowNum) + sc1 := utility.FloatDiv(add, sum2) + tempNumBid = add + //sc1 := utility.FloatDiv(utility.StringAsFloat(slice2[k][1]), sum2) //(slice2[k][1]) / sum2 + sc2 := decimal.NewFromFloat(sc1).Truncate(3).String() + slice2[k] = append(slice2[k], sc2) + } + five.Buy = slice2 + five.BuyNum = sum2 + return five +} + +// StartBinanceProWs 启动币安现货市场推送 +// workType normal-正常任务 trigger-主动触发任务 +func StartBinanceProWs(workType string) { + if wsBin == nil { + wsBin = excservice.NewBinanceWs(baseBinanceWsUrlAll, "") + } + + if wsBin == nil { + log.Error("实例化wsBin失败") + return + } + + if wsBin != nil && config.ExtConfig.ProxyUrl != "" { + wsBin.SetProxyUrl(config.ExtConfig.ProxyUrl) + } + + wsBin.WorkType = workType + wsBin.SetAllCallbacks(HandleWsAll, HandleWsAllKline) + + subscribeToTradeSet(wsBin) +} + +// subscribeToTradeSet 订阅给定交易集合的所有流 +func subscribeToTradeSet(ws *excservice.BinanceWs) { + // 订阅ticker、深度和交易的所有流 + streamNames := []string{ + "!miniTicker@arr", + } + err := ws.SubscribeAll(strings.Join(streamNames, "/")) + if err != nil { + log.Error("订阅流失败", zap.String("streams", strings.Join(streamNames, ",")), zap.Error(err)) + } +} + +// HandleWsAll 处理从WebSocket接收到的消息 +func HandleWsAll(msg []byte) { + if bytes.Contains(msg, []byte("miniTicker@arr")) { + handleTickerMessage(msg) + } +} + +// handleTickerMessage 处理ticker消息 +func handleTickerMessage(msg []byte) { + var streamResp tickerAllMessage + if err := sonic.Unmarshal(msg, &streamResp); err != nil { + log.Error("解码ticker消息失败", zap.Error(err)) + return + } + + if len(streamResp.Data) == 0 { + log.Error("ticker消息为空") + return + } + + pairs := make([]map[string]interface{}, 0) + trades := make([]models.TradeSet, 0) + pairsVal, _ := helper.DefaultRedis.GetString(rediskey.SpotSymbolTicker) + + if err := sonic.UnmarshalString(pairsVal, &pairs); err != nil { + log.Error("解码ticker消息失败", zap.Error(err)) + return + } + + for _, dataMap := range streamResp.Data { + symbolName, ok := dataMap["s"].(string) + + if !ok { + log.Error("ticker消息不包含有效symbol字段") + return + } + + if !utility.HasSuffix(symbolName, quoteAssetSymbols) { + continue + } + + key := fmt.Sprintf("%s:%s", global.TICKER_SPOT, symbolName) + tcVal, _ := helper.DefaultRedis.GetString(key) + tradeSet := models.TradeSet{} + if err := sonic.UnmarshalString(tcVal, &tradeSet); err != nil { + if tcVal != "" { + log.Error("解析tradeSet失败", zap.Error(err)) + } + continue + } + tradeSet.LastPrice = utility.StringFloat64Cut(dataMap["c"].(string), int32(tradeSet.PriceDigit)) + tradeSet.OpenPrice = utility.StrToFloatCut(dataMap["o"].(string), int32(tradeSet.PriceDigit)) + tradeSet.HighPrice = utility.StringFloat64Cut(dataMap["h"].(string), int32(tradeSet.PriceDigit)) + tradeSet.LowPrice = utility.StringFloat64Cut(dataMap["l"].(string), int32(tradeSet.PriceDigit)) + tradeSet.Volume = utility.StringFloat64Cut(dataMap["v"].(string), int32(tradeSet.AmountDigit)) + tradeSet.QuoteVolume = utility.StringFloat64Cut(dataMap["q"].(string), 5) + hasData := false + + for index := range pairs { + if symbol, ok := pairs[index]["symbol"].(string); ok { + if symbol == symbolName { + hasData = true + pairs[index]["price"] = tradeSet.LastPrice + break + } + } + } + + if !hasData { + pairs = append(pairs, map[string]interface{}{ + "symbol": symbolName, + "price": tradeSet.LastPrice, + }, + ) + } + + trades = append(trades, tradeSet) + tcVal, _ = sonic.MarshalString(&tradeSet) + + if tcVal != "" { + if err := helper.DefaultRedis.SetString(key, tcVal); err != nil { + log.Error("redis保存交易对失败", tradeSet.Coin, tradeSet.Currency) + } + } + } + + //判断触发现货下单 + if len(trades) > 0 { + for index := range trades { + if wsBin.WorkType == "normal" { + // 主单触发 + utility.SafeGoParam(binanceservice.JudgeSpotPrice, trades[index]) + + // 止损单 + utility.SafeGoParam(binanceservice.JudgeSpotStopLoss, trades[index]) + + // 触发加仓 + utility.SafeGoParam(binanceservice.JudgeSpotAddPosition, trades[index]) + + // 对冲平仓 + utility.SafeGoParam(binanceservice.JudgeSpotHedgeClosePosition, trades[index]) + // 保险对冲 + utility.SafeGoParam(binanceservice.JudgeSpotProtectHedge, trades[index]) + } else { + //todo + } + } + } + + if wsBin.WorkType == "normal" { + if len(pairs) > 0 { + pairsVal, _ = sonic.MarshalString(&pairs) + + if pairsVal != "" { + if err := helper.DefaultRedis.SetString(rediskey.SpotSymbolTicker, pairsVal); err != nil { + log.Error("redis保存", rediskey.SpotSymbolTicker, "失败,", pairs) + } + } + } + } +} + +// // HandleWsAllKline 处理kline推送结果 +func HandleWsAllKline(msg []byte, tradeSet models.TradeSet) { +} + +type WskLineData struct { + Line string `json:"i"` //"1m",K线间隔 + Timestamp int64 `json:"t"` // 这根K线的起始时间 + Open string `json:"o"` // 这根K线期间第一笔成交价 + Close string `json:"c"` // 这根K线期间末一笔成交价 + High string `json:"h"` // 这根K线期间最高成交价 + Low string `json:"l"` // 这根K线期间最低成交价 + Vol string `json:"v"` // 这根K线期间成交量 + QuoteVolume string `json:"q"` // 这根K线期间成交额 +} + +type tickerAllMessage struct { + Stream string `json:"stream"` + Data []map[string]interface{} `json:"data"` +} + +var ( + depthDuration int64 = 1700 //深度推送时间间隔,单位毫秒 + tickerDuration int64 = 1300 //24小时推送时间间隔,单位毫秒 + //allPushDuration int64 = 2000 //allpus推送时间间隔,单位毫秒 + marketDuration int64 = 3000 //首页+行情页推送时间间隔,单位毫秒 + dealDuration int64 = 1300 //最新成交推送时间间隔,单位毫秒 + klineDuration int64 = 800 //kline推送时间间隔,单位毫秒 + + usdtCode = "USDT" +) diff --git a/services/udunservice/udunservice.go b/services/udunservice/udunservice.go new file mode 100644 index 0000000..c824201 --- /dev/null +++ b/services/udunservice/udunservice.go @@ -0,0 +1,334 @@ +package udunservice + +import ( + "encoding/json" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/shopspring/decimal" + "go-admin/app/admin/models" + "go-admin/pkg/timehelper" + "go-admin/pkg/udunhelper" + "go-admin/pkg/utility" + "go.uber.org/zap" + "gorm.io/gorm" + "math" + "strconv" + "strings" +) + +// TradeCallback 充值,提币回调 +func TradeCallback(orm *gorm.DB, timestamp, nonce, body, sign string) string { + + log.Error("充值,提币回调返回值", zap.String("body", body), zap.String("timestamp", timestamp), zap.String("nonce", nonce), + zap.String("sign", sign)) + + //验证签名 + sign1 := udunhelper.CheckCallBackSign(timestamp, nonce, body) + //udunhelper.CheckCallBackSign() + if sign != sign1 { + log.Error("充值,提币回调返回值 签名不正确", zap.String("body", body), zap.String("timestamp", timestamp), zap.String("nonce", nonce), + zap.String("sign", sign)) + return "error" + } + + // 反序列化 + var trade udunhelper.CallBackRes + if err := json.Unmarshal([]byte(body), &trade); err != nil { + return "error" + } + // 充值 + if trade.TradeType == 1 { + // + return handleRecharge(orm, trade, timestamp) + } else if trade.TradeType == 2 { + //提币 + //return handleWithdraw(trade) + } + return "success" +} + +// 充值回调处理 +func handleRecharge(orm *gorm.DB, trade udunhelper.CallBackRes, timestamp string) string { + // 检测是否已经写入,根据交易Hash判断是否已经存在充值记录 + var charge models.LineRecharge + err := orm.Model(&models.LineRecharge{}).Where("txid = ?", trade.TxId).Find(&charge).Error + if err != nil { + log.Error("GetVtsRechargeByOrderNo", zap.Error(err)) + return "error" + } + + if charge.Status == "2" { + // 已经成功,则不继续写入 + log.Error("已经成功,则不继续写入", zap.String("txId", trade.TxId)) + return "error" + } + + // 充币通知 + amount := getBalance(trade.Amount, trade.Decimals) + fee := getBalance(trade.Fee, trade.Decimals) + // 查询钱包信息 + var wallet models.LineWallet + err = orm.Model(&models.LineWallet{}).Where("address = ?", trade.Address).Find(&wallet).Error + //wallet, err := walletdb.GetVtsWalletByAddress(trade.Address) + if err != nil { + log.Error("GetVtsWalletByAddress", zap.Error(err), zap.String("address", trade.Address)) + return "error" + } + // 加载币种信息 + var udCoin models.LineUduncoin + err = orm.Model(&models.LineUduncoin{}).Where("main_coin_type = ? AND coin_type = ?", trade.MainCoinType, trade.CoinType).Find(&udCoin).Error + //udCoin, err := uduncoindb.GetVtsUduncoinItemByCoinType(trade.MainCoinType, trade.CoinType) + if err != nil { + log.Error("GetVtsUduncoinItemByCoinType", zap.Error(err), zap.String("MainCoinType", trade.MainCoinType), + zap.String("CoinType", trade.CoinType)) + return "error" + } + height := utility.StringAsInteger(trade.BlockHigh) + //currTime := time.Now() + status := 1 + switch trade.Status { + case 0: + status = 1 + case 1: + status = 1 + case 2: + status = 3 // 失败 + case 3: + status = 2 // 成功 + case 4: + status = 3 // 失败 + } + coinCode := udCoin.Symbol + if strings.EqualFold(coinCode, "TRCUSDT") { + coinCode = "USDT" + } + //coin := coinservice.CoinCache.GetByCode(coinCode) + //if coin.Id == 0 { + // loghelper.Error("TradeCallback 充值,提币回调 未找到系统对应的币种Id", zap.String("udCoin.Symbol", udCoin.Symbol)) + // return "error" + //} + t1 := timehelper.IntToTime(utility.StringAsInt64(timestamp)) + //timehelper.IntToTime() + // 充值 + recharge := models.LineRecharge{ + UserId: wallet.UserId, + Confirms: strconv.Itoa(0), + TranType: strconv.Itoa(1), + BlockIndex: strconv.Itoa(height), + Amount: utility.FloatToStr(amount), + Fee: utility.FloatToStr(fee), + Account: "", + Address: trade.Address, + Txid: trade.TxId, + BlockTime: t1, + TimeReceived: t1, + MainCoin: udCoin.MainSymbol, + OrderNo: trade.TradeId, // 流水号 + Status: strconv.Itoa(status), + State: strconv.Itoa(trade.Status), + AddressFrom: "", + } + // 加载账户信息 + //beforeAmount := float64(0) + //afterAmount := float64(0) + if trade.Status == 3 { + tx := orm.Begin() + err := tx.Model(&models.LineRecharge{}).Create(&recharge).Error + if err != nil { + tx.Rollback() + log.Error("create LineRecharge err", zap.Error(err), zap.String("wallet.UserId", strconv.FormatInt(wallet.UserId, 10)), + zap.String("money", utility.FloatToStr(amount))) + } + err = tx.Model(&models.LineUser{}).Where("id = ?", wallet.UserId).Update("money", gorm.Expr("money + ?", amount)).Error + if err != nil { + tx.Rollback() + log.Error("update user money err", zap.Error(err), zap.String("wallet.UserId", strconv.FormatInt(wallet.UserId, 10)), + zap.String("money", utility.FloatToStr(amount))) + return "error" + } + tx.Commit() + } + + //hold := holddb.GetUserHold(wallet.UserId, recharge.CoinId) + //if hold.Id == 0 { + // hold = dbmodel.VtsHold{ + // Id: 0, + // CoinId: recharge.CoinId, + // UserId: wallet.UserId, + // Num: 0, + // UseNum: amount, + // FreezeNum: 0, + // CreateTime: currTime, + // UpdateTime: currTime, + // } + // beforeAmount = 0 + // afterAmount = amount + //} else { + // beforeAmount = hold.UseNum + // afterAmount = utility.FloatAdd(hold.UseNum, amount) + // hold.UseNum = amount + // hold.UpdateTime = currTime + //} + //// 流水 + //log := dbmodel.VtsCurrentHoldLog{ + // ID: 0, + // UserID: wallet.UserId, + // CoinID: recharge.CoinId, + // UseFree: 1, + // DealType: 5, + // Remarks: "优顿充值", + // RelateOrderNo: trade.TradeId, + // Amount: amount, + // BeforeAmount: beforeAmount, + // AfterAmount: afterAmount, + // Poundage: fee, + // CreateTime: currTime, + //} + // + //// 开启事务 + //tx, err := dbhelper.MasterPgdb.Beginx() + //if err != nil { + // loghelper.Error("Begin", zap.Error(err)) + // return "error" + //} + //if err = walletdb.RechargeInsert(recharge, tx); err != nil { + // _ = tx.Rollback() + // return "error" + //} + //// 账户写入 + //if trade.Status == 3 { + // if err = holddb.UpdateHoldUseNum(hold, tx); err != nil { + // _ = tx.Rollback() + // return "error" + // } + // // 流水 + // if err = holddb.AddCurrentHoldLog(log, tx); err != nil { + // _ = tx.Rollback() + // loghelper.Error("handleRecharge", zap.Error(err)) + // return "error" + // } + //} + //// 提交 + //if err = tx.Commit(); err != nil { + // loghelper.Error("Commit", zap.Error(err)) + // return "error" + //} + ////保存消息日志 + //templates := cmsdb.GetTempContent(3, 3) //充币通知 + //var template adminmodel.CmsTemplateContentDb + //if len(templates) > 0 { + // template = templates[0] + //} + //if template.TemplateId > 0 { + // // 写入站内信 + // store := dbmodel.CmsMessageUserDB{ + // UserId: recharge.UserId, + // MessageId: template.TemplateId, + // IsRead: 1, + // Type: 1, + // CreateTime: time.Now(), + // CategoryId: template.CategoryId, + // Content: strings.ReplaceAll(template.Content, "{num}", " "+utility.FloatToStr(amount)+" "+coinCode), + // LittleTitle: template.LittleTitle, + // } + // err = cmsdb.AddMessageUserItem(store) + // if err != nil { + // loghelper.Error("AddCmsMessageUser Error:", zap.Error(err)) + // } + //} + return "success" +} + +// 提币回调处理 +//func handleWithdraw(trade udunhelper.CallBackRes) string { +// // 提币通知 +// status := 7 +// switch trade.Status { +// case 0: +// status = 7 +// case 1: +// status = 7 +// case 2: +// status = 9 +// case 3: +// status = 8 +// case 4: +// status = 9 +// } +// if status < 8 { +// return "success" +// } +// // 加载 +// data, err := walletdb.GetWithdrawItemByOrderNo(trade.BusinessId) +// if err != nil { +// return "error" +// } +// data.Status = status +// data.State = trade.Status +// data.TxId = trade.TxId +// if status == 9 { +// data.Remark = "未知原因" +// } +// num := data.SumNum //utility.FloatAddCut(data.Num, data.NumFee, 8) +// // 更新这个表的状态 +// tx, err := dbhelper.MasterPgdb.Beginx() +// if err != nil { +// loghelper.Error("Begin", zap.Error(err)) +// return "error" +// } +// err = walletdb.WithdrawStatusByUd(data, tx) +// if err != nil { +// _ = tx.Rollback() +// return "error" +// } +// if status == 9 { +// //如果失败则把资金重新返回给用户,减去相应的冻结资金 +// err = holddb.UpdateHoldWithdraw(num, -num, data.UserId, data.CoinId, tx) +// if err != nil { +// _ = tx.Rollback() +// return "error" +// } +// } else { +// //成功的话则减去相应的冻结资金 +// err = holddb.UpdateHoldWithdraw(0, -num, data.UserId, data.CoinId, tx) +// if err != nil { +// _ = tx.Rollback() +// return "error" +// } +// } +// _ = tx.Commit() +// +// //保存消息日志 +// templates := cmsdb.GetTempContent(4, 3) // 提币通知 +// var template adminmodel.CmsTemplateContentDb +// if len(templates) > 0 { +// template = templates[0] +// } +// if template.TemplateId > 0 { +// coin := coinservice.CoinCache.GetById(data.CoinId) +// // 写入站内信 +// store := dbmodel.CmsMessageUserDB{ +// UserId: data.UserId, +// MessageId: template.TemplateId, +// IsRead: 1, +// Type: 1, +// CreateTime: time.Now(), +// CategoryId: template.CategoryId, +// Content: strings.ReplaceAll(template.Content, "{num}", " "+utility.FloatToStr(data.Num)+" "+coin.CoinCode), +// LittleTitle: template.LittleTitle, +// } +// err = cmsdb.AddMessageUserItem(store) +// if err != nil { +// loghelper.Error("AddCmsMessageUser Error:", zap.Error(err)) +// } +// } +// return "success" +//} + +// getBalance 获取金额 +func getBalance(balance, decimals string) float64 { + am, _ := strconv.ParseFloat(balance, 64) + dec, _ := strconv.ParseFloat(decimals, 64) + res := decimal.NewFromFloat(am / math.Pow(10, dec)) + amount, _ := res.Truncate(8).Float64() + return amount +} diff --git a/ssh/swag.sh b/ssh/swag.sh new file mode 100644 index 0000000..48139df --- /dev/null +++ b/ssh/swag.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +swag i -g init_router.go -dir app/admin/router --instanceName admin --parseDependency -o docs/admin diff --git a/static/form-generator/css/index.1a124643.css b/static/form-generator/css/index.1a124643.css new file mode 100644 index 0000000..0aa38db --- /dev/null +++ b/static/form-generator/css/index.1a124643.css @@ -0,0 +1 @@ +.add-item[data-v-60dcec16]{margin-top:8px}.url-item[data-v-60dcec16]{margin-bottom:12px}.tab-editor[data-v-3157a144]{position:absolute;top:33px;bottom:0;left:0;right:0;font-size:14px}.left-editor[data-v-3157a144]{position:relative;height:100%;background:#1e1e1e;overflow:hidden}.setting[data-v-3157a144]{position:absolute;right:15px;top:3px;color:#a9f122;font-size:18px;cursor:pointer;z-index:1}.right-preview[data-v-3157a144]{height:100%}.right-preview .result-wrapper[data-v-3157a144]{height:calc(100vh - 33px);width:100%;overflow:auto;padding:12px;-webkit-box-sizing:border-box;box-sizing:border-box}.action-bar[data-v-3157a144]{height:33px;background:#f2fafb;padding:0 15px;-webkit-box-sizing:border-box;box-sizing:border-box}.action-bar .bar-btn[data-v-3157a144]{display:inline-block;padding:0 6px;line-height:32px;color:#8285f5;cursor:pointer;font-size:14px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.action-bar .bar-btn i[data-v-3157a144]{font-size:20px}.action-bar .bar-btn[data-v-3157a144]:hover{color:#4348d4}.action-bar .bar-btn+.bar-btn[data-v-3157a144]{margin-left:8px}.action-bar .delete-btn[data-v-3157a144]{color:#f56c6c}.action-bar .delete-btn[data-v-3157a144]:hover{color:#ea0b30}[data-v-3157a144] .el-drawer__header,[data-v-44793736] .el-drawer__header{display:none}.action-bar[data-v-44793736]{height:33px;background:#f2fafb;padding:0 15px;-webkit-box-sizing:border-box;box-sizing:border-box}.action-bar .bar-btn[data-v-44793736]{display:inline-block;padding:0 6px;line-height:32px;color:#8285f5;cursor:pointer;font-size:14px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.action-bar .bar-btn i[data-v-44793736]{font-size:20px}.action-bar .bar-btn[data-v-44793736]:hover{color:#4348d4}.action-bar .bar-btn+.bar-btn[data-v-44793736]{margin-left:8px}.action-bar .delete-btn[data-v-44793736]{color:#f56c6c}.action-bar .delete-btn[data-v-44793736]:hover{color:#ea0b30}.json-editor[data-v-44793736]{height:calc(100vh - 33px)}.icon-ul[data-v-3ba3d51c]{margin:0;padding:0;font-size:0}.icon-ul li[data-v-3ba3d51c]{list-style-type:none;text-align:center;font-size:14px;display:inline-block;width:16.66%;-webkit-box-sizing:border-box;box-sizing:border-box;height:108px;padding:15px 6px 6px 6px;cursor:pointer;overflow:hidden}.icon-ul li[data-v-3ba3d51c]:hover{background:#f2f2f2}.icon-ul li.active-item[data-v-3ba3d51c]{background:#e1f3fb;color:#7a6df0}.icon-ul li>i[data-v-3ba3d51c]{font-size:30px;line-height:50px}.icon-dialog[data-v-3ba3d51c] .el-dialog{border-radius:8px;margin-bottom:0;margin-top:4vh!important;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;max-height:92vh;overflow:hidden;-webkit-box-sizing:border-box;box-sizing:border-box}.icon-dialog[data-v-3ba3d51c] .el-dialog .el-dialog__header{padding-top:14px}.icon-dialog[data-v-3ba3d51c] .el-dialog .el-dialog__body{margin:0 20px 20px 20px;padding:0;overflow:auto}.right-board[data-v-0c0004cd]{width:350px;position:absolute;right:0;top:0;padding-top:3px}.right-board .field-box[data-v-0c0004cd]{position:relative;height:calc(100vh - 42px);-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden}.right-board .el-scrollbar[data-v-0c0004cd]{height:100%}.select-item[data-v-0c0004cd]{display:-webkit-box;display:-ms-flexbox;display:flex;border:1px dashed #fff;-webkit-box-sizing:border-box;box-sizing:border-box}.select-item .close-btn[data-v-0c0004cd]{cursor:pointer;color:#f56c6c}.select-item .el-input+.el-input[data-v-0c0004cd]{margin-left:4px}.select-item+.select-item[data-v-0c0004cd]{margin-top:4px}.select-item.sortable-chosen[data-v-0c0004cd]{border:1px dashed #409eff}.select-line-icon[data-v-0c0004cd]{line-height:32px;font-size:22px;padding:0 4px;color:#777}.option-drag[data-v-0c0004cd]{cursor:move}.time-range .el-date-editor[data-v-0c0004cd]{width:227px}.time-range[data-v-0c0004cd] .el-icon-time{display:none}.document-link[data-v-0c0004cd]{position:absolute;display:block;width:26px;height:26px;top:0;left:0;cursor:pointer;background:#409eff;z-index:1;border-radius:0 0 6px 0;text-align:center;line-height:26px;color:#fff;font-size:18px}.node-label[data-v-0c0004cd]{font-size:14px}.node-icon[data-v-0c0004cd]{color:#bebfc3}.container{position:relative;width:100%;height:100%}.components-list{padding:8px;-webkit-box-sizing:border-box;box-sizing:border-box;height:100%}.components-list .components-item{display:inline-block;width:48%;margin:1%;-webkit-transition:-webkit-transform 0ms!important;transition:-webkit-transform 0ms!important;transition:transform 0ms!important;transition:transform 0ms,-webkit-transform 0ms!important}.components-draggable{padding-bottom:20px}.components-title{font-size:14px;color:#222;margin:6px 2px}.components-title .svg-icon{color:#666;font-size:18px}.components-body{padding:8px 10px;background:#f6f7ff;font-size:12px;cursor:move;border:1px dashed #f6f7ff;border-radius:3px}.components-body .svg-icon{color:#777;font-size:15px}.components-body:hover{border:1px dashed #787be8;color:#787be8}.components-body:hover .svg-icon{color:#787be8}.left-board{width:260px;position:absolute;left:0;top:0;height:100vh}.center-scrollbar,.left-scrollbar{height:calc(100vh - 42px);overflow:hidden}.center-scrollbar{border-left:1px solid #f1e8e8;border-right:1px solid #f1e8e8}.center-board,.center-scrollbar{-webkit-box-sizing:border-box;box-sizing:border-box}.center-board{height:100vh;width:auto;margin:0 350px 0 260px}.empty-info{position:absolute;top:46%;left:0;right:0;text-align:center;font-size:18px;color:#ccb1ea;letter-spacing:4px}.action-bar{position:relative;height:42px;text-align:right;padding:0 15px;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #f1e8e8;border-top:none;border-left:none}.action-bar .delete-btn{color:#f56c6c}.logo-wrapper{position:relative;height:42px;background:#fff;border-bottom:1px solid #f1e8e8;-webkit-box-sizing:border-box;box-sizing:border-box}.logo{position:absolute;left:12px;top:6px;line-height:30px;color:#00afff;font-weight:600;font-size:17px;white-space:nowrap}.logo>img{width:30px;height:30px;vertical-align:top}.logo .github{display:inline-block;vertical-align:sub;margin-left:15px}.logo .github>img{height:22px}.center-board-row{padding:12px 12px 15px 12px;-webkit-box-sizing:border-box;box-sizing:border-box}.center-board-row>.el-form{height:calc(100vh - 69px)}.drawing-board{height:100%;position:relative}.drawing-board .components-body{padding:0;margin:0;font-size:0}.drawing-board .sortable-ghost{position:relative;display:block;overflow:hidden}.drawing-board .sortable-ghost:before{content:" ";position:absolute;left:0;right:0;top:0;height:3px;background:#5959df;z-index:2}.drawing-board .components-item.sortable-ghost{width:100%;height:60px;background-color:#f6f7ff}.drawing-board .active-from-item>.el-form-item{background:#f6f7ff;border-radius:6px}.drawing-board .active-from-item>.drawing-item-copy,.drawing-board .active-from-item>.drawing-item-delete{display:initial}.drawing-board .active-from-item>.component-name{color:#409eff}.drawing-board .el-form-item{margin-bottom:15px}.drawing-item{position:relative;cursor:move}.drawing-item.unfocus-bordered:not(.active-from-item)>div:first-child{border:1px dashed #ccc}.drawing-item .el-form-item{padding:12px 10px}.drawing-row-item{position:relative;cursor:move;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px dashed #ccc;border-radius:3px;padding:0 2px;margin-bottom:15px}.drawing-row-item .drawing-row-item{margin-bottom:2px}.drawing-row-item .el-col{margin-top:22px}.drawing-row-item .el-form-item{margin-bottom:0}.drawing-row-item .drag-wrapper{min-height:80px}.drawing-row-item.active-from-item{border:1px dashed #409eff}.drawing-row-item .component-name{position:absolute;top:0;left:0;font-size:12px;color:#bbb;display:inline-block;padding:0 6px}.drawing-item:hover>.el-form-item,.drawing-row-item:hover>.el-form-item{background:#f6f7ff;border-radius:6px}.drawing-item:hover>.drawing-item-copy,.drawing-item:hover>.drawing-item-delete,.drawing-row-item:hover>.drawing-item-copy,.drawing-row-item:hover>.drawing-item-delete{display:initial}.drawing-item>.drawing-item-copy,.drawing-item>.drawing-item-delete,.drawing-row-item>.drawing-item-copy,.drawing-row-item>.drawing-item-delete{display:none;position:absolute;top:-10px;width:22px;height:22px;line-height:22px;text-align:center;border-radius:50%;font-size:12px;border:1px solid;cursor:pointer;z-index:1}.drawing-item>.drawing-item-copy,.drawing-row-item>.drawing-item-copy{right:56px;border-color:#409eff;color:#409eff;background:#fff}.drawing-item>.drawing-item-copy:hover,.drawing-row-item>.drawing-item-copy:hover{background:#409eff;color:#fff}.drawing-item>.drawing-item-delete,.drawing-row-item>.drawing-item-delete{right:24px;border-color:#f56c6c;color:#f56c6c;background:#fff}.drawing-item>.drawing-item-delete:hover,.drawing-row-item>.drawing-item-delete:hover{background:#f56c6c;color:#fff}body,html{margin:0;padding:0;background:#fff;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}body,html,input,textarea{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji}.editor-tabs{background:#121315}.editor-tabs .el-tabs__header{margin:0;border-bottom-color:#121315}.editor-tabs .el-tabs__header .el-tabs__nav{border-color:#121315}.editor-tabs .el-tabs__item{height:32px;line-height:32px;color:#888a8e;border-left:1px solid #121315!important;background:#363636;margin-right:5px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.editor-tabs .el-tabs__item.is-active{background:#1e1e1e;border-bottom-color:#1e1e1e!important;color:#fff}.editor-tabs .el-icon-edit{color:#f1fa8c}.editor-tabs .el-icon-document{color:#a95812}.right-scrollbar .el-scrollbar__view{padding:12px 18px 15px 15px}.el-scrollbar__wrap{-webkit-box-sizing:border-box;box-sizing:border-box;overflow-x:hidden!important}.center-tabs .el-tabs__header,.el-scrollbar__wrap{margin-bottom:0!important}.center-tabs .el-tabs__item{width:50%;text-align:center}.center-tabs .el-tabs__nav{width:100%}.reg-item{padding:12px 6px;background:#f8f8f8;position:relative;border-radius:4px}.reg-item .close-btn{position:absolute;right:-6px;top:-6px;display:block;width:16px;height:16px;line-height:16px;background:rgba(0,0,0,.2);border-radius:50%;color:#fff;text-align:center;z-index:1;cursor:pointer;font-size:12px}.reg-item .close-btn:hover{background:rgba(210,23,23,.5)}.reg-item+.reg-item{margin-top:18px}.action-bar .el-button+.el-button{margin-left:15px}.action-bar i{font-size:20px;vertical-align:middle;position:relative;top:-1px}.custom-tree-node{width:100%;font-size:14px}.custom-tree-node .node-operation{float:right}.custom-tree-node i[class*=el-icon]+i[class*=el-icon]{margin-left:6px}.custom-tree-node .el-icon-plus{color:#409eff}.custom-tree-node .el-icon-delete{color:#157a0c}.el-scrollbar__view{overflow-x:hidden}.el-rate{display:inline-block;vertical-align:text-top}.el-upload__tip{line-height:1.2}.svg-icon[data-v-19957a58]{width:1em;height:1em;vertical-align:-.15em;fill:currentColor;overflow:hidden}.svg-external-icon[data-v-19957a58]{background-color:currentColor;-webkit-mask-size:cover!important;mask-size:cover!important;display:inline-block} \ No newline at end of file diff --git a/static/form-generator/css/parser-example.69e16e51.css b/static/form-generator/css/parser-example.69e16e51.css new file mode 100644 index 0000000..09cac88 --- /dev/null +++ b/static/form-generator/css/parser-example.69e16e51.css @@ -0,0 +1 @@ +.test-form[data-v-77b1aafa]{margin:15px auto;width:800px;padding:15px} \ No newline at end of file diff --git a/static/form-generator/img/logo.e1bc3747.png b/static/form-generator/img/logo.e1bc3747.png new file mode 100644 index 0000000..9838306 Binary files /dev/null and b/static/form-generator/img/logo.e1bc3747.png differ diff --git a/static/form-generator/index.html b/static/form-generator/index.html new file mode 100644 index 0000000..b888095 --- /dev/null +++ b/static/form-generator/index.html @@ -0,0 +1 @@ +form-generator
\ No newline at end of file diff --git a/static/form-generator/js/chunk-vendors.971555db.js b/static/form-generator/js/chunk-vendors.971555db.js new file mode 100644 index 0000000..762db7c --- /dev/null +++ b/static/form-generator/js/chunk-vendors.971555db.js @@ -0,0 +1,19 @@ +(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-vendors"],{"00ee":function(t,e,s){var i=s("b622"),r=i("toStringTag"),n={};n[r]="z",t.exports="[object z]"===String(n)},"0366":function(t,e,s){var i=s("1c0b");t.exports=function(t,e,s){if(i(t),void 0===e)return t;switch(s){case 0:return function(){return t.call(e)};case 1:return function(s){return t.call(e,s)};case 2:return function(s,i){return t.call(e,s,i)};case 3:return function(s,i,r){return t.call(e,s,i,r)}}return function(){return t.apply(e,arguments)}}},"057f":function(t,e,s){var i=s("fc6a"),r=s("241c").f,n={}.toString,a="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[],o=function(t){try{return r(t)}catch(e){return a.slice()}};t.exports.f=function(t){return a&&"[object Window]"==n.call(t)?o(t):r(i(t))}},"06cf":function(t,e,s){var i=s("83ab"),r=s("d1e7"),n=s("5c6c"),a=s("fc6a"),o=s("c04e"),c=s("5135"),h=s("0cfb"),l=Object.getOwnPropertyDescriptor;e.f=i?l:function(t,e){if(t=a(t),e=o(e,!0),h)try{return l(t,e)}catch(s){}if(c(t,e))return n(!r.f.call(t,e),t[e])}},"0cfb":function(t,e,s){var i=s("83ab"),r=s("d039"),n=s("cc12");t.exports=!i&&!r((function(){return 7!=Object.defineProperty(n("div"),"a",{get:function(){return 7}}).a}))},1276:function(t,e,s){"use strict";var i=s("d784"),r=s("44e7"),n=s("825a"),a=s("1d80"),o=s("4840"),c=s("8aa5"),h=s("50c4"),l=s("14c3"),p=s("9263"),u=s("d039"),d=[].push,f=Math.min,m=4294967295,y=!u((function(){return!RegExp(m,"y")}));i("split",2,(function(t,e,s){var i;return i="c"=="abbc".split(/(b)*/)[1]||4!="test".split(/(?:)/,-1).length||2!="ab".split(/(?:ab)*/).length||4!=".".split(/(.?)(.?)/).length||".".split(/()()/).length>1||"".split(/.?/).length?function(t,s){var i=String(a(this)),n=void 0===s?m:s>>>0;if(0===n)return[];if(void 0===t)return[i];if(!r(t))return e.call(i,t,n);var o,c,h,l=[],u=(t.ignoreCase?"i":"")+(t.multiline?"m":"")+(t.unicode?"u":"")+(t.sticky?"y":""),f=0,y=new RegExp(t.source,u+"g");while(o=p.call(y,i)){if(c=y.lastIndex,c>f&&(l.push(i.slice(f,o.index)),o.length>1&&o.index=n))break;y.lastIndex===o.index&&y.lastIndex++}return f===i.length?!h&&y.test("")||l.push(""):l.push(i.slice(f)),l.length>n?l.slice(0,n):l}:"0".split(void 0,0).length?function(t,s){return void 0===t&&0===s?[]:e.call(this,t,s)}:e,[function(e,s){var r=a(this),n=void 0==e?void 0:e[t];return void 0!==n?n.call(e,r,s):i.call(String(r),e,s)},function(t,r){var a=s(i,t,this,r,i!==e);if(a.done)return a.value;var p=n(t),u=String(this),d=o(p,RegExp),g=p.unicode,x=(p.ignoreCase?"i":"")+(p.multiline?"m":"")+(p.unicode?"u":"")+(y?"y":"g"),b=new d(y?p:"^(?:"+p.source+")",x),v=void 0===r?m:r>>>0;if(0===v)return[];if(0===u.length)return null===l(b,u)?[u]:[];var w=0,P=0,T=[];while(P1?arguments[1]:void 0)}},1861:function(t,e,s){"use strict";Object.defineProperty(e,"__esModule",{value:!0});const i=!0,r=!0,n=!0,a=!0,o=!0,c=!0;class h{constructor(t,e={}){this.label=t,this.keyword=e.keyword,this.beforeExpr=!!e.beforeExpr,this.startsExpr=!!e.startsExpr,this.rightAssociative=!!e.rightAssociative,this.isLoop=!!e.isLoop,this.isAssign=!!e.isAssign,this.prefix=!!e.prefix,this.postfix=!!e.postfix,this.binop=null!=e.binop?e.binop:null,this.updateContext=null}}const l=new Map;function p(t,e={}){e.keyword=t;const s=new h(t,e);return l.set(t,s),s}function u(t,e){return new h(t,{beforeExpr:i,binop:e})}const d={num:new h("num",{startsExpr:r}),bigint:new h("bigint",{startsExpr:r}),regexp:new h("regexp",{startsExpr:r}),string:new h("string",{startsExpr:r}),name:new h("name",{startsExpr:r}),eof:new h("eof"),bracketL:new h("[",{beforeExpr:i,startsExpr:r}),bracketHashL:new h("#[",{beforeExpr:i,startsExpr:r}),bracketBarL:new h("[|",{beforeExpr:i,startsExpr:r}),bracketR:new h("]"),bracketBarR:new h("|]"),braceL:new h("{",{beforeExpr:i,startsExpr:r}),braceBarL:new h("{|",{beforeExpr:i,startsExpr:r}),braceHashL:new h("#{",{beforeExpr:i,startsExpr:r}),braceR:new h("}"),braceBarR:new h("|}"),parenL:new h("(",{beforeExpr:i,startsExpr:r}),parenR:new h(")"),comma:new h(",",{beforeExpr:i}),semi:new h(";",{beforeExpr:i}),colon:new h(":",{beforeExpr:i}),doubleColon:new h("::",{beforeExpr:i}),dot:new h("."),question:new h("?",{beforeExpr:i}),questionDot:new h("?."),arrow:new h("=>",{beforeExpr:i}),template:new h("template"),ellipsis:new h("...",{beforeExpr:i}),backQuote:new h("`",{startsExpr:r}),dollarBraceL:new h("${",{beforeExpr:i,startsExpr:r}),at:new h("@"),hash:new h("#",{startsExpr:r}),interpreterDirective:new h("#!..."),eq:new h("=",{beforeExpr:i,isAssign:a}),assign:new h("_=",{beforeExpr:i,isAssign:a}),incDec:new h("++/--",{prefix:o,postfix:c,startsExpr:r}),bang:new h("!",{beforeExpr:i,prefix:o,startsExpr:r}),tilde:new h("~",{beforeExpr:i,prefix:o,startsExpr:r}),pipeline:u("|>",0),nullishCoalescing:u("??",1),logicalOR:u("||",1),logicalAND:u("&&",2),bitwiseOR:u("|",3),bitwiseXOR:u("^",4),bitwiseAND:u("&",5),equality:u("==/!=/===/!==",6),relational:u("/<=/>=",7),bitShift:u("<>/>>>",8),plusMin:new h("+/-",{beforeExpr:i,binop:9,prefix:o,startsExpr:r}),modulo:new h("%",{beforeExpr:i,binop:10,startsExpr:r}),star:u("*",10),slash:u("/",10),exponent:new h("**",{beforeExpr:i,binop:11,rightAssociative:!0}),_break:p("break"),_case:p("case",{beforeExpr:i}),_catch:p("catch"),_continue:p("continue"),_debugger:p("debugger"),_default:p("default",{beforeExpr:i}),_do:p("do",{isLoop:n,beforeExpr:i}),_else:p("else",{beforeExpr:i}),_finally:p("finally"),_for:p("for",{isLoop:n}),_function:p("function",{startsExpr:r}),_if:p("if"),_return:p("return",{beforeExpr:i}),_switch:p("switch"),_throw:p("throw",{beforeExpr:i,prefix:o,startsExpr:r}),_try:p("try"),_var:p("var"),_const:p("const"),_while:p("while",{isLoop:n}),_with:p("with"),_new:p("new",{beforeExpr:i,startsExpr:r}),_this:p("this",{startsExpr:r}),_super:p("super",{startsExpr:r}),_class:p("class",{startsExpr:r}),_extends:p("extends",{beforeExpr:i}),_export:p("export"),_import:p("import",{startsExpr:r}),_null:p("null",{startsExpr:r}),_true:p("true",{startsExpr:r}),_false:p("false",{startsExpr:r}),_in:p("in",{beforeExpr:i,binop:7}),_instanceof:p("instanceof",{beforeExpr:i,binop:7}),_typeof:p("typeof",{beforeExpr:i,prefix:o,startsExpr:r}),_void:p("void",{beforeExpr:i,prefix:o,startsExpr:r}),_delete:p("delete",{beforeExpr:i,prefix:o,startsExpr:r})},f=0,m=1,y=2,g=4,x=8,b=16,v=32,w=64,P=128,T=m|y|P,E=1,A=2,S=4,C=8,k=16,N=64,I=128,O=256,D=512,M=1024,L=E|A|C|I,_=0|E|C|0,R=0|E|S|0,j=0|E|k|0,F=0|A|I,B=0|A,U=E|A|C|O,q=0|M,V=0|N,H=0|E|N,z=U|D,W=0|M,K=4,$=2,X=1,G=$|X,Y=$|K,J=X|K,Q=$,Z=X,tt=0,et=/\r\n?|[\n\u2028\u2029]/,st=new RegExp(et.source,"g");function it(t){switch(t){case 10:case 13:case 8232:case 8233:return!0;default:return!1}}const rt=/(?:\s|\/\/.*|\/\*[^]*?\*\/)*/g;function nt(t){switch(t){case 9:case 11:case 12:case 32:case 160:case 5760:case 8192:case 8193:case 8194:case 8195:case 8196:case 8197:case 8198:case 8199:case 8200:case 8201:case 8202:case 8239:case 8287:case 12288:case 65279:return!0;default:return!1}}class at{constructor(t,e){this.line=t,this.column=e}}class ot{constructor(t,e){this.start=t,this.end=e}}function ct(t,e){let s,i=1,r=0;st.lastIndex=0;while((s=st.exec(t))&&s.index0)i=e[--r];if(null===i)return;for(let a=0;a0?i.trailingComments=n:void 0!==i.trailingComments&&(i.trailingComments=[])}processComment(t){if("Program"===t.type&&t.body.length>0)return;const e=this.state.commentStack;let s,i,r,n,a;if(this.state.trailingComments.length>0)this.state.trailingComments[0].start>=t.end?(r=this.state.trailingComments,this.state.trailingComments=[]):this.state.trailingComments.length=0;else if(e.length>0){const s=lt(e);s.trailingComments&&s.trailingComments[0].start>=t.end&&(r=s.trailingComments,delete s.trailingComments)}e.length>0&<(e).start>=t.start&&(s=e.pop());while(e.length>0&<(e).start>=t.start)i=e.pop();if(!i&&s&&(i=s),s)switch(t.type){case"ObjectExpression":this.adjustCommentsAfterTrailingComma(t,t.properties);break;case"ObjectPattern":this.adjustCommentsAfterTrailingComma(t,t.properties,!0);break;case"CallExpression":this.adjustCommentsAfterTrailingComma(t,t.arguments);break;case"ArrayExpression":this.adjustCommentsAfterTrailingComma(t,t.elements);break;case"ArrayPattern":this.adjustCommentsAfterTrailingComma(t,t.elements,!0);break}else this.state.commentPreviousNode&&("ImportSpecifier"===this.state.commentPreviousNode.type&&"ImportSpecifier"!==t.type||"ExportSpecifier"===this.state.commentPreviousNode.type&&"ExportSpecifier"!==t.type)&&this.adjustCommentsAfterTrailingComma(t,[this.state.commentPreviousNode]);if(i){if(i.leadingComments)if(i!==t&&i.leadingComments.length>0&<(i.leadingComments).end<=t.start)t.leadingComments=i.leadingComments,delete i.leadingComments;else for(n=i.leadingComments.length-2;n>=0;--n)if(i.leadingComments[n].end<=t.start){t.leadingComments=i.leadingComments.splice(0,n+1);break}}else if(this.state.leadingComments.length>0)if(lt(this.state.leadingComments).end<=t.start){if(this.state.commentPreviousNode)for(a=0;a0&&(t.leadingComments=this.state.leadingComments,this.state.leadingComments=[])}else{for(n=0;nt.start)break;const e=this.state.leadingComments.slice(0,n);e.length&&(t.leadingComments=e),r=this.state.leadingComments.slice(n),0===r.length&&(r=null)}if(this.state.commentPreviousNode=t,r)if(r.length&&r[0].start>=t.start&<(r).end<=t.end)t.innerComments=r;else{const e=r.findIndex(e=>e.end>=t.end);e>0?(t.innerComments=r.slice(0,e),t.trailingComments=r.slice(e)):t.trailingComments=r}e.push(t)}}const ut=Object.freeze({ArgumentsDisallowedInInitializer:"'arguments' is not allowed in class field initializer",AsyncFunctionInSingleStatementContext:"Async functions can only be declared at the top level or inside a block",AwaitBindingIdentifier:"Can not use 'await' as identifier inside an async function",AwaitExpressionFormalParameter:"await is not allowed in async function parameters",AwaitNotInAsyncFunction:"Can not use keyword 'await' outside an async function",BadGetterArity:"getter must not have any formal parameters",BadSetterArity:"setter must have exactly one formal parameter",BadSetterRestParameter:"setter function argument must not be a rest parameter",ConstructorClassField:"Classes may not have a field named 'constructor'",ConstructorClassPrivateField:"Classes may not have a private field named '#constructor'",ConstructorIsAccessor:"Class constructor may not be an accessor",ConstructorIsAsync:"Constructor can't be an async function",ConstructorIsGenerator:"Constructor can't be a generator",DeclarationMissingInitializer:"%0 require an initialization value",DecoratorBeforeExport:"Decorators must be placed *before* the 'export' keyword. You can set the 'decoratorsBeforeExport' option to false to use the 'export @decorator class {}' syntax",DecoratorConstructor:"Decorators can't be used with a constructor. Did you mean '@dec class { ... }'?",DecoratorExportClass:"Using the export keyword between a decorator and a class is not allowed. Please use `export @dec class` instead.",DecoratorSemicolon:"Decorators must not be followed by a semicolon",DeletePrivateField:"Deleting a private field is not allowed",DestructureNamedImport:"ES2015 named imports do not destructure. Use another statement for destructuring after the import.",DuplicateConstructor:"Duplicate constructor in the same class",DuplicateDefaultExport:"Only one default export allowed per module.",DuplicateExport:"`%0` has already been exported. Exported identifiers must be unique.",DuplicateProto:"Redefinition of __proto__ property",DuplicateRegExpFlags:"Duplicate regular expression flag",ElementAfterRest:"Rest element must be last element",EscapedCharNotAnIdentifier:"Invalid Unicode escape",ExportDefaultFromAsIdentifier:"'from' is not allowed as an identifier after 'export default'",ForInOfLoopInitializer:"%0 loop variable declaration may not have an initializer",GeneratorInSingleStatementContext:"Generators can only be declared at the top level or inside a block",IllegalBreakContinue:"Unsyntactic %0",IllegalLanguageModeDirective:"Illegal 'use strict' directive in function with non-simple parameter list",IllegalReturn:"'return' outside of function",ImportCallArgumentTrailingComma:"Trailing comma is disallowed inside import(...) arguments",ImportCallArity:"import() requires exactly %0",ImportCallNotNewExpression:"Cannot use new with import(...)",ImportCallSpreadArgument:"... is not allowed in import()",ImportMetaOutsideModule:"import.meta may appear only with 'sourceType: \"module\"'",ImportOutsideModule:"'import' and 'export' may appear only with 'sourceType: \"module\"'",InvalidBigIntLiteral:"Invalid BigIntLiteral",InvalidCodePoint:"Code point out of bounds",InvalidDigit:"Expected number in radix %0",InvalidEscapeSequence:"Bad character escape sequence",InvalidEscapeSequenceTemplate:"Invalid escape sequence in template",InvalidEscapedReservedWord:"Escape sequence in keyword %0",InvalidIdentifier:"Invalid identifier %0",InvalidLhs:"Invalid left-hand side in %0",InvalidLhsBinding:"Binding invalid left-hand side in %0",InvalidNumber:"Invalid number",InvalidOrUnexpectedToken:"Unexpected character '%0'",InvalidParenthesizedAssignment:"Invalid parenthesized assignment pattern",InvalidPrivateFieldResolution:"Private name #%0 is not defined",InvalidPropertyBindingPattern:"Binding member expression",InvalidRecordProperty:"Only properties and spread elements are allowed in record definitions",InvalidRestAssignmentPattern:"Invalid rest operator's argument",LabelRedeclaration:"Label '%0' is already declared",LetInLexicalBinding:"'let' is not allowed to be used as a name in 'let' or 'const' declarations.",MalformedRegExpFlags:"Invalid regular expression flag",MissingClassName:"A class name is required",MissingEqInAssignment:"Only '=' operator can be used for specifying default value.",MissingUnicodeEscape:"Expecting Unicode escape sequence \\uXXXX",MixingCoalesceWithLogical:"Nullish coalescing operator(??) requires parens when mixing with logical operators",ModuleAttributeDifferentFromType:"The only accepted module attribute is `type`",ModuleAttributeInvalidValue:"Only string literals are allowed as module attribute values",ModuleAttributesWithDuplicateKeys:'Duplicate key "%0" is not allowed in module attributes',ModuleExportUndefined:"Export '%0' is not defined",MultipleDefaultsInSwitch:"Multiple default clauses",NewlineAfterThrow:"Illegal newline after throw",NoCatchOrFinally:"Missing catch or finally clause",NumberIdentifier:"Identifier directly after number",NumericSeparatorInEscapeSequence:"Numeric separators are not allowed inside unicode escape sequences or hex escape sequences",ObsoleteAwaitStar:"await* has been removed from the async functions proposal. Use Promise.all() instead.",OptionalChainingNoNew:"constructors in/after an Optional Chain are not allowed",OptionalChainingNoTemplate:"Tagged Template Literals are not allowed in optionalChain",ParamDupe:"Argument name clash",PatternHasAccessor:"Object pattern can't contain getter or setter",PatternHasMethod:"Object pattern can't contain methods",PipelineBodyNoArrow:'Unexpected arrow "=>" after pipeline body; arrow function in pipeline body must be parenthesized',PipelineBodySequenceExpression:"Pipeline body may not be a comma-separated sequence expression",PipelineHeadSequenceExpression:"Pipeline head should not be a comma-separated sequence expression",PipelineTopicUnused:"Pipeline is in topic style but does not use topic reference",PrimaryTopicNotAllowed:"Topic reference was used in a lexical context without topic binding",PrimaryTopicRequiresSmartPipeline:"Primary Topic Reference found but pipelineOperator not passed 'smart' for 'proposal' option.",PrivateInExpectedIn:"Private names are only allowed in property accesses (`obj.#%0`) or in `in` expressions (`#%0 in obj`)",PrivateNameRedeclaration:"Duplicate private name #%0",RecordExpressionBarIncorrectEndSyntaxType:"Record expressions ending with '|}' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'",RecordExpressionBarIncorrectStartSyntaxType:"Record expressions starting with '{|' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'",RecordExpressionHashIncorrectStartSyntaxType:"Record expressions starting with '#{' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'hash'",RecordNoProto:"'__proto__' is not allowed in Record expressions",RestTrailingComma:"Unexpected trailing comma after rest element",SloppyFunction:"In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement",StaticPrototype:"Classes may not have static property named prototype",StrictDelete:"Deleting local variable in strict mode",StrictEvalArguments:"Assigning to '%0' in strict mode",StrictEvalArgumentsBinding:"Binding '%0' in strict mode",StrictFunction:"In strict mode code, functions can only be declared at top level or inside a block",StrictOctalLiteral:"Legacy octal literals are not allowed in strict mode",StrictWith:"'with' in strict mode",SuperNotAllowed:"super() is only valid inside a class constructor of a subclass. Maybe a typo in the method name ('constructor') or not extending another class?",SuperPrivateField:"Private fields can't be accessed on super",TrailingDecorator:"Decorators must be attached to a class element",TupleExpressionBarIncorrectEndSyntaxType:"Tuple expressions ending with '|]' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'",TupleExpressionBarIncorrectStartSyntaxType:"Tuple expressions starting with '[|' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'",TupleExpressionHashIncorrectStartSyntaxType:"Tuple expressions starting with '#[' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'hash'",UnexpectedArgumentPlaceholder:"Unexpected argument placeholder",UnexpectedAwaitAfterPipelineBody:'Unexpected "await" after pipeline body; await must have parentheses in minimal proposal',UnexpectedDigitAfterHash:"Unexpected digit after hash token",UnexpectedImportExport:"'import' and 'export' may only appear at the top level",UnexpectedKeyword:"Unexpected keyword '%0'",UnexpectedLeadingDecorator:"Leading decorators must be attached to a class declaration",UnexpectedLexicalDeclaration:"Lexical declaration cannot appear in a single-statement context",UnexpectedNewTarget:"new.target can only be used in functions",UnexpectedNumericSeparator:"A numeric separator is only allowed between two digits",UnexpectedPrivateField:"Private names can only be used as the name of a class element (i.e. class C { #p = 42; #m() {} } )\n or a property of member expression (i.e. this.#p).",UnexpectedReservedWord:"Unexpected reserved word '%0'",UnexpectedSuper:"super is only allowed in object methods and classes",UnexpectedToken:"Unexpected token '%0'",UnexpectedTokenUnaryExponentiation:"Illegal expression. Wrap left hand side or entire exponentiation in parentheses.",UnsupportedBind:"Binding should be performed on object property.",UnsupportedDecoratorExport:"A decorated export must export a class declaration",UnsupportedDefaultExport:"Only expressions, functions or classes are allowed as the `default` export.",UnsupportedImport:"import can only be used in import() or import.meta",UnsupportedMetaProperty:"The only valid meta property for %0 is %0.%1",UnsupportedParameterDecorator:"Decorators cannot be used to decorate parameters",UnsupportedPropertyDecorator:"Decorators cannot be used to decorate object literal properties",UnsupportedSuper:"super can only be used with function calls (i.e. super()) or in property accesses (i.e. super.prop or super[prop])",UnterminatedComment:"Unterminated comment",UnterminatedRegExp:"Unterminated regular expression",UnterminatedString:"Unterminated string constant",UnterminatedTemplate:"Unterminated template",VarRedeclaration:"Identifier '%0' has already been declared",YieldBindingIdentifier:"Can not use 'yield' as identifier inside a generator",YieldInParameter:"yield is not allowed in generator parameters",ZeroDigitNumericSeparator:"Numeric separator can not be used after leading 0"});class dt extends pt{getLocationForPosition(t){let e;return e=t===this.state.start?this.state.startLoc:t===this.state.lastTokStart?this.state.lastTokStartLoc:t===this.state.end?this.state.endLoc:t===this.state.lastTokEnd?this.state.lastTokEndLoc:ct(this.input,t),e}raise(t,e,...s){return this.raiseWithData(t,void 0,e,...s)}raiseWithData(t,e,s,...i){const r=this.getLocationForPosition(t),n=s.replace(/%(\d+)/g,(t,e)=>i[e])+` (${r.line}:${r.column})`;return this._raise(Object.assign({loc:r,pos:t},e),n)}_raise(t,e){const s=new SyntaxError(e);if(Object.assign(s,t),this.options.errorRecovery)return this.isLookahead||this.state.errors.push(s),s;throw s}}function ft(t){return null!=t&&"Property"===t.type&&"init"===t.kind&&!1===t.method}var mt=t=>class extends t{estreeParseRegExpLiteral({pattern:t,flags:e}){let s=null;try{s=new RegExp(t,e)}catch(r){}const i=this.estreeParseLiteral(s);return i.regex={pattern:t,flags:e},i}estreeParseBigIntLiteral(t){const e="undefined"!==typeof BigInt?BigInt(t):null,s=this.estreeParseLiteral(e);return s.bigint=String(s.value||t),s}estreeParseLiteral(t){return this.parseLiteral(t,"Literal")}directiveToStmt(t){const e=t.value,s=this.startNodeAt(t.start,t.loc.start),i=this.startNodeAt(e.start,e.loc.start);return i.value=e.value,i.raw=e.extra.raw,s.expression=this.finishNodeAt(i,"Literal",e.end,e.loc.end),s.directive=e.extra.raw.slice(1,-1),this.finishNodeAt(s,"ExpressionStatement",t.end,t.loc.end)}initFunction(t,e){super.initFunction(t,e),t.expression=!1}checkDeclaration(t){ft(t)?this.checkDeclaration(t.value):super.checkDeclaration(t)}checkGetterSetterParams(t){const e=t,s="get"===e.kind?0:1,i=e.start;e.value.params.length!==s?"get"===t.kind?this.raise(i,ut.BadGetterArity):this.raise(i,ut.BadSetterArity):"set"===e.kind&&"RestElement"===e.value.params[0].type&&this.raise(i,ut.BadSetterRestParameter)}checkLVal(t,e=V,s,i,r){switch(t.type){case"ObjectPattern":t.properties.forEach(t=>{this.checkLVal("Property"===t.type?t.value:t,e,s,"object destructuring pattern",r)});break;default:super.checkLVal(t,e,s,i,r)}}checkProto(t,e,s,i){t.method||super.checkProto(t,e,s,i)}isValidDirective(t){var e;return"ExpressionStatement"===t.type&&"Literal"===t.expression.type&&"string"===typeof t.expression.value&&!(null==(e=t.expression.extra)?void 0:e.parenthesized)}stmtToDirective(t){const e=super.stmtToDirective(t),s=t.expression.value;return e.value.value=s,e}parseBlockBody(t,e,s,i){super.parseBlockBody(t,e,s,i);const r=t.directives.map(t=>this.directiveToStmt(t));t.body=r.concat(t.body),delete t.directives}pushClassMethod(t,e,s,i,r,n){this.parseMethod(e,s,i,r,n,"ClassMethod",!0),e.typeParameters&&(e.value.typeParameters=e.typeParameters,delete e.typeParameters),t.body.push(e)}parseExprAtom(t){switch(this.state.type){case d.num:case d.string:return this.estreeParseLiteral(this.state.value);case d.regexp:return this.estreeParseRegExpLiteral(this.state.value);case d.bigint:return this.estreeParseBigIntLiteral(this.state.value);case d._null:return this.estreeParseLiteral(null);case d._true:return this.estreeParseLiteral(!0);case d._false:return this.estreeParseLiteral(!1);default:return super.parseExprAtom(t)}}parseLiteral(t,e,s,i){const r=super.parseLiteral(t,e,s,i);return r.raw=r.extra.raw,delete r.extra,r}parseFunctionBody(t,e,s=!1){super.parseFunctionBody(t,e,s),t.expression="BlockStatement"!==t.body.type}parseMethod(t,e,s,i,r,n,a=!1){let o=this.startNode();return o.kind=t.kind,o=super.parseMethod(o,e,s,i,r,n,a),o.type="FunctionExpression",delete o.kind,t.value=o,n="ClassMethod"===n?"MethodDefinition":n,this.finishNode(t,n)}parseObjectMethod(t,e,s,i,r){const n=super.parseObjectMethod(t,e,s,i,r);return n&&(n.type="Property","method"===n.kind&&(n.kind="init"),n.shorthand=!1),n}parseObjectProperty(t,e,s,i,r){const n=super.parseObjectProperty(t,e,s,i,r);return n&&(n.kind="init",n.type="Property"),n}toAssignable(t){return ft(t)?(this.toAssignable(t.value),t):super.toAssignable(t)}toAssignableObjectExpressionProp(t,e){if("get"===t.kind||"set"===t.kind)throw this.raise(t.key.start,ut.PatternHasAccessor);if(t.method)throw this.raise(t.key.start,ut.PatternHasMethod);super.toAssignableObjectExpressionProp(t,e)}finishCallExpression(t,e){return super.finishCallExpression(t,e),"Import"===t.callee.type?(t.type="ImportExpression",t.source=t.arguments[0],delete t.arguments,delete t.callee):"CallExpression"===t.type&&(t.optional=!1),t}toReferencedListDeep(t,e){t&&super.toReferencedListDeep(t,e)}parseExport(t){switch(super.parseExport(t),t.type){case"ExportAllDeclaration":t.exported=null;break;case"ExportNamedDeclaration":1===t.specifiers.length&&"ExportNamespaceSpecifier"===t.specifiers[0].type&&(t.type="ExportAllDeclaration",t.exported=t.specifiers[0].exported,delete t.specifiers);break}return t}parseSubscript(...t){const e=super.parseSubscript(...t);return"MemberExpression"===e.type&&(e.optional=!1),e}};class yt{constructor(t,e,s,i){this.token=t,this.isExpr=!!e,this.preserveSpace=!!s,this.override=i}}const gt={braceStatement:new yt("{",!1),braceExpression:new yt("{",!0),templateQuasi:new yt("${",!1),parenStatement:new yt("(",!1),parenExpression:new yt("(",!0),template:new yt("`",!0,!0,t=>t.readTmplToken()),functionExpression:new yt("function",!0),functionStatement:new yt("function",!1)};d.parenR.updateContext=d.braceR.updateContext=function(){if(1===this.state.context.length)return void(this.state.exprAllowed=!0);let t=this.state.context.pop();t===gt.braceStatement&&"function"===this.curContext().token&&(t=this.state.context.pop()),this.state.exprAllowed=!t.isExpr},d.name.updateContext=function(t){let e=!1;t!==d.dot&&("of"===this.state.value&&!this.state.exprAllowed&&t!==d._function&&t!==d._class||"yield"===this.state.value&&this.prodParam.hasYield)&&(e=!0),this.state.exprAllowed=e,this.state.isIterator&&(this.state.isIterator=!1)},d.braceL.updateContext=function(t){this.state.context.push(this.braceIsBlock(t)?gt.braceStatement:gt.braceExpression),this.state.exprAllowed=!0},d.dollarBraceL.updateContext=function(){this.state.context.push(gt.templateQuasi),this.state.exprAllowed=!0},d.parenL.updateContext=function(t){const e=t===d._if||t===d._for||t===d._with||t===d._while;this.state.context.push(e?gt.parenStatement:gt.parenExpression),this.state.exprAllowed=!0},d.incDec.updateContext=function(){},d._function.updateContext=d._class.updateContext=function(t){t===d.dot||t===d.questionDot||(!t.beforeExpr||t===d.semi||t===d._else||t===d._return&&et.test(this.input.slice(this.state.lastTokEnd,this.state.start))||(t===d.colon||t===d.braceL)&&this.curContext()===gt.b_stat?this.state.context.push(gt.functionStatement):this.state.context.push(gt.functionExpression)),this.state.exprAllowed=!1},d.backQuote.updateContext=function(){this.curContext()===gt.template?this.state.context.pop():this.state.context.push(gt.template),this.state.exprAllowed=!1},d.star.updateContext=function(){this.state.exprAllowed=!1};let xt="ªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-Ֆՙՠ-ֈא-תׯ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࡠ-ࡪࢠ-ࢴࢶ-ࣇऄ-हऽॐक़-ॡॱ-ঀঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱৼਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡૹଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-హఽౘ-ౚౠౡಀಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഄ-ഌഎ-ഐഒ-ഺഽൎൔ-ൖൟ-ൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄຆ-ຊຌ-ຣລວ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏽᏸ-ᏽᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛮ-ᛸᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡸᢀ-ᢨᢪᢰ-ᣵᤀ-ᤞᥐ-ᥭᥰ-ᥴᦀ-ᦫᦰ-ᧉᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᲀ-ᲈᲐ-ᲺᲽ-Ჿᳩ-ᳬᳮ-ᳳᳵᳶᳺᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕ℘-ℝℤΩℨK-ℹℼ-ℿⅅ-ⅉⅎⅠ-ↈⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞ々-〇〡-〩〱-〵〸-〼ぁ-ゖ゛-ゟァ-ヺー-ヿㄅ-ㄯㄱ-ㆎㆠ-ㆿㇰ-ㇿ㐀-䶿一-鿼ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚝꚠ-ꛯꜗ-ꜟꜢ-ꞈꞋ-ꞿꟂ-ꟊꟵ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꣽꣾꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꧠ-ꧤꧦ-ꧯꧺ-ꧾꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꩾ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꬰ-ꭚꭜ-ꭩꭰ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ",bt="‌‍·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-٩ٰۖ-ۜ۟-۪ۤۧۨ-ۭ۰-۹ܑܰ-݊ަ-ް߀-߉߫-߽߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛࣓-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣ०-९ঁ-ঃ়া-ৄেৈো-্ৗৢৣ০-৯৾ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑ੦-ੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣ૦-૯ૺ-૿ଁ-ଃ଼ା-ୄେୈୋ-୍୕-ୗୢୣ୦-୯ஂா-ூெ-ைொ-்ௗ௦-௯ఀ-ఄా-ౄె-ైొ-్ౕౖౢౣ౦-౯ಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣ೦-೯ഀ-ഃ഻഼ാ-ൄെ-ൈൊ-്ൗൢൣ൦-൯ඁ-ඃ්ා-ුූෘ-ෟ෦-෯ෲෳัิ-ฺ็-๎๐-๙ັິ-ຼ່-ໍ໐-໙༘༙༠-༩༹༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှ၀-၉ၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏ-ႝ፝-፟፩-፱ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝០-៩᠋-᠍᠐-᠙ᢩᤠ-ᤫᤰ-᤻᥆-᥏᧐-᧚ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼-᪉᪐-᪙᪰-᪽ᪿᫀᬀ-ᬄ᬴-᭄᭐-᭙᭫-᭳ᮀ-ᮂᮡ-ᮭ᮰-᮹᯦-᯳ᰤ-᰷᱀-᱉᱐-᱙᳐-᳔᳒-᳨᳭᳴᳷-᳹᷀-᷹᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꘠-꘩꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧ꠬ꢀꢁꢴ-ꣅ꣐-꣙꣠-꣱ꣿ-꤉ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀꧐-꧙ꧥ꧰-꧹ꨩ-ꨶꩃꩌꩍ꩐-꩙ꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭꯰-꯹ﬞ︀-️︠-︯︳︴﹍-﹏0-9_";const vt=new RegExp("["+xt+"]"),wt=new RegExp("["+xt+bt+"]");xt=bt=null;const Pt=[0,11,2,25,2,18,2,1,2,14,3,13,35,122,70,52,268,28,4,48,48,31,14,29,6,37,11,29,3,35,5,7,2,4,43,157,19,35,5,35,5,39,9,51,157,310,10,21,11,7,153,5,3,0,2,43,2,1,4,0,3,22,11,22,10,30,66,18,2,1,11,21,11,25,71,55,7,1,65,0,16,3,2,2,2,28,43,28,4,28,36,7,2,27,28,53,11,21,11,18,14,17,111,72,56,50,14,50,14,35,349,41,7,1,79,28,11,0,9,21,107,20,28,22,13,52,76,44,33,24,27,35,30,0,3,0,9,34,4,0,13,47,15,3,22,0,2,0,36,17,2,24,85,6,2,0,2,3,2,14,2,9,8,46,39,7,3,1,3,21,2,6,2,1,2,4,4,0,19,0,13,4,159,52,19,3,21,2,31,47,21,1,2,0,185,46,42,3,37,47,21,0,60,42,14,0,72,26,230,43,117,63,32,7,3,0,3,7,2,1,2,23,16,0,2,0,95,7,3,38,17,0,2,0,29,0,11,39,8,0,22,0,12,45,20,0,35,56,264,8,2,36,18,0,50,29,113,6,2,1,2,37,22,0,26,5,2,1,2,31,15,0,328,18,190,0,80,921,103,110,18,195,2749,1070,4050,582,8634,568,8,30,114,29,19,47,17,3,32,20,6,18,689,63,129,74,6,0,67,12,65,1,2,0,29,6135,9,1237,43,8,8952,286,50,2,18,3,9,395,2309,106,6,12,4,8,8,9,5991,84,2,70,2,1,3,0,3,1,3,3,2,11,2,0,2,6,2,64,2,3,3,7,2,6,2,27,2,3,2,4,2,0,4,6,2,339,3,24,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,7,2357,44,11,6,17,0,370,43,1301,196,60,67,8,0,1205,3,2,26,2,1,2,0,3,0,2,9,2,3,2,0,2,0,7,0,5,0,2,0,2,0,2,2,2,1,2,0,3,0,2,0,2,0,2,0,2,0,2,1,2,0,3,3,2,6,2,3,2,3,2,0,2,9,2,16,6,2,2,4,2,16,4421,42717,35,4148,12,221,3,5761,15,7472,3104,541,1507,4938],Tt=[509,0,227,0,150,4,294,9,1368,2,2,1,6,3,41,2,5,0,166,1,574,3,9,9,370,1,154,10,176,2,54,14,32,9,16,3,46,10,54,9,7,2,37,13,2,9,6,1,45,0,13,2,49,13,9,3,2,11,83,11,7,0,161,11,6,9,7,3,56,1,2,6,3,1,3,2,10,0,11,1,3,6,4,4,193,17,10,9,5,0,82,19,13,9,214,6,3,8,28,1,83,16,16,9,82,12,9,9,84,14,5,9,243,14,166,9,71,5,2,1,3,3,2,0,2,1,13,9,120,6,3,6,4,0,29,9,41,6,2,3,9,0,10,10,47,15,406,7,2,7,17,9,57,21,2,13,123,5,4,0,2,1,2,6,2,0,9,9,49,4,2,1,2,4,9,9,330,3,19306,9,135,4,60,6,26,9,1014,0,2,54,8,3,82,0,12,1,19628,1,5319,4,4,5,9,7,3,6,31,3,149,2,1418,49,513,54,5,49,9,0,15,0,23,4,2,14,1361,6,2,16,3,6,2,1,2,4,262,6,10,9,419,13,1495,6,110,6,6,9,4759,9,787719,239];function Et(t,e){let s=65536;for(let i=0,r=e.length;it)return!1;if(s+=e[i+1],s>=t)return!0}return!1}function At(t){return t<65?36===t:t<=90||(t<97?95===t:t<=122||(t<=65535?t>=170&&vt.test(String.fromCharCode(t)):Et(t,Pt)))}function St(t){return t<48?36===t:t<58||!(t<65)&&(t<=90||(t<97?95===t:t<=122||(t<=65535?t>=170&&wt.test(String.fromCharCode(t)):Et(t,Pt)||Et(t,Tt))))}const Ct={keyword:["break","case","catch","continue","debugger","default","do","else","finally","for","function","if","return","switch","throw","try","var","const","while","with","new","this","super","class","extends","export","import","null","true","false","in","instanceof","typeof","void","delete"],strict:["implements","interface","let","package","private","protected","public","static","yield"],strictBind:["eval","arguments"]},kt=new Set(Ct.keyword),Nt=new Set(Ct.strict),It=new Set(Ct.strictBind);function Ot(t,e){return e&&"await"===t||"enum"===t}function Dt(t,e){return Ot(t,e)||Nt.has(t)}function Mt(t){return It.has(t)}function Lt(t,e){return Dt(t,e)||Mt(t)}function _t(t){return kt.has(t)}const Rt=/^in(stanceof)?$/;function jt(t,e){return 64===t&&64===e}const Ft=new Set(["_","any","bool","boolean","empty","extends","false","interface","mixed","null","number","static","string","true","typeof","void"]),Bt=Object.freeze({AmbiguousConditionalArrow:"Ambiguous expression: wrap the arrow functions in parentheses to disambiguate.",AmbiguousDeclareModuleKind:"Found both `declare module.exports` and `declare export` in the same module. Modules can only have 1 since they are either an ES module or they are a CommonJS module",AssignReservedType:"Cannot overwrite reserved type %0",DeclareClassElement:"The `declare` modifier can only appear on class fields.",DeclareClassFieldInitializer:"Initializers are not allowed in fields with the `declare` modifier.",DuplicateDeclareModuleExports:"Duplicate `declare module.exports` statement",EnumBooleanMemberNotInitialized:"Boolean enum members need to be initialized. Use either `%0 = true,` or `%0 = false,` in enum `%1`.",EnumDuplicateMemberName:"Enum member names need to be unique, but the name `%0` has already been used before in enum `%1`.",EnumInconsistentMemberValues:"Enum `%0` has inconsistent member initializers. Either use no initializers, or consistently use literals (either booleans, numbers, or strings) for all member initializers.",EnumInvalidExplicitType:"Enum type `%1` is not valid. Use one of `boolean`, `number`, `string`, or `symbol` in enum `%0`.",EnumInvalidExplicitTypeUnknownSupplied:"Supplied enum type is not valid. Use one of `boolean`, `number`, `string`, or `symbol` in enum `%0`.",EnumInvalidMemberInitializerPrimaryType:"Enum `%0` has type `%2`, so the initializer of `%1` needs to be a %2 literal.",EnumInvalidMemberInitializerSymbolType:"Symbol enum members cannot be initialized. Use `%1,` in enum `%0`.",EnumInvalidMemberInitializerUnknownType:"The enum member initializer for `%1` needs to be a literal (either a boolean, number, or string) in enum `%0`.",EnumInvalidMemberName:"Enum member names cannot start with lowercase 'a' through 'z'. Instead of using `%0`, consider using `%1`, in enum `%2`.",EnumNumberMemberNotInitialized:"Number enum members need to be initialized, e.g. `%1 = 1` in enum `%0`.",EnumStringMemberInconsistentlyInitailized:"String enum members need to consistently either all use initializers, or use no initializers, in enum `%0`.",ImportTypeShorthandOnlyInPureImport:"The `type` and `typeof` keywords on named imports can only be used on regular `import` statements. It cannot be used with `import type` or `import typeof` statements",InexactInsideExact:"Explicit inexact syntax cannot appear inside an explicit exact object type",InexactInsideNonObject:"Explicit inexact syntax cannot appear in class or interface definitions",InexactVariance:"Explicit inexact syntax cannot have variance",InvalidNonTypeImportInDeclareModule:"Imports within a `declare module` body must always be `import type` or `import typeof`",MissingTypeParamDefault:"Type parameter declaration needs a default, since a preceding type parameter declaration has a default.",NestedDeclareModule:"`declare module` cannot be used inside another `declare module`",NestedFlowComment:"Cannot have a flow comment inside another flow comment",OptionalBindingPattern:"A binding pattern parameter cannot be optional in an implementation signature.",SpreadVariance:"Spread properties cannot have variance",TypeBeforeInitializer:"Type annotations must come before default assignments, e.g. instead of `age = 25: number` use `age: number = 25`",TypeCastInPattern:"The type cast expression is expected to be wrapped with parenthesis",UnexpectedExplicitInexactInObject:"Explicit inexact syntax must appear at the end of an inexact object",UnexpectedReservedType:"Unexpected reserved type %0",UnexpectedReservedUnderscore:"`_` is only allowed as a type argument to call or new",UnexpectedSpaceBetweenModuloChecks:"Spaces between `%` and `checks` are not allowed here.",UnexpectedSpreadType:"Spread operator cannot appear in class or interface definitions",UnexpectedSubtractionOperand:'Unexpected token, expected "number" or "bigint"',UnexpectedTokenAfterTypeParameter:"Expected an arrow function after this type parameter declaration",UnsupportedDeclareExportKind:"`declare export %0` is not supported. Use `%1` instead",UnsupportedStatementInDeclareModule:"Only declares and type imports are allowed inside declare module",UnterminatedFlowComment:"Unterminated flow-comment"});function Ut(t){return"DeclareExportAllDeclaration"===t.type||"DeclareExportDeclaration"===t.type&&(!t.declaration||"TypeAlias"!==t.declaration.type&&"InterfaceDeclaration"!==t.declaration.type)}function qt(t){return"type"===t.importKind||"typeof"===t.importKind}function Vt(t){return(t.type===d.name||!!t.type.keyword)&&"from"!==t.value}const Ht={const:"declare export var",let:"declare export var",type:"export type",interface:"export interface"};function zt(t,e){const s=[],i=[];for(let r=0;rclass extends t{constructor(t,e){super(t,e),this.flowPragma=void 0}shouldParseTypes(){return this.getPluginOption("flow","all")||"flow"===this.flowPragma}shouldParseEnums(){return!!this.getPluginOption("flow","enums")}finishToken(t,e){return t!==d.string&&t!==d.semi&&t!==d.interpreterDirective&&void 0===this.flowPragma&&(this.flowPragma=null),super.finishToken(t,e)}addComment(t){if(void 0===this.flowPragma){const e=Wt.exec(t.value);if(e)if("flow"===e[1])this.flowPragma="flow";else{if("noflow"!==e[1])throw new Error("Unexpected flow pragma");this.flowPragma="noflow"}else;}return super.addComment(t)}flowParseTypeInitialiser(t){const e=this.state.inType;this.state.inType=!0,this.expect(t||d.colon);const s=this.flowParseType();return this.state.inType=e,s}flowParsePredicate(){const t=this.startNode(),e=this.state.startLoc,s=this.state.start;this.expect(d.modulo);const i=this.state.startLoc;return this.expectContextual("checks"),e.line===i.line&&e.column===i.column-1||this.raise(s,Bt.UnexpectedSpaceBetweenModuloChecks),this.eat(d.parenL)?(t.value=this.parseExpression(),this.expect(d.parenR),this.finishNode(t,"DeclaredPredicate")):this.finishNode(t,"InferredPredicate")}flowParseTypeAndPredicateInitialiser(){const t=this.state.inType;this.state.inType=!0,this.expect(d.colon);let e=null,s=null;return this.match(d.modulo)?(this.state.inType=t,s=this.flowParsePredicate()):(e=this.flowParseType(),this.state.inType=t,this.match(d.modulo)&&(s=this.flowParsePredicate())),[e,s]}flowParseDeclareClass(t){return this.next(),this.flowParseInterfaceish(t,!0),this.finishNode(t,"DeclareClass")}flowParseDeclareFunction(t){this.next();const e=t.id=this.parseIdentifier(),s=this.startNode(),i=this.startNode();this.isRelational("<")?s.typeParameters=this.flowParseTypeParameterDeclaration():s.typeParameters=null,this.expect(d.parenL);const r=this.flowParseFunctionTypeParams();return s.params=r.params,s.rest=r.rest,this.expect(d.parenR),[s.returnType,t.predicate]=this.flowParseTypeAndPredicateInitialiser(),i.typeAnnotation=this.finishNode(s,"FunctionTypeAnnotation"),e.typeAnnotation=this.finishNode(i,"TypeAnnotation"),this.resetEndLocation(e),this.semicolon(),this.finishNode(t,"DeclareFunction")}flowParseDeclare(t,e){if(this.match(d._class))return this.flowParseDeclareClass(t);if(this.match(d._function))return this.flowParseDeclareFunction(t);if(this.match(d._var))return this.flowParseDeclareVariable(t);if(this.eatContextual("module"))return this.match(d.dot)?this.flowParseDeclareModuleExports(t):(e&&this.raise(this.state.lastTokStart,Bt.NestedDeclareModule),this.flowParseDeclareModule(t));if(this.isContextual("type"))return this.flowParseDeclareTypeAlias(t);if(this.isContextual("opaque"))return this.flowParseDeclareOpaqueType(t);if(this.isContextual("interface"))return this.flowParseDeclareInterface(t);if(this.match(d._export))return this.flowParseDeclareExportDeclaration(t,e);throw this.unexpected()}flowParseDeclareVariable(t){return this.next(),t.id=this.flowParseTypeAnnotatableIdentifier(!0),this.scope.declareName(t.id.name,R,t.id.start),this.semicolon(),this.finishNode(t,"DeclareVariable")}flowParseDeclareModule(t){this.scope.enter(f),this.match(d.string)?t.id=this.parseExprAtom():t.id=this.parseIdentifier();const e=t.body=this.startNode(),s=e.body=[];this.expect(d.braceL);while(!this.match(d.braceR)){let t=this.startNode();this.match(d._import)?(this.next(),this.isContextual("type")||this.match(d._typeof)||this.raise(this.state.lastTokStart,Bt.InvalidNonTypeImportInDeclareModule),this.parseImport(t)):(this.expectContextual("declare",Bt.UnsupportedStatementInDeclareModule),t=this.flowParseDeclare(t,!0)),s.push(t)}this.scope.exit(),this.expect(d.braceR),this.finishNode(e,"BlockStatement");let i=null,r=!1;return s.forEach(t=>{Ut(t)?("CommonJS"===i&&this.raise(t.start,Bt.AmbiguousDeclareModuleKind),i="ES"):"DeclareModuleExports"===t.type&&(r&&this.raise(t.start,Bt.DuplicateDeclareModuleExports),"ES"===i&&this.raise(t.start,Bt.AmbiguousDeclareModuleKind),i="CommonJS",r=!0)}),t.kind=i||"CommonJS",this.finishNode(t,"DeclareModule")}flowParseDeclareExportDeclaration(t,e){if(this.expect(d._export),this.eat(d._default))return this.match(d._function)||this.match(d._class)?t.declaration=this.flowParseDeclare(this.startNode()):(t.declaration=this.flowParseType(),this.semicolon()),t.default=!0,this.finishNode(t,"DeclareExportDeclaration");if(this.match(d._const)||this.isLet()||(this.isContextual("type")||this.isContextual("interface"))&&!e){const t=this.state.value,e=Ht[t];throw this.raise(this.state.start,Bt.UnsupportedDeclareExportKind,t,e)}if(this.match(d._var)||this.match(d._function)||this.match(d._class)||this.isContextual("opaque"))return t.declaration=this.flowParseDeclare(this.startNode()),t.default=!1,this.finishNode(t,"DeclareExportDeclaration");if(this.match(d.star)||this.match(d.braceL)||this.isContextual("interface")||this.isContextual("type")||this.isContextual("opaque"))return t=this.parseExport(t),"ExportNamedDeclaration"===t.type&&(t.type="ExportDeclaration",t.default=!1,delete t.exportKind),t.type="Declare"+t.type,t;throw this.unexpected()}flowParseDeclareModuleExports(t){return this.next(),this.expectContextual("exports"),t.typeAnnotation=this.flowParseTypeAnnotation(),this.semicolon(),this.finishNode(t,"DeclareModuleExports")}flowParseDeclareTypeAlias(t){return this.next(),this.flowParseTypeAlias(t),t.type="DeclareTypeAlias",t}flowParseDeclareOpaqueType(t){return this.next(),this.flowParseOpaqueType(t,!0),t.type="DeclareOpaqueType",t}flowParseDeclareInterface(t){return this.next(),this.flowParseInterfaceish(t),this.finishNode(t,"DeclareInterface")}flowParseInterfaceish(t,e=!1){if(t.id=this.flowParseRestrictedIdentifier(!e,!0),this.scope.declareName(t.id.name,e?j:_,t.id.start),this.isRelational("<")?t.typeParameters=this.flowParseTypeParameterDeclaration():t.typeParameters=null,t.extends=[],t.implements=[],t.mixins=[],this.eat(d._extends))do{t.extends.push(this.flowParseInterfaceExtends())}while(!e&&this.eat(d.comma));if(this.isContextual("mixins")){this.next();do{t.mixins.push(this.flowParseInterfaceExtends())}while(this.eat(d.comma))}if(this.isContextual("implements")){this.next();do{t.implements.push(this.flowParseInterfaceExtends())}while(this.eat(d.comma))}t.body=this.flowParseObjectType({allowStatic:e,allowExact:!1,allowSpread:!1,allowProto:e,allowInexact:!1})}flowParseInterfaceExtends(){const t=this.startNode();return t.id=this.flowParseQualifiedTypeIdentifier(),this.isRelational("<")?t.typeParameters=this.flowParseTypeParameterInstantiation():t.typeParameters=null,this.finishNode(t,"InterfaceExtends")}flowParseInterface(t){return this.flowParseInterfaceish(t),this.finishNode(t,"InterfaceDeclaration")}checkNotUnderscore(t){"_"===t&&this.raise(this.state.start,Bt.UnexpectedReservedUnderscore)}checkReservedType(t,e,s){Ft.has(t)&&this.raise(e,s?Bt.AssignReservedType:Bt.UnexpectedReservedType,t)}flowParseRestrictedIdentifier(t,e){return this.checkReservedType(this.state.value,this.state.start,e),this.parseIdentifier(t)}flowParseTypeAlias(t){return t.id=this.flowParseRestrictedIdentifier(!1,!0),this.scope.declareName(t.id.name,_,t.id.start),this.isRelational("<")?t.typeParameters=this.flowParseTypeParameterDeclaration():t.typeParameters=null,t.right=this.flowParseTypeInitialiser(d.eq),this.semicolon(),this.finishNode(t,"TypeAlias")}flowParseOpaqueType(t,e){return this.expectContextual("type"),t.id=this.flowParseRestrictedIdentifier(!0,!0),this.scope.declareName(t.id.name,_,t.id.start),this.isRelational("<")?t.typeParameters=this.flowParseTypeParameterDeclaration():t.typeParameters=null,t.supertype=null,this.match(d.colon)&&(t.supertype=this.flowParseTypeInitialiser(d.colon)),t.impltype=null,e||(t.impltype=this.flowParseTypeInitialiser(d.eq)),this.semicolon(),this.finishNode(t,"OpaqueType")}flowParseTypeParameter(t=!1){const e=this.state.start,s=this.startNode(),i=this.flowParseVariance(),r=this.flowParseTypeAnnotatableIdentifier();return s.name=r.name,s.variance=i,s.bound=r.typeAnnotation,this.match(d.eq)?(this.eat(d.eq),s.default=this.flowParseType()):t&&this.raise(e,Bt.MissingTypeParamDefault),this.finishNode(s,"TypeParameter")}flowParseTypeParameterDeclaration(){const t=this.state.inType,e=this.startNode();e.params=[],this.state.inType=!0,this.isRelational("<")||this.match(d.jsxTagStart)?this.next():this.unexpected();let s=!1;do{const t=this.flowParseTypeParameter(s);e.params.push(t),t.default&&(s=!0),this.isRelational(">")||this.expect(d.comma)}while(!this.isRelational(">"));return this.expectRelational(">"),this.state.inType=t,this.finishNode(e,"TypeParameterDeclaration")}flowParseTypeParameterInstantiation(){const t=this.startNode(),e=this.state.inType;t.params=[],this.state.inType=!0,this.expectRelational("<");const s=this.state.noAnonFunctionType;this.state.noAnonFunctionType=!1;while(!this.isRelational(">"))t.params.push(this.flowParseType()),this.isRelational(">")||this.expect(d.comma);return this.state.noAnonFunctionType=s,this.expectRelational(">"),this.state.inType=e,this.finishNode(t,"TypeParameterInstantiation")}flowParseTypeParameterInstantiationCallOrNew(){const t=this.startNode(),e=this.state.inType;t.params=[],this.state.inType=!0,this.expectRelational("<");while(!this.isRelational(">"))t.params.push(this.flowParseTypeOrImplicitInstantiation()),this.isRelational(">")||this.expect(d.comma);return this.expectRelational(">"),this.state.inType=e,this.finishNode(t,"TypeParameterInstantiation")}flowParseInterfaceType(){const t=this.startNode();if(this.expectContextual("interface"),t.extends=[],this.eat(d._extends))do{t.extends.push(this.flowParseInterfaceExtends())}while(this.eat(d.comma));return t.body=this.flowParseObjectType({allowStatic:!1,allowExact:!1,allowSpread:!1,allowProto:!1,allowInexact:!1}),this.finishNode(t,"InterfaceTypeAnnotation")}flowParseObjectPropertyKey(){return this.match(d.num)||this.match(d.string)?this.parseExprAtom():this.parseIdentifier(!0)}flowParseObjectTypeIndexer(t,e,s){return t.static=e,this.lookahead().type===d.colon?(t.id=this.flowParseObjectPropertyKey(),t.key=this.flowParseTypeInitialiser()):(t.id=null,t.key=this.flowParseType()),this.expect(d.bracketR),t.value=this.flowParseTypeInitialiser(),t.variance=s,this.finishNode(t,"ObjectTypeIndexer")}flowParseObjectTypeInternalSlot(t,e){return t.static=e,t.id=this.flowParseObjectPropertyKey(),this.expect(d.bracketR),this.expect(d.bracketR),this.isRelational("<")||this.match(d.parenL)?(t.method=!0,t.optional=!1,t.value=this.flowParseObjectTypeMethodish(this.startNodeAt(t.start,t.loc.start))):(t.method=!1,this.eat(d.question)&&(t.optional=!0),t.value=this.flowParseTypeInitialiser()),this.finishNode(t,"ObjectTypeInternalSlot")}flowParseObjectTypeMethodish(t){t.params=[],t.rest=null,t.typeParameters=null,this.isRelational("<")&&(t.typeParameters=this.flowParseTypeParameterDeclaration()),this.expect(d.parenL);while(!this.match(d.parenR)&&!this.match(d.ellipsis))t.params.push(this.flowParseFunctionTypeParam()),this.match(d.parenR)||this.expect(d.comma);return this.eat(d.ellipsis)&&(t.rest=this.flowParseFunctionTypeParam()),this.expect(d.parenR),t.returnType=this.flowParseTypeInitialiser(),this.finishNode(t,"FunctionTypeAnnotation")}flowParseObjectTypeCallProperty(t,e){const s=this.startNode();return t.static=e,t.value=this.flowParseObjectTypeMethodish(s),this.finishNode(t,"ObjectTypeCallProperty")}flowParseObjectType({allowStatic:t,allowExact:e,allowSpread:s,allowProto:i,allowInexact:r}){const n=this.state.inType;this.state.inType=!0;const a=this.startNode();let o,c;a.callProperties=[],a.properties=[],a.indexers=[],a.internalSlots=[];let h=!1;e&&this.match(d.braceBarL)?(this.expect(d.braceBarL),o=d.braceBarR,c=!0):(this.expect(d.braceL),o=d.braceR,c=!1),a.exact=c;while(!this.match(o)){let e=!1,n=null,o=null;const l=this.startNode();if(i&&this.isContextual("proto")){const e=this.lookahead();e.type!==d.colon&&e.type!==d.question&&(this.next(),n=this.state.start,t=!1)}if(t&&this.isContextual("static")){const t=this.lookahead();t.type!==d.colon&&t.type!==d.question&&(this.next(),e=!0)}const p=this.flowParseVariance();if(this.eat(d.bracketL))null!=n&&this.unexpected(n),this.eat(d.bracketL)?(p&&this.unexpected(p.start),a.internalSlots.push(this.flowParseObjectTypeInternalSlot(l,e))):a.indexers.push(this.flowParseObjectTypeIndexer(l,e,p));else if(this.match(d.parenL)||this.isRelational("<"))null!=n&&this.unexpected(n),p&&this.unexpected(p.start),a.callProperties.push(this.flowParseObjectTypeCallProperty(l,e));else{let t="init";if(this.isContextual("get")||this.isContextual("set")){const e=this.lookahead();e.type!==d.name&&e.type!==d.string&&e.type!==d.num||(t=this.state.value,this.next())}const i=this.flowParseObjectTypeProperty(l,e,n,p,t,s,null!=r?r:!c);null===i?(h=!0,o=this.state.lastTokStart):a.properties.push(i)}this.flowObjectTypeSemicolon(),!o||this.match(d.braceR)||this.match(d.braceBarR)||this.raise(o,Bt.UnexpectedExplicitInexactInObject)}this.expect(o),s&&(a.inexact=h);const l=this.finishNode(a,"ObjectTypeAnnotation");return this.state.inType=n,l}flowParseObjectTypeProperty(t,e,s,i,r,n,a){if(this.eat(d.ellipsis)){const e=this.match(d.comma)||this.match(d.semi)||this.match(d.braceR)||this.match(d.braceBarR);return e?(n?a||this.raise(this.state.lastTokStart,Bt.InexactInsideExact):this.raise(this.state.lastTokStart,Bt.InexactInsideNonObject),i&&this.raise(i.start,Bt.InexactVariance),null):(n||this.raise(this.state.lastTokStart,Bt.UnexpectedSpreadType),null!=s&&this.unexpected(s),i&&this.raise(i.start,Bt.SpreadVariance),t.argument=this.flowParseType(),this.finishNode(t,"ObjectTypeSpreadProperty"))}{t.key=this.flowParseObjectPropertyKey(),t.static=e,t.proto=null!=s,t.kind=r;let n=!1;return this.isRelational("<")||this.match(d.parenL)?(t.method=!0,null!=s&&this.unexpected(s),i&&this.unexpected(i.start),t.value=this.flowParseObjectTypeMethodish(this.startNodeAt(t.start,t.loc.start)),"get"!==r&&"set"!==r||this.flowCheckGetterSetterParams(t)):("init"!==r&&this.unexpected(),t.method=!1,this.eat(d.question)&&(n=!0),t.value=this.flowParseTypeInitialiser(),t.variance=i),t.optional=n,this.finishNode(t,"ObjectTypeProperty")}}flowCheckGetterSetterParams(t){const e="get"===t.kind?0:1,s=t.start,i=t.value.params.length+(t.value.rest?1:0);i!==e&&("get"===t.kind?this.raise(s,ut.BadGetterArity):this.raise(s,ut.BadSetterArity)),"set"===t.kind&&t.value.rest&&this.raise(s,ut.BadSetterRestParameter)}flowObjectTypeSemicolon(){this.eat(d.semi)||this.eat(d.comma)||this.match(d.braceR)||this.match(d.braceBarR)||this.unexpected()}flowParseQualifiedTypeIdentifier(t,e,s){t=t||this.state.start,e=e||this.state.startLoc;let i=s||this.flowParseRestrictedIdentifier(!0);while(this.eat(d.dot)){const s=this.startNodeAt(t,e);s.qualification=i,s.id=this.flowParseRestrictedIdentifier(!0),i=this.finishNode(s,"QualifiedTypeIdentifier")}return i}flowParseGenericType(t,e,s){const i=this.startNodeAt(t,e);return i.typeParameters=null,i.id=this.flowParseQualifiedTypeIdentifier(t,e,s),this.isRelational("<")&&(i.typeParameters=this.flowParseTypeParameterInstantiation()),this.finishNode(i,"GenericTypeAnnotation")}flowParseTypeofType(){const t=this.startNode();return this.expect(d._typeof),t.argument=this.flowParsePrimaryType(),this.finishNode(t,"TypeofTypeAnnotation")}flowParseTupleType(){const t=this.startNode();t.types=[],this.expect(d.bracketL);while(this.state.possuper.parseFunctionBody(t,!0,s)):super.parseFunctionBody(t,!1,s)}parseFunctionBodyAndFinish(t,e,s=!1){if(this.match(d.colon)){const e=this.startNode();[e.typeAnnotation,t.predicate]=this.flowParseTypeAndPredicateInitialiser(),t.returnType=e.typeAnnotation?this.finishNode(e,"TypeAnnotation"):null}super.parseFunctionBodyAndFinish(t,e,s)}parseStatement(t,e){if(this.state.strict&&this.match(d.name)&&"interface"===this.state.value){const t=this.startNode();return this.next(),this.flowParseInterface(t)}if(this.shouldParseEnums()&&this.isContextual("enum")){const t=this.startNode();return this.next(),this.flowParseEnumDeclaration(t)}{const s=super.parseStatement(t,e);return void 0!==this.flowPragma||this.isValidDirective(s)||(this.flowPragma=null),s}}parseExpressionStatement(t,e){if("Identifier"===e.type)if("declare"===e.name){if(this.match(d._class)||this.match(d.name)||this.match(d._function)||this.match(d._var)||this.match(d._export))return this.flowParseDeclare(t)}else if(this.match(d.name)){if("interface"===e.name)return this.flowParseInterface(t);if("type"===e.name)return this.flowParseTypeAlias(t);if("opaque"===e.name)return this.flowParseOpaqueType(t,!1)}return super.parseExpressionStatement(t,e)}shouldParseExportDeclaration(){return this.isContextual("type")||this.isContextual("interface")||this.isContextual("opaque")||this.shouldParseEnums()&&this.isContextual("enum")||super.shouldParseExportDeclaration()}isExportDefaultSpecifier(){return(!this.match(d.name)||!("type"===this.state.value||"interface"===this.state.value||"opaque"===this.state.value||this.shouldParseEnums()&&"enum"===this.state.value))&&super.isExportDefaultSpecifier()}parseExportDefaultExpression(){if(this.shouldParseEnums()&&this.isContextual("enum")){const t=this.startNode();return this.next(),this.flowParseEnumDeclaration(t)}return super.parseExportDefaultExpression()}parseConditional(t,e,s,i,r){if(!this.match(d.question))return t;if(r){const n=this.tryParse(()=>super.parseConditional(t,e,s,i));return n.node?(n.error&&(this.state=n.failState),n.node):(r.start=n.error.pos||this.state.start,t)}this.expect(d.question);const n=this.state.clone(),a=this.state.noArrowAt,o=this.startNodeAt(s,i);let{consequent:c,failed:h}=this.tryParseConditionalConsequent(),[l,p]=this.getArrowLikeExpressions(c);if(h||p.length>0){const t=[...a];if(p.length>0){this.state=n,this.state.noArrowAt=t;for(let e=0;e1&&this.raise(n.start,Bt.AmbiguousConditionalArrow),h&&1===l.length&&(this.state=n,this.state.noArrowAt=t.concat(l[0].start),({consequent:c,failed:h}=this.tryParseConditionalConsequent()))}return this.getArrowLikeExpressions(c,!0),this.state.noArrowAt=a,this.expect(d.colon),o.test=t,o.consequent=c,o.alternate=this.forwardNoArrowParamsConversionAt(o,()=>this.parseMaybeAssign(e,void 0,void 0,void 0)),this.finishNode(o,"ConditionalExpression")}tryParseConditionalConsequent(){this.state.noArrowParamsConversionAt.push(this.state.start);const t=this.parseMaybeAssign(),e=!this.match(d.colon);return this.state.noArrowParamsConversionAt.pop(),{consequent:t,failed:e}}getArrowLikeExpressions(t,e){const s=[t],i=[];while(0!==s.length){const t=s.pop();"ArrowFunctionExpression"===t.type?(t.typeParameters||!t.returnType?this.finishArrowValidation(t):i.push(t),s.push(t.body)):"ConditionalExpression"===t.type&&(s.push(t.consequent),s.push(t.alternate))}return e?(i.forEach(t=>this.finishArrowValidation(t)),[i,[]]):zt(i,t=>t.params.every(t=>this.isAssignable(t,!0)))}finishArrowValidation(t){var e;this.toAssignableList(t.params,null==(e=t.extra)?void 0:e.trailingComma),this.scope.enter(y|g),super.checkParams(t,!1,!0),this.scope.exit()}forwardNoArrowParamsConversionAt(t,e){let s;return-1!==this.state.noArrowParamsConversionAt.indexOf(t.start)?(this.state.noArrowParamsConversionAt.push(this.state.start),s=e(),this.state.noArrowParamsConversionAt.pop()):s=e(),s}parseParenItem(t,e,s){if(t=super.parseParenItem(t,e,s),this.eat(d.question)&&(t.optional=!0,this.resetEndLocation(t)),this.match(d.colon)){const i=this.startNodeAt(e,s);return i.expression=t,i.typeAnnotation=this.flowParseTypeAnnotation(),this.finishNode(i,"TypeCastExpression")}return t}assertModuleNodeAllowed(t){"ImportDeclaration"===t.type&&("type"===t.importKind||"typeof"===t.importKind)||"ExportNamedDeclaration"===t.type&&"type"===t.exportKind||"ExportAllDeclaration"===t.type&&"type"===t.exportKind||super.assertModuleNodeAllowed(t)}parseExport(t){const e=super.parseExport(t);return"ExportNamedDeclaration"!==e.type&&"ExportAllDeclaration"!==e.type||(e.exportKind=e.exportKind||"value"),e}parseExportDeclaration(t){if(this.isContextual("type")){t.exportKind="type";const e=this.startNode();return this.next(),this.match(d.braceL)?(t.specifiers=this.parseExportSpecifiers(),this.parseExportFrom(t),null):this.flowParseTypeAlias(e)}if(this.isContextual("opaque")){t.exportKind="type";const e=this.startNode();return this.next(),this.flowParseOpaqueType(e,!1)}if(this.isContextual("interface")){t.exportKind="type";const e=this.startNode();return this.next(),this.flowParseInterface(e)}if(this.shouldParseEnums()&&this.isContextual("enum")){t.exportKind="value";const e=this.startNode();return this.next(),this.flowParseEnumDeclaration(e)}return super.parseExportDeclaration(t)}eatExportStar(t){return!!super.eatExportStar(...arguments)||!(!this.isContextual("type")||this.lookahead().type!==d.star)&&(t.exportKind="type",this.next(),this.next(),!0)}maybeParseExportNamespaceSpecifier(t){const e=this.state.start,s=super.maybeParseExportNamespaceSpecifier(t);return s&&"type"===t.exportKind&&this.unexpected(e),s}parseClassId(t,e,s){super.parseClassId(t,e,s),this.isRelational("<")&&(t.typeParameters=this.flowParseTypeParameterDeclaration())}parseClassMember(t,e,s,i){const r=this.state.start;if(this.isContextual("declare")){if(this.parseClassMemberFromModifier(t,e))return;e.declare=!0}super.parseClassMember(t,e,s,i),e.declare&&("ClassProperty"!==e.type&&"ClassPrivateProperty"!==e.type?this.raise(r,Bt.DeclareClassElement):e.value&&this.raise(e.value.start,Bt.DeclareClassFieldInitializer))}getTokenFromCode(t){const e=this.input.charCodeAt(this.state.pos+1);return 123===t&&124===e?this.finishOp(d.braceBarL,2):!this.state.inType||62!==t&&60!==t?jt(t,e)?(this.state.isIterator=!0,super.readWord()):super.getTokenFromCode(t):this.finishOp(d.relational,1)}isAssignable(t,e){switch(t.type){case"Identifier":case"ObjectPattern":case"ArrayPattern":case"AssignmentPattern":return!0;case"ObjectExpression":{const e=t.properties.length-1;return t.properties.every((t,s)=>"ObjectMethod"!==t.type&&(s===e||"SpreadElement"===t.type)&&this.isAssignable(t))}case"ObjectProperty":return this.isAssignable(t.value);case"SpreadElement":return this.isAssignable(t.argument);case"ArrayExpression":return t.elements.every(t=>this.isAssignable(t));case"AssignmentExpression":return"="===t.operator;case"ParenthesizedExpression":case"TypeCastExpression":return this.isAssignable(t.expression);case"MemberExpression":case"OptionalMemberExpression":return!e;default:return!1}}toAssignable(t){return"TypeCastExpression"===t.type?super.toAssignable(this.typeCastToParameter(t)):super.toAssignable(t)}toAssignableList(t,e){for(let s=0;s1||!e)&&this.raise(r.typeAnnotation.start,Bt.TypeCastInPattern)}return t}checkLVal(t,e=V,s,i){if("TypeCastExpression"!==t.type)return super.checkLVal(t,e,s,i)}parseClassProperty(t){return this.match(d.colon)&&(t.typeAnnotation=this.flowParseTypeAnnotation()),super.parseClassProperty(t)}parseClassPrivateProperty(t){return this.match(d.colon)&&(t.typeAnnotation=this.flowParseTypeAnnotation()),super.parseClassPrivateProperty(t)}isClassMethod(){return this.isRelational("<")||super.isClassMethod()}isClassProperty(){return this.match(d.colon)||super.isClassProperty()}isNonstaticConstructor(t){return!this.match(d.colon)&&super.isNonstaticConstructor(t)}pushClassMethod(t,e,s,i,r,n){e.variance&&this.unexpected(e.variance.start),delete e.variance,this.isRelational("<")&&(e.typeParameters=this.flowParseTypeParameterDeclaration()),super.pushClassMethod(t,e,s,i,r,n)}pushClassPrivateMethod(t,e,s,i){e.variance&&this.unexpected(e.variance.start),delete e.variance,this.isRelational("<")&&(e.typeParameters=this.flowParseTypeParameterDeclaration()),super.pushClassPrivateMethod(t,e,s,i)}parseClassSuper(t){if(super.parseClassSuper(t),t.superClass&&this.isRelational("<")&&(t.superTypeParameters=this.flowParseTypeParameterInstantiation()),this.isContextual("implements")){this.next();const e=t.implements=[];do{const t=this.startNode();t.id=this.flowParseRestrictedIdentifier(!0),this.isRelational("<")?t.typeParameters=this.flowParseTypeParameterInstantiation():t.typeParameters=null,e.push(this.finishNode(t,"ClassImplements"))}while(this.eat(d.comma))}}parsePropertyName(t,e){const s=this.flowParseVariance(),i=super.parsePropertyName(t,e);return t.variance=s,i}parseObjPropValue(t,e,s,i,r,n,a,o){let c;t.variance&&this.unexpected(t.variance.start),delete t.variance,this.isRelational("<")&&(c=this.flowParseTypeParameterDeclaration(),this.match(d.parenL)||this.unexpected()),super.parseObjPropValue(t,e,s,i,r,n,a,o),c&&((t.value||t).typeParameters=c)}parseAssignableListItemTypes(t){return this.eat(d.question)&&("Identifier"!==t.type&&this.raise(t.start,Bt.OptionalBindingPattern),t.optional=!0),this.match(d.colon)&&(t.typeAnnotation=this.flowParseTypeAnnotation()),this.resetEndLocation(t),t}parseMaybeDefault(t,e,s){const i=super.parseMaybeDefault(t,e,s);return"AssignmentPattern"===i.type&&i.typeAnnotation&&i.right.startsuper.parseMaybeAssign(t,e,s,i),a),!n.error)return n.node;const{context:r}=this.state;r[r.length-1]===gt.j_oTag?r.length-=2:r[r.length-1]===gt.j_expr&&(r.length-=1)}if((null==(r=n)?void 0:r.error)||this.isRelational("<")){var o,c,h;let r;a=a||this.state.clone();const l=this.tryParse(()=>{r=this.flowParseTypeParameterDeclaration();const n=this.forwardNoArrowParamsConversionAt(r,()=>super.parseMaybeAssign(t,e,s,i));return n.typeParameters=r,this.resetStartLocationFromNode(n,r),n},a),p="ArrowFunctionExpression"===(null==(o=l.node)?void 0:o.type)?l.node:null;if(!l.error&&p)return p;if(null==(c=n)?void 0:c.node)return this.state=n.failState,n.node;if(p)return this.state=l.failState,p;if(null==(h=n)?void 0:h.thrown)throw n.error;if(l.thrown)throw l.error;throw this.raise(r.start,Bt.UnexpectedTokenAfterTypeParameter)}return super.parseMaybeAssign(t,e,s,i)}parseArrow(t){if(this.match(d.colon)){const e=this.tryParse(()=>{const e=this.state.noAnonFunctionType;this.state.noAnonFunctionType=!0;const s=this.startNode();return[s.typeAnnotation,t.predicate]=this.flowParseTypeAndPredicateInitialiser(),this.state.noAnonFunctionType=e,this.canInsertSemicolon()&&this.unexpected(),this.match(d.arrow)||this.unexpected(),s});if(e.thrown)return null;e.error&&(this.state=e.failState),t.returnType=e.node.typeAnnotation?this.finishNode(e.node,"TypeAnnotation"):null}return super.parseArrow(t)}shouldParseArrow(){return this.match(d.colon)||super.shouldParseArrow()}setArrowFunctionParameters(t,e){-1!==this.state.noArrowParamsConversionAt.indexOf(t.start)?t.params=e:super.setArrowFunctionParameters(t,e)}checkParams(t,e,s){if(!s||-1===this.state.noArrowParamsConversionAt.indexOf(t.start))return super.checkParams(...arguments)}parseParenAndDistinguishExpression(t){return super.parseParenAndDistinguishExpression(t&&-1===this.state.noArrowAt.indexOf(this.state.start))}parseSubscripts(t,e,s,i){if("Identifier"===t.type&&"async"===t.name&&-1!==this.state.noArrowAt.indexOf(e)){this.next();const i=this.startNodeAt(e,s);i.callee=t,i.arguments=this.parseCallExpressionArguments(d.parenR,!1),t=this.finishNode(i,"CallExpression")}else if("Identifier"===t.type&&"async"===t.name&&this.isRelational("<")){const r=this.state.clone(),n=this.tryParse(t=>this.parseAsyncArrowWithTypeParameters(e,s)||t(),r);if(!n.error&&!n.aborted)return n.node;const a=this.tryParse(()=>super.parseSubscripts(t,e,s,i),r);if(a.node&&!a.error)return a.node;if(n.node)return this.state=n.failState,n.node;if(a.node)return this.state=a.failState,a.node;throw n.error||a.error}return super.parseSubscripts(t,e,s,i)}parseSubscript(t,e,s,i,r){if(this.match(d.questionDot)&&this.isLookaheadRelational("<")){if(r.optionalChainMember=!0,i)return r.stop=!0,t;this.next();const n=this.startNodeAt(e,s);return n.callee=t,n.typeArguments=this.flowParseTypeParameterInstantiation(),this.expect(d.parenL),n.arguments=this.parseCallExpressionArguments(d.parenR,!1),n.optional=!0,this.finishCallExpression(n,!0)}if(!i&&this.shouldParseTypes()&&this.isRelational("<")){const i=this.startNodeAt(e,s);i.callee=t;const n=this.tryParse(()=>(i.typeArguments=this.flowParseTypeParameterInstantiationCallOrNew(),this.expect(d.parenL),i.arguments=this.parseCallExpressionArguments(d.parenR,!1),r.optionalChainMember&&(i.optional=!1),this.finishCallExpression(i,r.optionalChainMember)));if(n.node)return n.error&&(this.state=n.failState),n.node}return super.parseSubscript(t,e,s,i,r)}parseNewArguments(t){let e=null;this.shouldParseTypes()&&this.isRelational("<")&&(e=this.tryParse(()=>this.flowParseTypeParameterInstantiationCallOrNew()).node),t.typeArguments=e,super.parseNewArguments(t)}parseAsyncArrowWithTypeParameters(t,e){const s=this.startNodeAt(t,e);if(this.parseFunctionParams(s),this.parseArrow(s))return this.parseArrowExpression(s,void 0,!0)}readToken_mult_modulo(t){const e=this.input.charCodeAt(this.state.pos+1);if(42===t&&47===e&&this.state.hasFlowComment)return this.state.hasFlowComment=!1,this.state.pos+=2,void this.nextToken();super.readToken_mult_modulo(t)}readToken_pipe_amp(t){const e=this.input.charCodeAt(this.state.pos+1);124!==t||125!==e?super.readToken_pipe_amp(t):this.finishOp(d.braceBarR,2)}parseTopLevel(t,e){const s=super.parseTopLevel(t,e);return this.state.hasFlowComment&&this.raise(this.state.pos,Bt.UnterminatedFlowComment),s}skipBlockComment(){if(this.hasPlugin("flowComments")&&this.skipFlowComment())return this.state.hasFlowComment&&this.unexpected(null,Bt.NestedFlowComment),this.hasFlowCommentCompletion(),this.state.pos+=this.skipFlowComment(),void(this.state.hasFlowComment=!0);if(this.state.hasFlowComment){const t=this.input.indexOf("*-/",this.state.pos+=2);if(-1===t)throw this.raise(this.state.pos-2,ut.UnterminatedComment);this.state.pos=t+3}else super.skipBlockComment()}skipFlowComment(){const{pos:t}=this.state;let e=2;while([32,9].includes(this.input.charCodeAt(t+e)))e++;const s=this.input.charCodeAt(e+t),i=this.input.charCodeAt(e+t+1);return 58===s&&58===i?e+2:"flow-include"===this.input.slice(e+t,e+t+12)?e+12:58===s&&58!==i&&e}hasFlowCommentCompletion(){const t=this.input.indexOf("*/",this.state.pos);if(-1===t)throw this.raise(this.state.pos,ut.UnterminatedComment)}flowEnumErrorBooleanMemberNotInitialized(t,{enumName:e,memberName:s}){this.raise(t,Bt.EnumBooleanMemberNotInitialized,s,e)}flowEnumErrorInvalidMemberName(t,{enumName:e,memberName:s}){const i=s[0].toUpperCase()+s.slice(1);this.raise(t,Bt.EnumInvalidMemberName,s,i,e)}flowEnumErrorDuplicateMemberName(t,{enumName:e,memberName:s}){this.raise(t,Bt.EnumDuplicateMemberName,s,e)}flowEnumErrorInconsistentMemberValues(t,{enumName:e}){this.raise(t,Bt.EnumInconsistentMemberValues,e)}flowEnumErrorInvalidExplicitType(t,{enumName:e,suppliedType:s}){return this.raise(t,null===s?Bt.EnumInvalidExplicitTypeUnknownSupplied:Bt.EnumInvalidExplicitType,e,s)}flowEnumErrorInvalidMemberInitializer(t,{enumName:e,explicitType:s,memberName:i}){let r=null;switch(s){case"boolean":case"number":case"string":r=Bt.EnumInvalidMemberInitializerPrimaryType;break;case"symbol":r=Bt.EnumInvalidMemberInitializerSymbolType;break;default:r=Bt.EnumInvalidMemberInitializerUnknownType}return this.raise(t,r,e,i,s)}flowEnumErrorNumberMemberNotInitialized(t,{enumName:e,memberName:s}){this.raise(t,Bt.EnumNumberMemberNotInitialized,e,s)}flowEnumErrorStringMemberInconsistentlyInitailized(t,{enumName:e}){this.raise(t,Bt.EnumStringMemberInconsistentlyInitailized,e)}flowEnumMemberInit(){const t=this.state.start,e=()=>this.match(d.comma)||this.match(d.braceR);switch(this.state.type){case d.num:{const s=this.parseLiteral(this.state.value,"NumericLiteral");return e()?{type:"number",pos:s.start,value:s}:{type:"invalid",pos:t}}case d.string:{const s=this.parseLiteral(this.state.value,"StringLiteral");return e()?{type:"string",pos:s.start,value:s}:{type:"invalid",pos:t}}case d._true:case d._false:{const s=this.parseBooleanLiteral();return e()?{type:"boolean",pos:s.start,value:s}:{type:"invalid",pos:t}}default:return{type:"invalid",pos:t}}}flowEnumMemberRaw(){const t=this.state.start,e=this.parseIdentifier(!0),s=this.eat(d.eq)?this.flowEnumMemberInit():{type:"none",pos:t};return{id:e,init:s}}flowEnumCheckExplicitTypeMismatch(t,e,s){const{explicitType:i}=e;null!==i&&i!==s&&this.flowEnumErrorInvalidMemberInitializer(t,e)}flowEnumMembers({enumName:t,explicitType:e}){const s=new Set,i={booleanMembers:[],numberMembers:[],stringMembers:[],defaultedMembers:[]};while(!this.match(d.braceR)){const r=this.startNode(),{id:n,init:a}=this.flowEnumMemberRaw(),o=n.name;if(""===o)continue;/^[a-z]/.test(o)&&this.flowEnumErrorInvalidMemberName(n.start,{enumName:t,memberName:o}),s.has(o)&&this.flowEnumErrorDuplicateMemberName(n.start,{enumName:t,memberName:o}),s.add(o);const c={enumName:t,explicitType:e,memberName:o};switch(r.id=n,a.type){case"boolean":this.flowEnumCheckExplicitTypeMismatch(a.pos,c,"boolean"),r.init=a.value,i.booleanMembers.push(this.finishNode(r,"EnumBooleanMember"));break;case"number":this.flowEnumCheckExplicitTypeMismatch(a.pos,c,"number"),r.init=a.value,i.numberMembers.push(this.finishNode(r,"EnumNumberMember"));break;case"string":this.flowEnumCheckExplicitTypeMismatch(a.pos,c,"string"),r.init=a.value,i.stringMembers.push(this.finishNode(r,"EnumStringMember"));break;case"invalid":throw this.flowEnumErrorInvalidMemberInitializer(a.pos,c);case"none":switch(e){case"boolean":this.flowEnumErrorBooleanMemberNotInitialized(a.pos,c);break;case"number":this.flowEnumErrorNumberMemberNotInitialized(a.pos,c);break;default:i.defaultedMembers.push(this.finishNode(r,"EnumDefaultedMember"))}}this.match(d.braceR)||this.expect(d.comma)}return i}flowEnumStringMembers(t,e,{enumName:s}){if(0===t.length)return e;if(0===e.length)return t;if(e.length>t.length){for(let e=0;e(t.members=[],this.expect(d.braceR),this.finishNode(t,"EnumStringBody"));t.explicitType=!1;const n=r.booleanMembers.length,a=r.numberMembers.length,o=r.stringMembers.length,c=r.defaultedMembers.length;if(n||a||o||c){if(n||a){if(!a&&!o&&n>=c){for(let t=0,s=r.defaultedMembers;t=c){for(let t=0,s=r.defaultedMembers;t",nbsp:" ",iexcl:"¡",cent:"¢",pound:"£",curren:"¤",yen:"¥",brvbar:"¦",sect:"§",uml:"¨",copy:"©",ordf:"ª",laquo:"«",not:"¬",shy:"­",reg:"®",macr:"¯",deg:"°",plusmn:"±",sup2:"²",sup3:"³",acute:"´",micro:"µ",para:"¶",middot:"·",cedil:"¸",sup1:"¹",ordm:"º",raquo:"»",frac14:"¼",frac12:"½",frac34:"¾",iquest:"¿",Agrave:"À",Aacute:"Á",Acirc:"Â",Atilde:"Ã",Auml:"Ä",Aring:"Å",AElig:"Æ",Ccedil:"Ç",Egrave:"È",Eacute:"É",Ecirc:"Ê",Euml:"Ë",Igrave:"Ì",Iacute:"Í",Icirc:"Î",Iuml:"Ï",ETH:"Ð",Ntilde:"Ñ",Ograve:"Ò",Oacute:"Ó",Ocirc:"Ô",Otilde:"Õ",Ouml:"Ö",times:"×",Oslash:"Ø",Ugrave:"Ù",Uacute:"Ú",Ucirc:"Û",Uuml:"Ü",Yacute:"Ý",THORN:"Þ",szlig:"ß",agrave:"à",aacute:"á",acirc:"â",atilde:"ã",auml:"ä",aring:"å",aelig:"æ",ccedil:"ç",egrave:"è",eacute:"é",ecirc:"ê",euml:"ë",igrave:"ì",iacute:"í",icirc:"î",iuml:"ï",eth:"ð",ntilde:"ñ",ograve:"ò",oacute:"ó",ocirc:"ô",otilde:"õ",ouml:"ö",divide:"÷",oslash:"ø",ugrave:"ù",uacute:"ú",ucirc:"û",uuml:"ü",yacute:"ý",thorn:"þ",yuml:"ÿ",OElig:"Œ",oelig:"œ",Scaron:"Š",scaron:"š",Yuml:"Ÿ",fnof:"ƒ",circ:"ˆ",tilde:"˜",Alpha:"Α",Beta:"Β",Gamma:"Γ",Delta:"Δ",Epsilon:"Ε",Zeta:"Ζ",Eta:"Η",Theta:"Θ",Iota:"Ι",Kappa:"Κ",Lambda:"Λ",Mu:"Μ",Nu:"Ν",Xi:"Ξ",Omicron:"Ο",Pi:"Π",Rho:"Ρ",Sigma:"Σ",Tau:"Τ",Upsilon:"Υ",Phi:"Φ",Chi:"Χ",Psi:"Ψ",Omega:"Ω",alpha:"α",beta:"β",gamma:"γ",delta:"δ",epsilon:"ε",zeta:"ζ",eta:"η",theta:"θ",iota:"ι",kappa:"κ",lambda:"λ",mu:"μ",nu:"ν",xi:"ξ",omicron:"ο",pi:"π",rho:"ρ",sigmaf:"ς",sigma:"σ",tau:"τ",upsilon:"υ",phi:"φ",chi:"χ",psi:"ψ",omega:"ω",thetasym:"ϑ",upsih:"ϒ",piv:"ϖ",ensp:" ",emsp:" ",thinsp:" ",zwnj:"‌",zwj:"‍",lrm:"‎",rlm:"‏",ndash:"–",mdash:"—",lsquo:"‘",rsquo:"’",sbquo:"‚",ldquo:"“",rdquo:"”",bdquo:"„",dagger:"†",Dagger:"‡",bull:"•",hellip:"…",permil:"‰",prime:"′",Prime:"″",lsaquo:"‹",rsaquo:"›",oline:"‾",frasl:"⁄",euro:"€",image:"ℑ",weierp:"℘",real:"ℜ",trade:"™",alefsym:"ℵ",larr:"←",uarr:"↑",rarr:"→",darr:"↓",harr:"↔",crarr:"↵",lArr:"⇐",uArr:"⇑",rArr:"⇒",dArr:"⇓",hArr:"⇔",forall:"∀",part:"∂",exist:"∃",empty:"∅",nabla:"∇",isin:"∈",notin:"∉",ni:"∋",prod:"∏",sum:"∑",minus:"−",lowast:"∗",radic:"√",prop:"∝",infin:"∞",ang:"∠",and:"∧",or:"∨",cap:"∩",cup:"∪",int:"∫",there4:"∴",sim:"∼",cong:"≅",asymp:"≈",ne:"≠",equiv:"≡",le:"≤",ge:"≥",sub:"⊂",sup:"⊃",nsub:"⊄",sube:"⊆",supe:"⊇",oplus:"⊕",otimes:"⊗",perp:"⊥",sdot:"⋅",lceil:"⌈",rceil:"⌉",lfloor:"⌊",rfloor:"⌋",lang:"〈",rang:"〉",loz:"◊",spades:"♠",clubs:"♣",hearts:"♥",diams:"♦"},Xt=/^[\da-fA-F]+$/,Gt=/^\d+$/,Yt=Object.freeze({AttributeIsEmpty:"JSX attributes must only be assigned a non-empty expression",MissingClosingTagFragment:"Expected corresponding JSX closing tag for <>",MissingClosingTagElement:"Expected corresponding JSX closing tag for <%0>",UnsupportedJsxValue:"JSX value should be either an expression or a quoted JSX text",UnterminatedJsxContent:"Unterminated JSX contents",UnwrappedAdjacentJSXElements:"Adjacent JSX elements must be wrapped in an enclosing tag. Did you want a JSX fragment <>...?"});function Jt(t){return!!t&&("JSXOpeningFragment"===t.type||"JSXClosingFragment"===t.type)}function Qt(t){if("JSXIdentifier"===t.type)return t.name;if("JSXNamespacedName"===t.type)return t.namespace.name+":"+t.name.name;if("JSXMemberExpression"===t.type)return Qt(t.object)+"."+Qt(t.property);throw new Error("Node had unexpected type: "+t.type)}gt.j_oTag=new yt("...",!0,!0),d.jsxName=new h("jsxName"),d.jsxText=new h("jsxText",{beforeExpr:!0}),d.jsxTagStart=new h("jsxTagStart",{startsExpr:!0}),d.jsxTagEnd=new h("jsxTagEnd"),d.jsxTagStart.updateContext=function(){this.state.context.push(gt.j_expr),this.state.context.push(gt.j_oTag),this.state.exprAllowed=!1},d.jsxTagEnd.updateContext=function(t){const e=this.state.context.pop();e===gt.j_oTag&&t===d.slash||e===gt.j_cTag?(this.state.context.pop(),this.state.exprAllowed=this.curContext()===gt.j_expr):this.state.exprAllowed=!0};var Zt=t=>class extends t{jsxReadToken(){let t="",e=this.state.pos;for(;;){if(this.state.pos>=this.length)throw this.raise(this.state.start,Yt.UnterminatedJsxContent);const s=this.input.charCodeAt(this.state.pos);switch(s){case 60:case 123:return this.state.pos===this.state.start?60===s&&this.state.exprAllowed?(++this.state.pos,this.finishToken(d.jsxTagStart)):super.getTokenFromCode(s):(t+=this.input.slice(e,this.state.pos),this.finishToken(d.jsxText,t));case 38:t+=this.input.slice(e,this.state.pos),t+=this.jsxReadEntity(),e=this.state.pos;break;default:it(s)?(t+=this.input.slice(e,this.state.pos),t+=this.jsxReadNewLine(!0),e=this.state.pos):++this.state.pos}}}jsxReadNewLine(t){const e=this.input.charCodeAt(this.state.pos);let s;return++this.state.pos,13===e&&10===this.input.charCodeAt(this.state.pos)?(++this.state.pos,s=t?"\n":"\r\n"):s=String.fromCharCode(e),++this.state.curLine,this.state.lineStart=this.state.pos,s}jsxReadString(t){let e="",s=++this.state.pos;for(;;){if(this.state.pos>=this.length)throw this.raise(this.state.start,ut.UnterminatedString);const i=this.input.charCodeAt(this.state.pos);if(i===t)break;38===i?(e+=this.input.slice(s,this.state.pos),e+=this.jsxReadEntity(),s=this.state.pos):it(i)?(e+=this.input.slice(s,this.state.pos),e+=this.jsxReadNewLine(!1),s=this.state.pos):++this.state.pos}return e+=this.input.slice(s,this.state.pos++),this.finishToken(d.string,e)}jsxReadEntity(){let t,e="",s=0,i=this.input[this.state.pos];const r=++this.state.pos;while(this.state.pos0}get allowSuper(){return(this.currentThisScope().flags&b)>0}get allowDirectSuper(){return(this.currentThisScope().flags&v)>0}get inClass(){return(this.currentThisScope().flags&w)>0}get inNonArrowFunction(){return(this.currentThisScope().flags&y)>0}get treatFunctionsAsVar(){return this.treatFunctionsAsVarInScope(this.currentScope())}createScope(t){return new te(t)}enter(t){this.scopeStack.push(this.createScope(t))}exit(){this.scopeStack.pop()}treatFunctionsAsVarInScope(t){return!!(t.flags&y||!this.inModule&&t.flags&m)}declareName(t,e,s){let i=this.currentScope();if(e&C||e&k)this.checkRedeclarationInScope(i,t,e,s),e&k?i.functions.push(t):i.lexical.push(t),e&C&&this.maybeExportDefined(i,t);else if(e&S)for(let r=this.scopeStack.length-1;r>=0;--r)if(i=this.scopeStack[r],this.checkRedeclarationInScope(i,t,e,s),i.var.push(t),this.maybeExportDefined(i,t),i.flags&T)break;this.inModule&&i.flags&m&&this.undefinedExports.delete(t)}maybeExportDefined(t,e){this.inModule&&t.flags&m&&this.undefinedExports.delete(e)}checkRedeclarationInScope(t,e,s,i){this.isRedeclaredInScope(t,e,s)&&this.raise(i,ut.VarRedeclaration,e)}isRedeclaredInScope(t,e,s){return!!(s&E)&&(s&C?t.lexical.indexOf(e)>-1||t.functions.indexOf(e)>-1||t.var.indexOf(e)>-1:s&k?t.lexical.indexOf(e)>-1||!this.treatFunctionsAsVarInScope(t)&&t.var.indexOf(e)>-1:t.lexical.indexOf(e)>-1&&!(t.flags&x&&t.lexical[0]===e)||!this.treatFunctionsAsVarInScope(t)&&t.functions.indexOf(e)>-1)}checkLocalExport(t){-1===this.scopeStack[0].lexical.indexOf(t.name)&&-1===this.scopeStack[0].var.indexOf(t.name)&&-1===this.scopeStack[0].functions.indexOf(t.name)&&this.undefinedExports.set(t.name,t.start)}currentScope(){return this.scopeStack[this.scopeStack.length-1]}currentVarScope(){for(let t=this.scopeStack.length-1;;t--){const e=this.scopeStack[t];if(e.flags&T)return e}}currentThisScope(){for(let t=this.scopeStack.length-1;;t--){const e=this.scopeStack[t];if((e.flags&T||e.flags&w)&&!(e.flags&g))return e}}}class se extends te{constructor(...t){super(...t),this.types=[],this.enums=[],this.constEnums=[],this.classes=[],this.exportOnlyBindings=[]}}class ie extends ee{createScope(t){return new se(t)}declareName(t,e,s){const i=this.currentScope();if(e&M)return this.maybeExportDefined(i,t),void i.exportOnlyBindings.push(t);super.declareName(...arguments),e&A&&(e&E||(this.checkRedeclarationInScope(i,t,e,s),this.maybeExportDefined(i,t)),i.types.push(t)),e&O&&i.enums.push(t),e&D&&i.constEnums.push(t),e&I&&i.classes.push(t)}isRedeclaredInScope(t,e,s){if(t.enums.indexOf(e)>-1){if(s&O){const i=!!(s&D),r=t.constEnums.indexOf(e)>-1;return i!==r}return!0}return s&I&&t.classes.indexOf(e)>-1?t.lexical.indexOf(e)>-1&&!!(s&E):!!(s&A&&t.types.indexOf(e)>-1)||super.isRedeclaredInScope(...arguments)}checkLocalExport(t){-1===this.scopeStack[0].types.indexOf(t.name)&&-1===this.scopeStack[0].exportOnlyBindings.indexOf(t.name)&&super.checkLocalExport(t)}}const re=0,ne=1,ae=2,oe=4;class ce{constructor(){this.stacks=[]}enter(t){this.stacks.push(t)}exit(){this.stacks.pop()}currentFlags(){return this.stacks[this.stacks.length-1]}get hasAwait(){return(this.currentFlags()&ae)>0}get hasYield(){return(this.currentFlags()&ne)>0}get hasReturn(){return(this.currentFlags()&oe)>0}}function he(t,e){return(t?ae:0)|(e?ne:0)}function le(t){if(null==t)throw new Error(`Unexpected ${t} value.`);return t}function pe(t){if(!t)throw new Error("Assert fail")}const ue=Object.freeze({ClassMethodHasDeclare:"Class methods cannot have the 'declare' modifier",ClassMethodHasReadonly:"Class methods cannot have the 'readonly' modifier",DeclareClassFieldHasInitializer:"'declare' class fields cannot have an initializer",DuplicateModifier:"Duplicate modifier: '%0'",EmptyHeritageClauseType:"'%0' list cannot be empty.",IndexSignatureHasAbstract:"Index signatures cannot have the 'abstract' modifier",IndexSignatureHasAccessibility:"Index signatures cannot have an accessibility modifier ('%0')",IndexSignatureHasStatic:"Index signatures cannot have the 'static' modifier",OptionalTypeBeforeRequired:"A required element cannot follow an optional element.",PatternIsOptional:"A binding pattern parameter cannot be optional in an implementation signature.",PrivateElementHasAbstract:"Private elements cannot have the 'abstract' modifier.",PrivateElementHasAccessibility:"Private elements cannot have an accessibility modifier ('%0')",TemplateTypeHasSubstitution:"Template literal types cannot have any substitution",TypeAnnotationAfterAssign:"Type annotations must come before default assignments, e.g. instead of `age = 25: number` use `age: number = 25`",UnexpectedReadonly:"'readonly' type modifier is only permitted on array and tuple literal types.",UnexpectedTypeAnnotation:"Did not expect a type annotation here.",UnexpectedTypeCastInParameter:"Unexpected type cast in parameter position.",UnsupportedImportTypeArgument:"Argument in a type import must be a string literal",UnsupportedParameterPropertyKind:"A parameter property may not be declared using a binding pattern.",UnsupportedSignatureParameterKind:"Name in a signature must be an Identifier, ObjectPattern or ArrayPattern, instead got %0"});function de(t){switch(t){case"any":return"TSAnyKeyword";case"boolean":return"TSBooleanKeyword";case"bigint":return"TSBigIntKeyword";case"never":return"TSNeverKeyword";case"number":return"TSNumberKeyword";case"object":return"TSObjectKeyword";case"string":return"TSStringKeyword";case"symbol":return"TSSymbolKeyword";case"undefined":return"TSUndefinedKeyword";case"unknown":return"TSUnknownKeyword";default:return}}var fe=t=>class extends t{getScopeHandler(){return ie}tsIsIdentifier(){return this.match(d.name)}tsNextTokenCanFollowModifier(){return this.next(),!this.hasPrecedingLineBreak()&&!this.match(d.parenL)&&!this.match(d.parenR)&&!this.match(d.colon)&&!this.match(d.eq)&&!this.match(d.question)&&!this.match(d.bang)}tsParseModifier(t){if(!this.match(d.name))return;const e=this.state.value;return-1!==t.indexOf(e)&&this.tsTryParse(this.tsNextTokenCanFollowModifier.bind(this))?e:void 0}tsParseModifiers(t,e){for(;;){const s=this.state.start,i=this.tsParseModifier(e);if(!i)break;Object.hasOwnProperty.call(t,i)&&this.raise(s,ue.DuplicateModifier,i),t[i]=!0}}tsIsListTerminator(t){switch(t){case"EnumMembers":case"TypeMembers":return this.match(d.braceR);case"HeritageClauseElement":return this.match(d.braceL);case"TupleElementTypes":return this.match(d.bracketR);case"TypeParametersOrArguments":return this.isRelational(">")}throw new Error("Unreachable")}tsParseList(t,e){const s=[];while(!this.tsIsListTerminator(t))s.push(e());return s}tsParseDelimitedList(t,e){return le(this.tsParseDelimitedListWorker(t,e,!0))}tsParseDelimitedListWorker(t,e,s){const i=[];for(;;){if(this.tsIsListTerminator(t))break;const r=e();if(null==r)return;if(i.push(r),!this.eat(d.comma)){if(this.tsIsListTerminator(t))break;return void(s&&this.expect(d.comma))}}return i}tsParseBracketedList(t,e,s,i){i||(s?this.expect(d.bracketL):this.expectRelational("<"));const r=this.tsParseDelimitedList(t,e);return s?this.expect(d.bracketR):this.expectRelational(">"),r}tsParseImportType(){const t=this.startNode();return this.expect(d._import),this.expect(d.parenL),this.match(d.string)||this.raise(this.state.start,ue.UnsupportedImportTypeArgument),t.argument=this.parseExprAtom(),this.expect(d.parenR),this.eat(d.dot)&&(t.qualifier=this.tsParseEntityName(!0)),this.isRelational("<")&&(t.typeParameters=this.tsParseTypeArguments()),this.finishNode(t,"TSImportType")}tsParseEntityName(t){let e=this.parseIdentifier();while(this.eat(d.dot)){const s=this.startNodeAtNode(e);s.left=e,s.right=this.parseIdentifier(t),e=this.finishNode(s,"TSQualifiedName")}return e}tsParseTypeReference(){const t=this.startNode();return t.typeName=this.tsParseEntityName(!1),!this.hasPrecedingLineBreak()&&this.isRelational("<")&&(t.typeParameters=this.tsParseTypeArguments()),this.finishNode(t,"TSTypeReference")}tsParseThisTypePredicate(t){this.next();const e=this.startNodeAtNode(t);return e.parameterName=t,e.typeAnnotation=this.tsParseTypeAnnotation(!1),this.finishNode(e,"TSTypePredicate")}tsParseThisTypeNode(){const t=this.startNode();return this.next(),this.finishNode(t,"TSThisType")}tsParseTypeQuery(){const t=this.startNode();return this.expect(d._typeof),this.match(d._import)?t.exprName=this.tsParseImportType():t.exprName=this.tsParseEntityName(!0),this.finishNode(t,"TSTypeQuery")}tsParseTypeParameter(){const t=this.startNode();return t.name=this.parseIdentifierName(t.start),t.constraint=this.tsEatThenParseType(d._extends),t.default=this.tsEatThenParseType(d.eq),this.finishNode(t,"TSTypeParameter")}tsTryParseTypeParameters(){if(this.isRelational("<"))return this.tsParseTypeParameters()}tsParseTypeParameters(){const t=this.startNode();return this.isRelational("<")||this.match(d.jsxTagStart)?this.next():this.unexpected(),t.params=this.tsParseBracketedList("TypeParametersOrArguments",this.tsParseTypeParameter.bind(this),!1,!0),this.finishNode(t,"TSTypeParameterDeclaration")}tsTryNextParseConstantContext(){return this.lookahead().type===d._const?(this.next(),this.tsParseTypeReference()):null}tsFillSignature(t,e){const s=t===d.arrow;e.typeParameters=this.tsTryParseTypeParameters(),this.expect(d.parenL),e.parameters=this.tsParseBindingListForSignature(),(s||this.match(t))&&(e.typeAnnotation=this.tsParseTypeOrTypePredicateAnnotation(t))}tsParseBindingListForSignature(){return this.parseBindingList(d.parenR,41).map(t=>("Identifier"!==t.type&&"RestElement"!==t.type&&"ObjectPattern"!==t.type&&"ArrayPattern"!==t.type&&this.raise(t.start,ue.UnsupportedSignatureParameterKind,t.type),t))}tsParseTypeMemberSemicolon(){this.eat(d.comma)||this.semicolon()}tsParseSignatureMember(t,e){return this.tsFillSignature(d.colon,e),this.tsParseTypeMemberSemicolon(),this.finishNode(e,t)}tsIsUnambiguouslyIndexSignature(){return this.next(),this.eat(d.name)&&this.match(d.colon)}tsTryParseIndexSignature(t){if(!this.match(d.bracketL)||!this.tsLookAhead(this.tsIsUnambiguouslyIndexSignature.bind(this)))return;this.expect(d.bracketL);const e=this.parseIdentifier();e.typeAnnotation=this.tsParseTypeAnnotation(),this.resetEndLocation(e),this.expect(d.bracketR),t.parameters=[e];const s=this.tsTryParseTypeAnnotation();return s&&(t.typeAnnotation=s),this.tsParseTypeMemberSemicolon(),this.finishNode(t,"TSIndexSignature")}tsParsePropertyOrMethodSignature(t,e){this.eat(d.question)&&(t.optional=!0);const s=t;if(e||!this.match(d.parenL)&&!this.isRelational("<")){const t=s;e&&(t.readonly=!0);const i=this.tsTryParseTypeAnnotation();return i&&(t.typeAnnotation=i),this.tsParseTypeMemberSemicolon(),this.finishNode(t,"TSPropertySignature")}{const t=s;return this.tsFillSignature(d.colon,t),this.tsParseTypeMemberSemicolon(),this.finishNode(t,"TSMethodSignature")}}tsParseTypeMember(){const t=this.startNode();if(this.match(d.parenL)||this.isRelational("<"))return this.tsParseSignatureMember("TSCallSignatureDeclaration",t);if(this.match(d._new)){const e=this.startNode();return this.next(),this.match(d.parenL)||this.isRelational("<")?this.tsParseSignatureMember("TSConstructSignatureDeclaration",t):(t.key=this.createIdentifier(e,"new"),this.tsParsePropertyOrMethodSignature(t,!1))}const e=!!this.tsParseModifier(["readonly"]),s=this.tsTryParseIndexSignature(t);return s?(e&&(t.readonly=!0),s):(this.parsePropertyName(t,!1),this.tsParsePropertyOrMethodSignature(t,e))}tsParseTypeLiteral(){const t=this.startNode();return t.members=this.tsParseObjectTypeMembers(),this.finishNode(t,"TSTypeLiteral")}tsParseObjectTypeMembers(){this.expect(d.braceL);const t=this.tsParseList("TypeMembers",this.tsParseTypeMember.bind(this));return this.expect(d.braceR),t}tsIsStartOfMappedType(){return this.next(),this.eat(d.plusMin)?this.isContextual("readonly"):(this.isContextual("readonly")&&this.next(),!!this.match(d.bracketL)&&(this.next(),!!this.tsIsIdentifier()&&(this.next(),this.match(d._in))))}tsParseMappedTypeParameter(){const t=this.startNode();return t.name=this.parseIdentifierName(t.start),t.constraint=this.tsExpectThenParseType(d._in),this.finishNode(t,"TSTypeParameter")}tsParseMappedType(){const t=this.startNode();return this.expect(d.braceL),this.match(d.plusMin)?(t.readonly=this.state.value,this.next(),this.expectContextual("readonly")):this.eatContextual("readonly")&&(t.readonly=!0),this.expect(d.bracketL),t.typeParameter=this.tsParseMappedTypeParameter(),this.expect(d.bracketR),this.match(d.plusMin)?(t.optional=this.state.value,this.next(),this.expect(d.question)):this.eat(d.question)&&(t.optional=!0),t.typeAnnotation=this.tsTryParseType(),this.semicolon(),this.expect(d.braceR),this.finishNode(t,"TSMappedType")}tsParseTupleType(){const t=this.startNode();t.elementTypes=this.tsParseBracketedList("TupleElementTypes",this.tsParseTupleElementType.bind(this),!0,!1);let e=!1;return t.elementTypes.forEach(t=>{"TSOptionalType"===t.type?e=!0:e&&"TSRestType"!==t.type&&this.raise(t.start,ue.OptionalTypeBeforeRequired)}),this.finishNode(t,"TSTupleType")}tsParseTupleElementType(){if(this.match(d.ellipsis)){const t=this.startNode();return this.next(),t.typeAnnotation=this.tsParseType(),this.match(d.comma)&&93!==this.lookaheadCharCode()&&this.raiseRestNotLast(this.state.start),this.finishNode(t,"TSRestType")}const t=this.tsParseType();if(this.eat(d.question)){const e=this.startNodeAtNode(t);return e.typeAnnotation=t,this.finishNode(e,"TSOptionalType")}return t}tsParseParenthesizedType(){const t=this.startNode();return this.expect(d.parenL),t.typeAnnotation=this.tsParseType(),this.expect(d.parenR),this.finishNode(t,"TSParenthesizedType")}tsParseFunctionOrConstructorType(t){const e=this.startNode();return"TSConstructorType"===t&&this.expect(d._new),this.tsFillSignature(d.arrow,e),this.finishNode(e,t)}tsParseLiteralTypeNode(){const t=this.startNode();return t.literal=(()=>{switch(this.state.type){case d.num:case d.bigint:case d.string:case d._true:case d._false:return this.parseExprAtom();default:throw this.unexpected()}})(),this.finishNode(t,"TSLiteralType")}tsParseTemplateLiteralType(){const t=this.startNode(),e=this.parseTemplate(!1);return e.expressions.length>0&&this.raise(e.expressions[0].start,ue.TemplateTypeHasSubstitution),t.literal=e,this.finishNode(t,"TSLiteralType")}tsParseThisTypeOrThisTypePredicate(){const t=this.tsParseThisTypeNode();return this.isContextual("is")&&!this.hasPrecedingLineBreak()?this.tsParseThisTypePredicate(t):t}tsParseNonArrayType(){switch(this.state.type){case d.name:case d._void:case d._null:{const t=this.match(d._void)?"TSVoidKeyword":this.match(d._null)?"TSNullKeyword":de(this.state.value);if(void 0!==t&&46!==this.lookaheadCharCode()){const e=this.startNode();return this.next(),this.finishNode(e,t)}return this.tsParseTypeReference()}case d.string:case d.num:case d.bigint:case d._true:case d._false:return this.tsParseLiteralTypeNode();case d.plusMin:if("-"===this.state.value){const t=this.startNode(),e=this.lookahead();if(e.type!==d.num&&e.type!==d.bigint)throw this.unexpected();return t.literal=this.parseMaybeUnary(),this.finishNode(t,"TSLiteralType")}break;case d._this:return this.tsParseThisTypeOrThisTypePredicate();case d._typeof:return this.tsParseTypeQuery();case d._import:return this.tsParseImportType();case d.braceL:return this.tsLookAhead(this.tsIsStartOfMappedType.bind(this))?this.tsParseMappedType():this.tsParseTypeLiteral();case d.bracketL:return this.tsParseTupleType();case d.parenL:return this.tsParseParenthesizedType();case d.backQuote:return this.tsParseTemplateLiteralType()}throw this.unexpected()}tsParseArrayTypeOrHigher(){let t=this.tsParseNonArrayType();while(!this.hasPrecedingLineBreak()&&this.eat(d.bracketL))if(this.match(d.bracketR)){const e=this.startNodeAtNode(t);e.elementType=t,this.expect(d.bracketR),t=this.finishNode(e,"TSArrayType")}else{const e=this.startNodeAtNode(t);e.objectType=t,e.indexType=this.tsParseType(),this.expect(d.bracketR),t=this.finishNode(e,"TSIndexedAccessType")}return t}tsParseTypeOperator(t){const e=this.startNode();return this.expectContextual(t),e.operator=t,e.typeAnnotation=this.tsParseTypeOperatorOrHigher(),"readonly"===t&&this.tsCheckTypeAnnotationForReadOnly(e),this.finishNode(e,"TSTypeOperator")}tsCheckTypeAnnotationForReadOnly(t){switch(t.typeAnnotation.type){case"TSTupleType":case"TSArrayType":return;default:this.raise(t.start,ue.UnexpectedReadonly)}}tsParseInferType(){const t=this.startNode();this.expectContextual("infer");const e=this.startNode();return e.name=this.parseIdentifierName(e.start),t.typeParameter=this.finishNode(e,"TSTypeParameter"),this.finishNode(t,"TSInferType")}tsParseTypeOperatorOrHigher(){const t=["keyof","unique","readonly"].find(t=>this.isContextual(t));return t?this.tsParseTypeOperator(t):this.isContextual("infer")?this.tsParseInferType():this.tsParseArrayTypeOrHigher()}tsParseUnionOrIntersectionType(t,e,s){this.eat(s);let i=e();if(this.match(s)){const r=[i];while(this.eat(s))r.push(e());const n=this.startNodeAtNode(i);n.types=r,i=this.finishNode(n,t)}return i}tsParseIntersectionTypeOrHigher(){return this.tsParseUnionOrIntersectionType("TSIntersectionType",this.tsParseTypeOperatorOrHigher.bind(this),d.bitwiseAND)}tsParseUnionTypeOrHigher(){return this.tsParseUnionOrIntersectionType("TSUnionType",this.tsParseIntersectionTypeOrHigher.bind(this),d.bitwiseOR)}tsIsStartOfFunctionType(){return!!this.isRelational("<")||this.match(d.parenL)&&this.tsLookAhead(this.tsIsUnambiguouslyStartOfFunctionType.bind(this))}tsSkipParameterStart(){if(this.match(d.name)||this.match(d._this))return this.next(),!0;if(this.match(d.braceL)){let t=1;this.next();while(t>0)this.match(d.braceL)?++t:this.match(d.braceR)&&--t,this.next();return!0}if(this.match(d.bracketL)){let t=1;this.next();while(t>0)this.match(d.bracketL)?++t:this.match(d.bracketR)&&--t,this.next();return!0}return!1}tsIsUnambiguouslyStartOfFunctionType(){if(this.next(),this.match(d.parenR)||this.match(d.ellipsis))return!0;if(this.tsSkipParameterStart()){if(this.match(d.colon)||this.match(d.comma)||this.match(d.question)||this.match(d.eq))return!0;if(this.match(d.parenR)&&(this.next(),this.match(d.arrow)))return!0}return!1}tsParseTypeOrTypePredicateAnnotation(t){return this.tsInType(()=>{const e=this.startNode();this.expect(t);const s=this.tsTryParse(this.tsParseTypePredicateAsserts.bind(this));if(s&&this.match(d._this)){let t=this.tsParseThisTypeOrThisTypePredicate();if("TSThisType"===t.type){const s=this.startNodeAtNode(e);s.parameterName=t,s.asserts=!0,t=this.finishNode(s,"TSTypePredicate")}else t.asserts=!0;return e.typeAnnotation=t,this.finishNode(e,"TSTypeAnnotation")}const i=this.tsIsIdentifier()&&this.tsTryParse(this.tsParseTypePredicatePrefix.bind(this));if(!i){if(!s)return this.tsParseTypeAnnotation(!1,e);const t=this.startNodeAtNode(e);return t.parameterName=this.parseIdentifier(),t.asserts=s,e.typeAnnotation=this.finishNode(t,"TSTypePredicate"),this.finishNode(e,"TSTypeAnnotation")}const r=this.tsParseTypeAnnotation(!1),n=this.startNodeAtNode(e);return n.parameterName=i,n.typeAnnotation=r,n.asserts=s,e.typeAnnotation=this.finishNode(n,"TSTypePredicate"),this.finishNode(e,"TSTypeAnnotation")})}tsTryParseTypeOrTypePredicateAnnotation(){return this.match(d.colon)?this.tsParseTypeOrTypePredicateAnnotation(d.colon):void 0}tsTryParseTypeAnnotation(){return this.match(d.colon)?this.tsParseTypeAnnotation():void 0}tsTryParseType(){return this.tsEatThenParseType(d.colon)}tsParseTypePredicatePrefix(){const t=this.parseIdentifier();if(this.isContextual("is")&&!this.hasPrecedingLineBreak())return this.next(),t}tsParseTypePredicateAsserts(){if(!this.match(d.name)||"asserts"!==this.state.value||this.hasPrecedingLineBreak())return!1;const t=this.state.containsEsc;return this.next(),!(!this.match(d.name)&&!this.match(d._this))&&(t&&this.raise(this.state.lastTokStart,ut.InvalidEscapedReservedWord,"asserts"),!0)}tsParseTypeAnnotation(t=!0,e=this.startNode()){return this.tsInType(()=>{t&&this.expect(d.colon),e.typeAnnotation=this.tsParseType()}),this.finishNode(e,"TSTypeAnnotation")}tsParseType(){pe(this.state.inType);const t=this.tsParseNonConditionalType();if(this.hasPrecedingLineBreak()||!this.eat(d._extends))return t;const e=this.startNodeAtNode(t);return e.checkType=t,e.extendsType=this.tsParseNonConditionalType(),this.expect(d.question),e.trueType=this.tsParseType(),this.expect(d.colon),e.falseType=this.tsParseType(),this.finishNode(e,"TSConditionalType")}tsParseNonConditionalType(){return this.tsIsStartOfFunctionType()?this.tsParseFunctionOrConstructorType("TSFunctionType"):this.match(d._new)?this.tsParseFunctionOrConstructorType("TSConstructorType"):this.tsParseUnionTypeOrHigher()}tsParseTypeAssertion(){const t=this.startNode(),e=this.tsTryNextParseConstantContext();return t.typeAnnotation=e||this.tsNextThenParseType(),this.expectRelational(">"),t.expression=this.parseMaybeUnary(),this.finishNode(t,"TSTypeAssertion")}tsParseHeritageClause(t){const e=this.state.start,s=this.tsParseDelimitedList("HeritageClauseElement",this.tsParseExpressionWithTypeArguments.bind(this));return s.length||this.raise(e,ue.EmptyHeritageClauseType,t),s}tsParseExpressionWithTypeArguments(){const t=this.startNode();return t.expression=this.tsParseEntityName(!1),this.isRelational("<")&&(t.typeParameters=this.tsParseTypeArguments()),this.finishNode(t,"TSExpressionWithTypeArguments")}tsParseInterfaceDeclaration(t){t.id=this.parseIdentifier(),this.checkLVal(t.id,F,void 0,"typescript interface declaration"),t.typeParameters=this.tsTryParseTypeParameters(),this.eat(d._extends)&&(t.extends=this.tsParseHeritageClause("extends"));const e=this.startNode();return e.body=this.tsInType(this.tsParseObjectTypeMembers.bind(this)),t.body=this.finishNode(e,"TSInterfaceBody"),this.finishNode(t,"TSInterfaceDeclaration")}tsParseTypeAliasDeclaration(t){return t.id=this.parseIdentifier(),this.checkLVal(t.id,B,void 0,"typescript type alias"),t.typeParameters=this.tsTryParseTypeParameters(),t.typeAnnotation=this.tsExpectThenParseType(d.eq),this.semicolon(),this.finishNode(t,"TSTypeAliasDeclaration")}tsInNoContext(t){const e=this.state.context;this.state.context=[e[0]];try{return t()}finally{this.state.context=e}}tsInType(t){const e=this.state.inType;this.state.inType=!0;try{return t()}finally{this.state.inType=e}}tsEatThenParseType(t){return this.match(t)?this.tsNextThenParseType():void 0}tsExpectThenParseType(t){return this.tsDoThenParseType(()=>this.expect(t))}tsNextThenParseType(){return this.tsDoThenParseType(()=>this.next())}tsDoThenParseType(t){return this.tsInType(()=>(t(),this.tsParseType()))}tsParseEnumMember(){const t=this.startNode();return t.id=this.match(d.string)?this.parseExprAtom():this.parseIdentifier(!0),this.eat(d.eq)&&(t.initializer=this.parseMaybeAssign()),this.finishNode(t,"TSEnumMember")}tsParseEnumDeclaration(t,e){return e&&(t.const=!0),t.id=this.parseIdentifier(),this.checkLVal(t.id,e?z:U,void 0,"typescript enum declaration"),this.expect(d.braceL),t.members=this.tsParseDelimitedList("EnumMembers",this.tsParseEnumMember.bind(this)),this.expect(d.braceR),this.finishNode(t,"TSEnumDeclaration")}tsParseModuleBlock(){const t=this.startNode();return this.scope.enter(f),this.expect(d.braceL),this.parseBlockOrModuleBlockBody(t.body=[],void 0,!0,d.braceR),this.scope.exit(),this.finishNode(t,"TSModuleBlock")}tsParseModuleOrNamespaceDeclaration(t,e=!1){if(t.id=this.parseIdentifier(),e||this.checkLVal(t.id,W,null,"module or namespace declaration"),this.eat(d.dot)){const e=this.startNode();this.tsParseModuleOrNamespaceDeclaration(e,!0),t.body=e}else this.scope.enter(P),this.prodParam.enter(re),t.body=this.tsParseModuleBlock(),this.prodParam.exit(),this.scope.exit();return this.finishNode(t,"TSModuleDeclaration")}tsParseAmbientExternalModuleDeclaration(t){return this.isContextual("global")?(t.global=!0,t.id=this.parseIdentifier()):this.match(d.string)?t.id=this.parseExprAtom():this.unexpected(),this.match(d.braceL)?(this.scope.enter(P),this.prodParam.enter(re),t.body=this.tsParseModuleBlock(),this.prodParam.exit(),this.scope.exit()):this.semicolon(),this.finishNode(t,"TSModuleDeclaration")}tsParseImportEqualsDeclaration(t,e){return t.isExport=e||!1,t.id=this.parseIdentifier(),this.checkLVal(t.id,_,void 0,"import equals declaration"),this.expect(d.eq),t.moduleReference=this.tsParseModuleReference(),this.semicolon(),this.finishNode(t,"TSImportEqualsDeclaration")}tsIsExternalModuleReference(){return this.isContextual("require")&&40===this.lookaheadCharCode()}tsParseModuleReference(){return this.tsIsExternalModuleReference()?this.tsParseExternalModuleReference():this.tsParseEntityName(!1)}tsParseExternalModuleReference(){const t=this.startNode();if(this.expectContextual("require"),this.expect(d.parenL),!this.match(d.string))throw this.unexpected();return t.expression=this.parseExprAtom(),this.expect(d.parenR),this.finishNode(t,"TSExternalModuleReference")}tsLookAhead(t){const e=this.state.clone(),s=t();return this.state=e,s}tsTryParseAndCatch(t){const e=this.tryParse(e=>t()||e());if(!e.aborted&&e.node)return e.error&&(this.state=e.failState),e.node}tsTryParse(t){const e=this.state.clone(),s=t();return void 0!==s&&!1!==s?s:void(this.state=e)}tsTryParseDeclare(t){if(this.isLineTerminator())return;let e,s=this.state.type;switch(this.isContextual("let")&&(s=d._var,e="let"),s){case d._function:return this.parseFunctionStatement(t,!1,!0);case d._class:return t.declare=!0,this.parseClass(t,!0,!1);case d._const:if(this.match(d._const)&&this.isLookaheadContextual("enum"))return this.expect(d._const),this.expectContextual("enum"),this.tsParseEnumDeclaration(t,!0);case d._var:return e=e||this.state.value,this.parseVarStatement(t,e);case d.name:{const e=this.state.value;return"global"===e?this.tsParseAmbientExternalModuleDeclaration(t):this.tsParseDeclaration(t,e,!0)}}}tsTryParseExportDeclaration(){return this.tsParseDeclaration(this.startNode(),this.state.value,!0)}tsParseExpressionStatement(t,e){switch(e.name){case"declare":{const e=this.tsTryParseDeclare(t);if(e)return e.declare=!0,e;break}case"global":if(this.match(d.braceL)){this.scope.enter(P),this.prodParam.enter(re);const s=t;return s.global=!0,s.id=e,s.body=this.tsParseModuleBlock(),this.scope.exit(),this.prodParam.exit(),this.finishNode(s,"TSModuleDeclaration")}break;default:return this.tsParseDeclaration(t,e.name,!1)}}tsParseDeclaration(t,e,s){switch(e){case"abstract":if(this.tsCheckLineTerminatorAndMatch(d._class,s)){const e=t;return e.abstract=!0,s&&(this.next(),this.match(d._class)||this.unexpected(null,d._class)),this.parseClass(e,!0,!1)}break;case"enum":if(s||this.match(d.name))return s&&this.next(),this.tsParseEnumDeclaration(t,!1);break;case"interface":if(this.tsCheckLineTerminatorAndMatch(d.name,s))return s&&this.next(),this.tsParseInterfaceDeclaration(t);break;case"module":if(s&&this.next(),this.match(d.string))return this.tsParseAmbientExternalModuleDeclaration(t);if(this.tsCheckLineTerminatorAndMatch(d.name,s))return this.tsParseModuleOrNamespaceDeclaration(t);break;case"namespace":if(this.tsCheckLineTerminatorAndMatch(d.name,s))return s&&this.next(),this.tsParseModuleOrNamespaceDeclaration(t);break;case"type":if(this.tsCheckLineTerminatorAndMatch(d.name,s))return s&&this.next(),this.tsParseTypeAliasDeclaration(t);break}}tsCheckLineTerminatorAndMatch(t,e){return(e||this.match(t))&&!this.isLineTerminator()}tsTryParseGenericAsyncArrowFunction(t,e){if(!this.isRelational("<"))return;const s=this.state.maybeInArrowParameters,i=this.state.yieldPos,r=this.state.awaitPos;this.state.maybeInArrowParameters=!0,this.state.yieldPos=-1,this.state.awaitPos=-1;const n=this.tsTryParseAndCatch(()=>{const s=this.startNodeAt(t,e);return s.typeParameters=this.tsParseTypeParameters(),super.parseFunctionParams(s),s.returnType=this.tsTryParseTypeOrTypePredicateAnnotation(),this.expect(d.arrow),s});return this.state.maybeInArrowParameters=s,this.state.yieldPos=i,this.state.awaitPos=r,n?this.parseArrowExpression(n,null,!0):void 0}tsParseTypeArguments(){const t=this.startNode();return t.params=this.tsInType(()=>this.tsInNoContext(()=>(this.expectRelational("<"),this.tsParseDelimitedList("TypeParametersOrArguments",this.tsParseType.bind(this))))),this.state.exprAllowed=!1,this.expectRelational(">"),this.finishNode(t,"TSTypeParameterInstantiation")}tsIsDeclarationStart(){if(this.match(d.name))switch(this.state.value){case"abstract":case"declare":case"enum":case"interface":case"module":case"namespace":case"type":return!0}return!1}isExportDefaultSpecifier(){return!this.tsIsDeclarationStart()&&super.isExportDefaultSpecifier()}parseAssignableListItem(t,e){const s=this.state.start,i=this.state.startLoc;let r,n=!1;t&&(r=this.parseAccessModifier(),n=!!this.tsParseModifier(["readonly"]));const a=this.parseMaybeDefault();this.parseAssignableListItemTypes(a);const o=this.parseMaybeDefault(a.start,a.loc.start,a);if(r||n){const t=this.startNodeAt(s,i);return e.length&&(t.decorators=e),r&&(t.accessibility=r),n&&(t.readonly=n),"Identifier"!==o.type&&"AssignmentPattern"!==o.type&&this.raise(t.start,ue.UnsupportedParameterPropertyKind),t.parameter=o,this.finishNode(t,"TSParameterProperty")}return e.length&&(a.decorators=e),o}parseFunctionBodyAndFinish(t,e,s=!1){this.match(d.colon)&&(t.returnType=this.tsParseTypeOrTypePredicateAnnotation(d.colon));const i="FunctionDeclaration"===e?"TSDeclareFunction":"ClassMethod"===e?"TSDeclareMethod":void 0;i&&!this.match(d.braceL)&&this.isLineTerminator()?this.finishNode(t,i):super.parseFunctionBodyAndFinish(t,e,s)}registerFunctionStatementId(t){!t.body&&t.id?this.checkLVal(t.id,q,null,"function name"):super.registerFunctionStatementId(...arguments)}parseSubscript(t,e,s,i,r){if(!this.hasPrecedingLineBreak()&&this.match(d.bang)){this.state.exprAllowed=!1,this.next();const i=this.startNodeAt(e,s);return i.expression=t,this.finishNode(i,"TSNonNullExpression")}if(this.isRelational("<")){const n=this.tsTryParseAndCatch(()=>{if(!i&&this.atPossibleAsyncArrow(t)){const t=this.tsTryParseGenericAsyncArrowFunction(e,s);if(t)return t}const n=this.startNodeAt(e,s);n.callee=t;const a=this.tsParseTypeArguments();if(a){if(!i&&this.eat(d.parenL))return n.arguments=this.parseCallExpressionArguments(d.parenR,!1),n.typeParameters=a,this.finishCallExpression(n,r.optionalChainMember);if(this.match(d.backQuote))return this.parseTaggedTemplateExpression(e,s,t,r,a)}this.unexpected()});if(n)return n}return super.parseSubscript(t,e,s,i,r)}parseNewArguments(t){if(this.isRelational("<")){const e=this.tsTryParseAndCatch(()=>{const t=this.tsParseTypeArguments();return this.match(d.parenL)||this.unexpected(),t});e&&(t.typeParameters=e)}super.parseNewArguments(t)}parseExprOp(t,e,s,i,r){if(le(d._in.binop)>i&&!this.hasPrecedingLineBreak()&&this.isContextual("as")){const n=this.startNodeAt(e,s);n.expression=t;const a=this.tsTryNextParseConstantContext();return n.typeAnnotation=a||this.tsNextThenParseType(),this.finishNode(n,"TSAsExpression"),this.parseExprOp(n,e,s,i,r)}return super.parseExprOp(t,e,s,i,r)}checkReservedWord(t,e,s,i){}checkDuplicateExports(){}parseImport(t){if(this.match(d.name)||this.match(d.star)||this.match(d.braceL)){const e=this.lookahead();if(this.match(d.name)&&e.type===d.eq)return this.tsParseImportEqualsDeclaration(t);!this.isContextual("type")||e.type===d.comma||e.type===d.name&&"from"===e.value?t.importKind="value":(t.importKind="type",this.next())}const e=super.parseImport(t);return"type"===e.importKind&&e.specifiers.length>1&&"ImportDefaultSpecifier"===e.specifiers[0].type&&this.raise(e.start,"A type-only import can specify a default import or named bindings, but not both."),e}parseExport(t){if(this.match(d._import))return this.expect(d._import),this.tsParseImportEqualsDeclaration(t,!0);if(this.eat(d.eq)){const e=t;return e.expression=this.parseExpression(),this.semicolon(),this.finishNode(e,"TSExportAssignment")}if(this.eatContextual("as")){const e=t;return this.expectContextual("namespace"),e.id=this.parseIdentifier(),this.semicolon(),this.finishNode(e,"TSNamespaceExportDeclaration")}return this.isContextual("type")&&this.lookahead().type===d.braceL?(this.next(),t.exportKind="type"):t.exportKind="value",super.parseExport(t)}isAbstractClass(){return this.isContextual("abstract")&&this.lookahead().type===d._class}parseExportDefaultExpression(){if(this.isAbstractClass()){const t=this.startNode();return this.next(),this.parseClass(t,!0,!0),t.abstract=!0,t}if("interface"===this.state.value){const t=this.tsParseDeclaration(this.startNode(),this.state.value,!0);if(t)return t}return super.parseExportDefaultExpression()}parseStatementContent(t,e){if(this.state.type===d._const){const t=this.lookahead();if(t.type===d.name&&"enum"===t.value){const t=this.startNode();return this.expect(d._const),this.expectContextual("enum"),this.tsParseEnumDeclaration(t,!0)}}return super.parseStatementContent(t,e)}parseAccessModifier(){return this.tsParseModifier(["public","protected","private"])}parseClassMember(t,e,s,i){this.tsParseModifiers(e,["declare"]);const r=this.parseAccessModifier();r&&(e.accessibility=r),this.tsParseModifiers(e,["declare"]),super.parseClassMember(t,e,s,i)}parseClassMemberWithIsStatic(t,e,s,i,r){this.tsParseModifiers(e,["abstract","readonly","declare"]);const n=this.tsTryParseIndexSignature(e);if(n)return t.body.push(n),e.abstract&&this.raise(e.start,ue.IndexSignatureHasAbstract),i&&this.raise(e.start,ue.IndexSignatureHasStatic),void(e.accessibility&&this.raise(e.start,ue.IndexSignatureHasAccessibility,e.accessibility));super.parseClassMemberWithIsStatic(t,e,s,i,r)}parsePostMemberNameModifiers(t){const e=this.eat(d.question);e&&(t.optional=!0),t.readonly&&this.match(d.parenL)&&this.raise(t.start,ue.ClassMethodHasReadonly),t.declare&&this.match(d.parenL)&&this.raise(t.start,ue.ClassMethodHasDeclare)}parseExpressionStatement(t,e){const s="Identifier"===e.type?this.tsParseExpressionStatement(t,e):void 0;return s||super.parseExpressionStatement(t,e)}shouldParseExportDeclaration(){return!!this.tsIsDeclarationStart()||super.shouldParseExportDeclaration()}parseConditional(t,e,s,i,r){if(!r||!this.match(d.question))return super.parseConditional(t,e,s,i,r);const n=this.tryParse(()=>super.parseConditional(t,e,s,i));return n.node?(n.error&&(this.state=n.failState),n.node):(r.start=n.error.pos||this.state.start,t)}parseParenItem(t,e,s){if(t=super.parseParenItem(t,e,s),this.eat(d.question)&&(t.optional=!0,this.resetEndLocation(t)),this.match(d.colon)){const i=this.startNodeAt(e,s);return i.expression=t,i.typeAnnotation=this.tsParseTypeAnnotation(),this.finishNode(i,"TSTypeCastExpression")}return t}parseExportDeclaration(t){const e=this.state.start,s=this.state.startLoc,i=this.eatContextual("declare");let r;return this.match(d.name)&&(r=this.tsTryParseExportDeclaration()),r||(r=super.parseExportDeclaration(t)),r&&("TSInterfaceDeclaration"===r.type||"TSTypeAliasDeclaration"===r.type||i)&&(t.exportKind="type"),r&&i&&(this.resetStartLocation(r,e,s),r.declare=!0),r}parseClassId(t,e,s){if((!e||s)&&this.isContextual("implements"))return;super.parseClassId(t,e,s,t.declare?q:L);const i=this.tsTryParseTypeParameters();i&&(t.typeParameters=i)}parseClassPropertyAnnotation(t){!t.optional&&this.eat(d.bang)&&(t.definite=!0);const e=this.tsTryParseTypeAnnotation();e&&(t.typeAnnotation=e)}parseClassProperty(t){return this.parseClassPropertyAnnotation(t),t.declare&&this.match(d.equal)&&this.raise(this.state.start,ue.DeclareClassFieldHasInitializer),super.parseClassProperty(t)}parseClassPrivateProperty(t){return t.abstract&&this.raise(t.start,ue.PrivateElementHasAbstract),t.accessibility&&this.raise(t.start,ue.PrivateElementHasAccessibility,t.accessibility),this.parseClassPropertyAnnotation(t),super.parseClassPrivateProperty(t)}pushClassMethod(t,e,s,i,r,n){const a=this.tsTryParseTypeParameters();a&&(e.typeParameters=a),super.pushClassMethod(t,e,s,i,r,n)}pushClassPrivateMethod(t,e,s,i){const r=this.tsTryParseTypeParameters();r&&(e.typeParameters=r),super.pushClassPrivateMethod(t,e,s,i)}parseClassSuper(t){super.parseClassSuper(t),t.superClass&&this.isRelational("<")&&(t.superTypeParameters=this.tsParseTypeArguments()),this.eatContextual("implements")&&(t.implements=this.tsParseHeritageClause("implements"))}parseObjPropValue(t,...e){const s=this.tsTryParseTypeParameters();s&&(t.typeParameters=s),super.parseObjPropValue(t,...e)}parseFunctionParams(t,e){const s=this.tsTryParseTypeParameters();s&&(t.typeParameters=s),super.parseFunctionParams(t,e)}parseVarId(t,e){super.parseVarId(t,e),"Identifier"===t.id.type&&this.eat(d.bang)&&(t.definite=!0);const s=this.tsTryParseTypeAnnotation();s&&(t.id.typeAnnotation=s,this.resetEndLocation(t.id))}parseAsyncArrowFromCallExpression(t,e){return this.match(d.colon)&&(t.returnType=this.tsParseTypeAnnotation()),super.parseAsyncArrowFromCallExpression(t,e)}parseMaybeAssign(...t){var e,s,i,r,n,a,o;let c,h,l,p;if(this.match(d.jsxTagStart)){if(c=this.state.clone(),h=this.tryParse(()=>super.parseMaybeAssign(...t),c),!h.error)return h.node;const{context:e}=this.state;e[e.length-1]===gt.j_oTag?e.length-=2:e[e.length-1]===gt.j_expr&&(e.length-=1)}if(!(null==(e=h)?void 0:e.error)&&!this.isRelational("<"))return super.parseMaybeAssign(...t);c=c||this.state.clone();const u=this.tryParse(e=>{var s;p=this.tsParseTypeParameters();const i=super.parseMaybeAssign(...t);return("ArrowFunctionExpression"!==i.type||i.extra&&i.extra.parenthesized)&&e(),0!==(null==(s=p)?void 0:s.params.length)&&this.resetStartLocationFromNode(i,p),i.typeParameters=p,i},c);if(!u.error&&!u.aborted)return u.node;if(!h&&(pe(!this.hasPlugin("jsx")),l=this.tryParse(()=>super.parseMaybeAssign(...t),c),!l.error))return l.node;if(null==(s=h)?void 0:s.node)return this.state=h.failState,h.node;if(u.node)return this.state=u.failState,u.node;if(null==(i=l)?void 0:i.node)return this.state=l.failState,l.node;if(null==(r=h)?void 0:r.thrown)throw h.error;if(u.thrown)throw u.error;if(null==(n=l)?void 0:n.thrown)throw l.error;throw(null==(a=h)?void 0:a.error)||u.error||(null==(o=l)?void 0:o.error)}parseMaybeUnary(t){return!this.hasPlugin("jsx")&&this.isRelational("<")?this.tsParseTypeAssertion():super.parseMaybeUnary(t)}parseArrow(t){if(this.match(d.colon)){const e=this.tryParse(t=>{const e=this.tsParseTypeOrTypePredicateAnnotation(d.colon);return!this.canInsertSemicolon()&&this.match(d.arrow)||t(),e});if(e.aborted)return;e.thrown||(e.error&&(this.state=e.failState),t.returnType=e.node)}return super.parseArrow(t)}parseAssignableListItemTypes(t){this.eat(d.question)&&("Identifier"!==t.type&&this.raise(t.start,ue.PatternIsOptional),t.optional=!0);const e=this.tsTryParseTypeAnnotation();return e&&(t.typeAnnotation=e),this.resetEndLocation(t),t}toAssignable(t){switch(t.type){case"TSTypeCastExpression":return super.toAssignable(this.typeCastToParameter(t));case"TSParameterProperty":return super.toAssignable(t);case"TSAsExpression":case"TSNonNullExpression":case"TSTypeAssertion":return t.expression=this.toAssignable(t.expression),t;default:return super.toAssignable(t)}}checkLVal(t,e=V,s,i){switch(t.type){case"TSTypeCastExpression":return;case"TSParameterProperty":return void this.checkLVal(t.parameter,e,s,"parameter property");case"TSAsExpression":case"TSNonNullExpression":case"TSTypeAssertion":return void this.checkLVal(t.expression,e,s,i);default:return void super.checkLVal(t,e,s,i)}}parseBindingAtom(){switch(this.state.type){case d._this:return this.parseIdentifier(!0);default:return super.parseBindingAtom()}}parseMaybeDecoratorArguments(t){if(this.isRelational("<")){const e=this.tsParseTypeArguments();if(this.match(d.parenL)){const s=super.parseMaybeDecoratorArguments(t);return s.typeParameters=e,s}this.unexpected(this.state.start,d.parenL)}return super.parseMaybeDecoratorArguments(t)}isClassMethod(){return this.isRelational("<")||super.isClassMethod()}isClassProperty(){return this.match(d.bang)||this.match(d.colon)||super.isClassProperty()}parseMaybeDefault(...t){const e=super.parseMaybeDefault(...t);return"AssignmentPattern"===e.type&&e.typeAnnotation&&e.right.startthis.tsParseTypeArguments());e&&(t.typeParameters=e)}return super.jsxParseOpeningElementAfterName(t)}getGetterSetterExpectedParamCount(t){const e=super.getGetterSetterExpectedParamCount(t),s=t.params[0],i=s&&"Identifier"===s.type&&"this"===s.name;return i?e+1:e}};d.placeholder=new h("%%",{startsExpr:!0});var me=t=>class extends t{parsePlaceholder(t){if(this.match(d.placeholder)){const e=this.startNode();return this.next(),this.assertNoSpace("Unexpected space in placeholder."),e.name=super.parseIdentifier(!0),this.assertNoSpace("Unexpected space in placeholder."),this.expect(d.placeholder),this.finishPlaceholder(e,t)}}finishPlaceholder(t,e){const s=!(!t.expectedNode||"Placeholder"!==t.type);return t.expectedNode=e,s?t:this.finishNode(t,"Placeholder")}getTokenFromCode(t){return 37===t&&37===this.input.charCodeAt(this.state.pos+1)?this.finishOp(d.placeholder,2):super.getTokenFromCode(...arguments)}parseExprAtom(){return this.parsePlaceholder("Expression")||super.parseExprAtom(...arguments)}parseIdentifier(){return this.parsePlaceholder("Identifier")||super.parseIdentifier(...arguments)}checkReservedWord(t){void 0!==t&&super.checkReservedWord(...arguments)}parseBindingAtom(){return this.parsePlaceholder("Pattern")||super.parseBindingAtom(...arguments)}checkLVal(t){"Placeholder"!==t.type&&super.checkLVal(...arguments)}toAssignable(t){return t&&"Placeholder"===t.type&&"Expression"===t.expectedNode?(t.expectedNode="Pattern",t):super.toAssignable(...arguments)}verifyBreakContinue(t){t.label&&"Placeholder"===t.label.type||super.verifyBreakContinue(...arguments)}parseExpressionStatement(t,e){if("Placeholder"!==e.type||e.extra&&e.extra.parenthesized)return super.parseExpressionStatement(...arguments);if(this.match(d.colon)){const s=t;return s.label=this.finishPlaceholder(e,"Identifier"),this.next(),s.body=this.parseStatement("label"),this.finishNode(s,"LabeledStatement")}return this.semicolon(),t.name=e.name,this.finishPlaceholder(t,"Statement")}parseBlock(){return this.parsePlaceholder("BlockStatement")||super.parseBlock(...arguments)}parseFunctionId(){return this.parsePlaceholder("Identifier")||super.parseFunctionId(...arguments)}parseClass(t,e,s){const i=e?"ClassDeclaration":"ClassExpression";this.next(),this.takeDecorators(t);const r=this.parsePlaceholder("Identifier");if(r)if(this.match(d._extends)||this.match(d.placeholder)||this.match(d.braceL))t.id=r;else{if(s||!e)return t.id=null,t.body=this.finishPlaceholder(r,"ClassBody"),this.finishNode(t,i);this.unexpected(null,"A class name is required")}else this.parseClassId(t,e,s);return this.parseClassSuper(t),t.body=this.parsePlaceholder("ClassBody")||this.parseClassBody(!!t.superClass),this.finishNode(t,i)}parseExport(t){const e=this.parsePlaceholder("Identifier");if(!e)return super.parseExport(...arguments);if(!this.isContextual("from")&&!this.match(d.comma))return t.specifiers=[],t.source=null,t.declaration=this.finishPlaceholder(e,"Declaration"),this.finishNode(t,"ExportNamedDeclaration");this.expectPlugin("exportDefaultFrom");const s=this.startNode();return s.exported=e,t.specifiers=[this.finishNode(s,"ExportDefaultSpecifier")],super.parseExport(t)}isExportDefaultSpecifier(){if(this.match(d._default)){const t=this.nextTokenStart();if(this.isUnparsedContextual(t,"from")&&this.input.startsWith(d.placeholder.label,this.nextTokenStartSince(t+4)))return!0}return super.isExportDefaultSpecifier()}maybeParseExportDefaultSpecifier(t){return!!(t.specifiers&&t.specifiers.length>0)||super.maybeParseExportDefaultSpecifier(...arguments)}checkExport(t){const{specifiers:e}=t;(null==e?void 0:e.length)&&(t.specifiers=e.filter(t=>"Placeholder"===t.exported.type)),super.checkExport(t),t.specifiers=e}parseImport(t){const e=this.parsePlaceholder("Identifier");if(!e)return super.parseImport(...arguments);if(t.specifiers=[],!this.isContextual("from")&&!this.match(d.comma))return t.source=this.finishPlaceholder(e,"StringLiteral"),this.semicolon(),this.finishNode(t,"ImportDeclaration");const s=this.startNodeAtNode(e);if(s.local=e,this.finishNode(s,"ImportDefaultSpecifier"),t.specifiers.push(s),this.eat(d.comma)){const e=this.maybeParseStarImportSpecifier(t);e||this.parseNamedImportSpecifiers(t)}return this.expectContextual("from"),t.source=this.parseImportSource(),this.semicolon(),this.finishNode(t,"ImportDeclaration")}parseImportSource(){return this.parsePlaceholder("StringLiteral")||super.parseImportSource(...arguments)}},ye=t=>class extends t{parseV8Intrinsic(){if(this.match(d.modulo)){const t=this.state.start,e=this.startNode();if(this.eat(d.modulo),this.match(d.name)){const t=this.parseIdentifierName(this.state.start),s=this.createIdentifier(e,t);if(s.type="V8IntrinsicIdentifier",this.match(d.parenL))return s}this.unexpected(t)}}parseExprAtom(){return this.parseV8Intrinsic()||super.parseExprAtom(...arguments)}};function ge(t,e){return t.some(t=>Array.isArray(t)?t[0]===e:t===e)}function xe(t,e,s){const i=t.find(t=>Array.isArray(t)?t[0]===e:t===e);return i&&Array.isArray(i)?i[1][s]:null}const be=["minimal","smart","fsharp"],ve=["hash","bar"];function we(t){if(ge(t,"decorators")){if(ge(t,"decorators-legacy"))throw new Error("Cannot use the decorators and decorators-legacy plugin together");const e=xe(t,"decorators","decoratorsBeforeExport");if(null==e)throw new Error("The 'decorators' plugin requires a 'decoratorsBeforeExport' option, whose value must be a boolean. If you are migrating from Babylon/Babel 6 or want to use the old decorators proposal, you should use the 'decorators-legacy' plugin instead of 'decorators'.");if("boolean"!==typeof e)throw new Error("'decoratorsBeforeExport' must be a boolean.")}if(ge(t,"flow")&&ge(t,"typescript"))throw new Error("Cannot combine flow and typescript plugins.");if(ge(t,"placeholders")&&ge(t,"v8intrinsic"))throw new Error("Cannot combine placeholders and v8intrinsic plugins.");if(ge(t,"pipelineOperator")&&!be.includes(xe(t,"pipelineOperator","proposal")))throw new Error("'pipelineOperator' requires 'proposal' option whose value should be one of: "+be.map(t=>`'${t}'`).join(", "));if(ge(t,"moduleAttributes")){const e=xe(t,"moduleAttributes","version");if("may-2020"!==e)throw new Error("The 'moduleAttributes' plugin requires a 'version' option, representing the last proposal update. Currently, the only supported value is 'may-2020'.")}if(ge(t,"recordAndTuple")&&!ve.includes(xe(t,"recordAndTuple","syntaxType")))throw new Error("'recordAndTuple' requires 'syntaxType' option whose value should be one of: "+ve.map(t=>`'${t}'`).join(", "))}const Pe={estree:mt,jsx:Zt,flow:Kt,typescript:fe,v8intrinsic:ye,placeholders:me},Te=Object.keys(Pe),Ee={sourceType:"script",sourceFilename:void 0,startLine:1,allowAwaitOutsideFunction:!1,allowReturnOutsideFunction:!1,allowImportExportEverywhere:!1,allowSuperOutsideMethod:!1,allowUndeclaredExports:!1,plugins:[],strictMode:null,ranges:!1,tokens:!1,createParenthesizedExpressions:!1,errorRecovery:!1};function Ae(t){const e={};for(let s=0,i=Object.keys(Ee);s=48&&t<=57};const ke=new Set(["g","m","s","i","y","u"]),Ne={decBinOct:[46,66,69,79,95,98,101,111],hex:[46,88,95,120]},Ie={bin:[48,49]};Ie.oct=[...Ie.bin,50,51,52,53,54,55],Ie.dec=[...Ie.oct,56,57],Ie.hex=[...Ie.dec,65,66,67,68,69,70,97,98,99,100,101,102];class Oe{constructor(t){this.type=t.type,this.value=t.value,this.start=t.start,this.end=t.end,this.loc=new ot(t.startLoc,t.endLoc)}}class De extends dt{constructor(t,e){super(),this.tokens=[],this.state=new Se,this.state.init(t),this.input=e,this.length=e.length,this.isLookahead=!1}pushToken(t){this.tokens.length=this.state.tokensLength,this.tokens.push(t),++this.state.tokensLength}next(){this.isLookahead||(this.checkKeywordEscapes(),this.options.tokens&&this.pushToken(new Oe(this.state))),this.state.lastTokEnd=this.state.end,this.state.lastTokStart=this.state.start,this.state.lastTokEndLoc=this.state.endLoc,this.state.lastTokStartLoc=this.state.startLoc,this.nextToken()}eat(t){return!!this.match(t)&&(this.next(),!0)}match(t){return this.state.type===t}lookahead(){const t=this.state;this.state=t.clone(!0),this.isLookahead=!0,this.next(),this.isLookahead=!1;const e=this.state;return this.state=t,e}nextTokenStart(){return this.nextTokenStartSince(this.state.pos)}nextTokenStartSince(t){rt.lastIndex=t;const e=rt.exec(this.input);return t+e[0].length}lookaheadCharCode(){return this.input.charCodeAt(this.nextTokenStart())}setStrict(t){if(this.state.strict=t,this.match(d.num)||this.match(d.string)){this.state.pos=this.state.start;while(this.state.pos=this.length)return void this.finishToken(d.eof);const e=null==t?void 0:t.override;e?e(this):this.getTokenFromCode(this.input.codePointAt(this.state.pos))}pushComment(t,e,s,i,r,n){const a={type:t?"CommentBlock":"CommentLine",value:e,start:s,end:i,loc:new ot(r,n)};this.options.tokens&&this.pushToken(a),this.state.comments.push(a),this.addComment(a)}skipBlockComment(){const t=this.state.curPosition(),e=this.state.pos,s=this.input.indexOf("*/",this.state.pos+2);if(-1===s)throw this.raise(e,ut.UnterminatedComment);let i;this.state.pos=s+2,st.lastIndex=e;while((i=st.exec(this.input))&&i.index=48&&e<=57)throw this.raise(this.state.pos,ut.UnexpectedDigitAfterHash);if(123===e||91===e&&this.hasPlugin("recordAndTuple")){if(this.expectPlugin("recordAndTuple"),"hash"!==this.getPluginOption("recordAndTuple","syntaxType"))throw this.raise(this.state.pos,123===e?ut.RecordExpressionHashIncorrectStartSyntaxType:ut.TupleExpressionHashIncorrectStartSyntaxType);123===e?this.finishToken(d.braceHashL):this.finishToken(d.bracketHashL),this.state.pos+=2}else this.finishOp(d.hash,1)}readToken_dot(){const t=this.input.charCodeAt(this.state.pos+1);t>=48&&t<=57?this.readNumber(!0):46===t&&46===this.input.charCodeAt(this.state.pos+2)?(this.state.pos+=3,this.finishToken(d.ellipsis)):(++this.state.pos,this.finishToken(d.dot))}readToken_slash(){if(this.state.exprAllowed&&!this.state.inType)return++this.state.pos,void this.readRegexp();const t=this.input.charCodeAt(this.state.pos+1);61===t?this.finishOp(d.assign,2):this.finishOp(d.slash,1)}readToken_interpreter(){if(0!==this.state.pos||this.length<2)return!1;let t=this.input.charCodeAt(this.state.pos+1);if(33!==t)return!1;const e=this.state.pos;this.state.pos+=1;while(!it(t)&&++this.state.pos=48&&e<=57?(++this.state.pos,this.finishToken(d.question)):(this.state.pos+=2,this.finishToken(d.questionDot)):61===e?this.finishOp(d.assign,3):this.finishOp(d.nullishCoalescing,2)}getTokenFromCode(t){switch(t){case 46:return void this.readToken_dot();case 40:return++this.state.pos,void this.finishToken(d.parenL);case 41:return++this.state.pos,void this.finishToken(d.parenR);case 59:return++this.state.pos,void this.finishToken(d.semi);case 44:return++this.state.pos,void this.finishToken(d.comma);case 91:if(this.hasPlugin("recordAndTuple")&&124===this.input.charCodeAt(this.state.pos+1)){if("bar"!==this.getPluginOption("recordAndTuple","syntaxType"))throw this.raise(this.state.pos,ut.TupleExpressionBarIncorrectStartSyntaxType);this.finishToken(d.bracketBarL),this.state.pos+=2}else++this.state.pos,this.finishToken(d.bracketL);return;case 93:return++this.state.pos,void this.finishToken(d.bracketR);case 123:if(this.hasPlugin("recordAndTuple")&&124===this.input.charCodeAt(this.state.pos+1)){if("bar"!==this.getPluginOption("recordAndTuple","syntaxType"))throw this.raise(this.state.pos,ut.RecordExpressionBarIncorrectStartSyntaxType);this.finishToken(d.braceBarL),this.state.pos+=2}else++this.state.pos,this.finishToken(d.braceL);return;case 125:return++this.state.pos,void this.finishToken(d.braceR);case 58:return void(this.hasPlugin("functionBind")&&58===this.input.charCodeAt(this.state.pos+1)?this.finishOp(d.doubleColon,2):(++this.state.pos,this.finishToken(d.colon)));case 63:return void this.readToken_question();case 96:return++this.state.pos,void this.finishToken(d.backQuote);case 48:{const t=this.input.charCodeAt(this.state.pos+1);if(120===t||88===t)return void this.readRadixNumber(16);if(111===t||79===t)return void this.readRadixNumber(8);if(98===t||66===t)return void this.readRadixNumber(2)}case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:return void this.readNumber(!1);case 34:case 39:return void this.readString(t);case 47:return void this.readToken_slash();case 37:case 42:return void this.readToken_mult_modulo(t);case 124:case 38:return void this.readToken_pipe_amp(t);case 94:return void this.readToken_caret();case 43:case 45:return void this.readToken_plus_min(t);case 60:case 62:return void this.readToken_lt_gt(t);case 61:case 33:return void this.readToken_eq_excl(t);case 126:return void this.finishOp(d.tilde,1);case 64:return++this.state.pos,void this.finishToken(d.at);case 35:return void this.readToken_numberSign();case 92:return void this.readWord();default:if(At(t))return void this.readWord()}throw this.raise(this.state.pos,ut.InvalidOrUnexpectedToken,String.fromCodePoint(t))}finishOp(t,e){const s=this.input.slice(this.state.pos,this.state.pos+e);this.state.pos+=e,this.finishToken(t,s)}readRegexp(){const t=this.state.pos;let e,s;for(;;){if(this.state.pos>=this.length)throw this.raise(t,ut.UnterminatedRegExp);const i=this.input.charAt(this.state.pos);if(et.test(i))throw this.raise(t,ut.UnterminatedRegExp);if(e)e=!1;else{if("["===i)s=!0;else if("]"===i&&s)s=!1;else if("/"===i&&!s)break;e="\\"===i}++this.state.pos}const i=this.input.slice(t,this.state.pos);++this.state.pos;let r="";while(this.state.pos-1&&this.raise(this.state.pos+1,ut.DuplicateRegExpFlags);else{if(!St(e)&&92!==e)break;this.raise(this.state.pos+1,ut.MalformedRegExpFlags)}++this.state.pos,r+=t}this.finishToken(d.regexp,{pattern:i,flags:r})}readInt(t,e,s,i=!0){const r=this.state.pos,n=16===t?Ne.hex:Ne.decBinOct,a=16===t?Ie.hex:10===t?Ie.dec:8===t?Ie.oct:Ie.bin;let o=!1,c=0;for(let h=0,l=null==e?1/0:e;h-1||n.indexOf(e)>-1||Number.isNaN(e))&&this.raise(this.state.pos,ut.UnexpectedNumericSeparator),i||this.raise(this.state.pos,ut.NumericSeparatorInEscapeSequence),++this.state.pos}else{if(r=e>=97?e-97+10:e>=65?e-65+10:Ce(e)?e-48:1/0,r>=t)if(this.options.errorRecovery&&r<=9)r=0,this.raise(this.state.start+h+2,ut.InvalidDigit,t);else{if(!s)break;r=0,o=!0}++this.state.pos,c=c*t+r}}return this.state.pos===r||null!=e&&this.state.pos-r!==e||o?null:c}readRadixNumber(t){const e=this.state.pos;let s=!1;this.state.pos+=2;const i=this.readInt(t);null==i&&this.raise(this.state.start+2,ut.InvalidDigit,t);const r=this.input.charCodeAt(this.state.pos);if(95===r&&this.expectPlugin("numericSeparator",this.state.pos),110===r&&(++this.state.pos,s=!0),At(this.input.codePointAt(this.state.pos)))throw this.raise(this.state.pos,ut.NumberIdentifier);if(s){const t=this.input.slice(e,this.state.pos).replace(/[_n]/g,"");this.finishToken(d.bigint,t)}else this.finishToken(d.num,i)}readNumber(t){const e=this.state.pos;let s=!1,i=!1,r=!1;t||null!==this.readInt(10)||this.raise(e,ut.InvalidNumber);let n=this.state.pos-e>=2&&48===this.input.charCodeAt(e);n&&(this.state.strict&&this.raise(e,ut.StrictOctalLiteral),/[89]/.test(this.input.slice(e,this.state.pos))&&(n=!1,r=!0));let a=this.input.charCodeAt(this.state.pos);if(46!==a||n||(++this.state.pos,this.readInt(10),s=!0,a=this.input.charCodeAt(this.state.pos)),69!==a&&101!==a||n||(a=this.input.charCodeAt(++this.state.pos),43!==a&&45!==a||++this.state.pos,null===this.readInt(10)&&this.raise(e,ut.InvalidNumber),s=!0,a=this.input.charCodeAt(this.state.pos)),this.hasPlugin("numericSeparator")&&(n||r)){const t=this.input.slice(e,this.state.pos).indexOf("_");t>0&&this.raise(t+e,ut.ZeroDigitNumericSeparator)}if(95===a&&this.expectPlugin("numericSeparator",this.state.pos),110===a&&((s||n||r)&&this.raise(e,ut.InvalidBigIntLiteral),++this.state.pos,i=!0),At(this.input.codePointAt(this.state.pos)))throw this.raise(this.state.pos,ut.NumberIdentifier);const o=this.input.slice(e,this.state.pos).replace(/[_n]/g,"");if(i)return void this.finishToken(d.bigint,o);const c=n?parseInt(o,8):parseFloat(o);this.finishToken(d.num,c)}readCodePoint(t){const e=this.input.charCodeAt(this.state.pos);let s;if(123===e){const e=++this.state.pos;if(s=this.readHexChar(this.input.indexOf("}",this.state.pos)-this.state.pos,!0,t),++this.state.pos,null!==s&&s>1114111){if(!t)return null;this.raise(e,ut.InvalidCodePoint)}}else s=this.readHexChar(4,!1,t);return s}readString(t){let e="",s=++this.state.pos;for(;;){if(this.state.pos>=this.length)throw this.raise(this.state.start,ut.UnterminatedString);const i=this.input.charCodeAt(this.state.pos);if(i===t)break;if(92===i)e+=this.input.slice(s,this.state.pos),e+=this.readEscapedChar(!1),s=this.state.pos;else if(8232===i||8233===i)++this.state.pos,++this.state.curLine,this.state.lineStart=this.state.pos;else{if(it(i))throw this.raise(this.state.start,ut.UnterminatedString);++this.state.pos}}e+=this.input.slice(s,this.state.pos++),this.finishToken(d.string,e)}readTmplToken(){let t="",e=this.state.pos,s=!1;for(;;){if(this.state.pos>=this.length)throw this.raise(this.state.start,ut.UnterminatedTemplate);const i=this.input.charCodeAt(this.state.pos);if(96===i||36===i&&123===this.input.charCodeAt(this.state.pos+1))return this.state.pos===this.state.start&&this.match(d.template)?36===i?(this.state.pos+=2,void this.finishToken(d.dollarBraceL)):(++this.state.pos,void this.finishToken(d.backQuote)):(t+=this.input.slice(e,this.state.pos),void this.finishToken(d.template,s?null:t));if(92===i){t+=this.input.slice(e,this.state.pos);const i=this.readEscapedChar(!0);null===i?s=!0:t+=i,e=this.state.pos}else if(it(i)){switch(t+=this.input.slice(e,this.state.pos),++this.state.pos,i){case 13:10===this.input.charCodeAt(this.state.pos)&&++this.state.pos;case 10:t+="\n";break;default:t+=String.fromCharCode(i);break}++this.state.curLine,this.state.lineStart=this.state.pos,e=this.state.pos}else++this.state.pos}}readEscapedChar(t){const e=!t,s=this.input.charCodeAt(++this.state.pos);switch(++this.state.pos,s){case 110:return"\n";case 114:return"\r";case 120:{const t=this.readHexChar(2,!1,e);return null===t?null:String.fromCharCode(t)}case 117:{const t=this.readCodePoint(e);return null===t?null:String.fromCodePoint(t)}case 116:return"\t";case 98:return"\b";case 118:return"\v";case 102:return"\f";case 13:10===this.input.charCodeAt(this.state.pos)&&++this.state.pos;case 10:this.state.lineStart=this.state.pos,++this.state.curLine;case 8232:case 8233:return"";case 56:case 57:if(t)return null;default:if(s>=48&&s<=55){const e=this.state.pos-1,s=this.input.substr(this.state.pos-1,3).match(/^[0-7]+/);let i=s[0],r=parseInt(i,8);r>255&&(i=i.slice(0,-1),r=parseInt(i,8)),this.state.pos+=i.length-1;const n=this.input.charCodeAt(this.state.pos);if("0"!==i||56===n||57===n){if(t)return null;this.state.strict?this.raise(e,ut.StrictOctalLiteral):this.state.octalPositions.push(e)}return String.fromCharCode(r)}return String.fromCharCode(s)}}readHexChar(t,e,s){const i=this.state.pos,r=this.readInt(16,t,e,!1);return null===r&&(s?this.raise(i,ut.InvalidEscapeSequence):this.state.pos=i-1),r}readWord1(){let t="";this.state.containsEsc=!1;const e=this.state.pos;let s=this.state.pos;while(this.state.posthis.state.lastTokEnd&&this.raise(this.state.lastTokEnd,t)}unexpected(t,e="Unexpected token"){throw"string"!==typeof e&&(e=`Unexpected token, expected "${e.label}"`),this.raise(null!=t?t:this.state.start,e)}expectPlugin(t,e){if(!this.hasPlugin(t))throw this.raiseWithData(null!=e?e:this.state.start,{missingPlugin:[t]},`This experimental syntax requires enabling the parser plugin: '${t}'`);return!0}expectOnePlugin(t,e){if(!t.some(t=>this.hasPlugin(t)))throw this.raiseWithData(null!=e?e:this.state.start,{missingPlugin:t},`This experimental syntax requires enabling one of the following parser plugin(s): '${t.join(", ")}'`)}checkYieldAwaitInDefaultParams(){-1!==this.state.yieldPos&&(-1===this.state.awaitPos||this.state.yieldPos{throw s.node=t,s});if(this.state.errors.length>e.errors.length){const t=this.state;return this.state=e,{node:i,error:t.errors[e.errors.length],thrown:!1,aborted:!1,failState:t}}return{node:i,error:null,thrown:!1,aborted:!1,failState:null}}catch(i){const t=this.state;if(this.state=e,i instanceof SyntaxError)return{node:null,error:i,thrown:!0,aborted:!1,failState:t};if(i===s)return{node:s.node,error:null,thrown:!1,aborted:!0,failState:t};throw i}}checkExpressionErrors(t,e){if(!t)return!1;const{shorthandAssign:s,doubleProto:i}=t;if(!e)return s>=0||i>=0;s>=0&&this.unexpected(s),i>=0&&this.raise(i,ut.DuplicateProto)}isLiteralPropertyName(){return this.match(d.name)||!!this.state.type.keyword||this.match(d.string)||this.match(d.num)||this.match(d.bigint)}}class Le{constructor(){this.shorthandAssign=-1,this.doubleProto=-1}}class _e{constructor(t,e,s){this.type="",this.start=e,this.end=0,this.loc=new ot(s),(null==t?void 0:t.options.ranges)&&(this.range=[e,0]),(null==t?void 0:t.filename)&&(this.loc.filename=t.filename)}__clone(){const t=new _e,e=Object.keys(this);for(let s=0,i=e.length;s"ParenthesizedExpression"===t.type?je(t.expression):t;class Fe extends Re{toAssignable(t){var e,s;let i=void 0;switch(("ParenthesizedExpression"===t.type||(null==(e=t.extra)?void 0:e.parenthesized))&&(i=je(t),"Identifier"!==i.type&&"MemberExpression"!==i.type&&this.raise(t.start,ut.InvalidParenthesizedAssignment)),t.type){case"Identifier":case"ObjectPattern":case"ArrayPattern":case"AssignmentPattern":break;case"ObjectExpression":t.type="ObjectPattern";for(let e=0,s=t.properties.length,i=s-1;e=s.left.start&&(e.shorthandAssign=-1),this.checkLVal(o,void 0,void 0,"assignment expression"),this.next(),s.right=this.parseMaybeAssign(t),this.finishNode(s,"AssignmentExpression")}return a&&this.checkExpressionErrors(e,!0),o}parseMaybeConditional(t,e,s){const i=this.state.start,r=this.state.startLoc,n=this.state.potentialArrowAt,a=this.parseExprOps(t,e);return"ArrowFunctionExpression"===a.type&&a.start===n||this.checkExpressionErrors(e,!1)?a:this.parseConditional(a,t,i,r,s)}parseConditional(t,e,s,i,r){if(this.eat(d.question)){const r=this.startNodeAt(s,i);return r.test=t,r.consequent=this.parseMaybeAssign(),this.expect(d.colon),r.alternate=this.parseMaybeAssign(e),this.finishNode(r,"ConditionalExpression")}return t}parseExprOps(t,e){const s=this.state.start,i=this.state.startLoc,r=this.state.potentialArrowAt,n=this.parseMaybeUnary(e);return"ArrowFunctionExpression"===n.type&&n.start===r||this.checkExpressionErrors(e,!1)?n:this.parseExprOp(n,s,i,-1,t)}parseExprOp(t,e,s,i,r){let n=this.state.type.binop;if(null!=n&&(!r||!this.match(d._in))&&n>i){const a=this.state.value;if("|>"===a&&this.state.inFSharpPipelineDirectBody)return t;const o=this.startNodeAt(e,s);o.left=t,o.operator=a,"**"!==a||"UnaryExpression"!==t.type||!this.options.createParenthesizedExpressions&&t.extra&&t.extra.parenthesized||this.raise(t.argument.start,ut.UnexpectedTokenUnaryExponentiation);const c=this.state.type,h=c===d.logicalOR||c===d.logicalAND,l=c===d.nullishCoalescing;if(c===d.pipeline?(this.expectPlugin("pipelineOperator"),this.state.inPipeline=!0,this.checkPipelineAtInfixOperator(t,e)):l&&(n=d.logicalAND.binop),this.next(),c===d.pipeline&&"minimal"===this.getPluginOption("pipelineOperator","proposal")&&this.match(d.name)&&"await"===this.state.value&&this.prodParam.hasAwait)throw this.raise(this.state.start,ut.UnexpectedAwaitAfterPipelineBody);o.right=this.parseExprOpRightExpr(c,n,r),this.finishNode(o,h||l?"LogicalExpression":"BinaryExpression");const p=this.state.type;if(l&&(p===d.logicalOR||p===d.logicalAND)||h&&p===d.nullishCoalescing)throw this.raise(this.state.start,ut.MixingCoalesceWithLogical);return this.parseExprOp(o,e,s,i,r)}return t}parseExprOpRightExpr(t,e,s){const i=this.state.start,r=this.state.startLoc;switch(t){case d.pipeline:switch(this.getPluginOption("pipelineOperator","proposal")){case"smart":return this.withTopicPermittingContext(()=>this.parseSmartPipelineBody(this.parseExprOpBaseRightExpr(t,e,s),i,r));case"fsharp":return this.withSoloAwaitPermittingContext(()=>this.parseFSharpPipelineBody(e,s))}default:return this.parseExprOpBaseRightExpr(t,e,s)}}parseExprOpBaseRightExpr(t,e,s){const i=this.state.start,r=this.state.startLoc;return this.parseExprOp(this.parseMaybeUnary(),i,r,t.rightAssociative?e-1:e,s)}parseMaybeUnary(t){if(this.isContextual("await")&&this.isAwaitAllowed())return this.parseAwait();if(this.state.type.prefix){const e=this.startNode(),s=this.match(d.incDec);if(e.operator=this.state.value,e.prefix=!0,"throw"===e.operator&&this.expectPlugin("throwExpressions"),this.next(),e.argument=this.parseMaybeUnary(),this.checkExpressionErrors(t,!0),s)this.checkLVal(e.argument,void 0,void 0,"prefix operation");else if(this.state.strict&&"delete"===e.operator){const t=e.argument;"Identifier"===t.type?this.raise(e.start,ut.StrictDelete):"MemberExpression"!==t.type&&"OptionalMemberExpression"!==t.type||"PrivateName"!==t.property.type||this.raise(e.start,ut.DeletePrivateField)}return this.finishNode(e,s?"UpdateExpression":"UnaryExpression")}const e=this.state.start,s=this.state.startLoc;let i=this.parseExprSubscripts(t);if(this.checkExpressionErrors(t,!1))return i;while(this.state.type.postfix&&!this.canInsertSemicolon()){const t=this.startNodeAt(e,s);t.operator=this.state.value,t.prefix=!1,t.argument=i,this.checkLVal(i,void 0,void 0,"postfix operation"),this.next(),i=this.finishNode(t,"UpdateExpression")}return i}parseExprSubscripts(t){const e=this.state.start,s=this.state.startLoc,i=this.state.potentialArrowAt,r=this.parseExprAtom(t);return"ArrowFunctionExpression"===r.type&&r.start===i?r:this.parseSubscripts(r,e,s)}parseSubscripts(t,e,s,i){const r={optionalChainMember:!1,maybeAsyncArrow:this.atPossibleAsyncArrow(t),stop:!1};do{const n=this.state.maybeInAsyncArrowHead;r.maybeAsyncArrow&&(this.state.maybeInAsyncArrowHead=!0),t=this.parseSubscript(t,e,s,i,r),r.maybeAsyncArrow=!1,this.state.maybeInAsyncArrowHead=n}while(!r.stop);return t}parseSubscript(t,e,s,i,r){if(!i&&this.eat(d.doubleColon)){const n=this.startNodeAt(e,s);return n.object=t,n.callee=this.parseNoCallExpr(),r.stop=!0,this.parseSubscripts(this.finishNode(n,"BindExpression"),e,s,i)}let n=!1;if(this.match(d.questionDot)){if(r.optionalChainMember=n=!0,i&&40===this.lookaheadCharCode())return r.stop=!0,t;this.next()}const a=this.eat(d.bracketL);if(n&&!this.match(d.parenL)&&!this.match(d.backQuote)||a||this.eat(d.dot)){const i=this.startNodeAt(e,s);return i.object=t,i.property=a?this.parseExpression():this.parseMaybePrivateName(!0),i.computed=a,"PrivateName"===i.property.type&&("Super"===i.object.type&&this.raise(e,ut.SuperPrivateField),this.classScope.usePrivateName(i.property.id.name,i.property.start)),a&&this.expect(d.bracketR),r.optionalChainMember?(i.optional=n,this.finishNode(i,"OptionalMemberExpression")):this.finishNode(i,"MemberExpression")}if(!i&&this.match(d.parenL)){const i=this.state.maybeInArrowParameters,a=this.state.yieldPos,o=this.state.awaitPos;this.state.maybeInArrowParameters=!0,this.state.yieldPos=-1,this.state.awaitPos=-1,this.next();let c=this.startNodeAt(e,s);return c.callee=t,r.optionalChainMember&&(c.optional=n),c.arguments=n?this.parseCallExpressionArguments(d.parenR,!1):this.parseCallExpressionArguments(d.parenR,r.maybeAsyncArrow,"Import"===t.type,"Super"!==t.type,c),this.finishCallExpression(c,r.optionalChainMember),r.maybeAsyncArrow&&this.shouldParseAsyncArrow()&&!n?(r.stop=!0,c=this.parseAsyncArrowFromCallExpression(this.startNodeAt(e,s),c),this.checkYieldAwaitInDefaultParams(),this.state.yieldPos=a,this.state.awaitPos=o):(this.toReferencedListDeep(c.arguments),-1!==a&&(this.state.yieldPos=a),(this.isAwaitAllowed()||i)&&-1===o||(this.state.awaitPos=o)),this.state.maybeInArrowParameters=i,c}return this.match(d.backQuote)?this.parseTaggedTemplateExpression(e,s,t,r):(r.stop=!0,t)}parseTaggedTemplateExpression(t,e,s,i,r){const n=this.startNodeAt(t,e);return n.tag=s,n.quasi=this.parseTemplate(!0),r&&(n.typeParameters=r),i.optionalChainMember&&this.raise(t,ut.OptionalChainingNoTemplate),this.finishNode(n,"TaggedTemplateExpression")}atPossibleAsyncArrow(t){return"Identifier"===t.type&&"async"===t.name&&this.state.lastTokEnd===t.end&&!this.canInsertSemicolon()&&t.end-t.start===5&&t.start===this.state.potentialArrowAt}finishCallExpression(t,e){if("Import"===t.callee.type)if(2===t.arguments.length&&this.expectPlugin("moduleAttributes"),0===t.arguments.length||t.arguments.length>2)this.raise(t.start,ut.ImportCallArity,this.hasPlugin("moduleAttributes")?"one or two arguments":"one argument");else for(let s=0,i=t.arguments;s1?(i=this.startNodeAt(c,h),i.expressions=l,this.finishNodeAt(i,"SequenceExpression",g,x)):i=l[0],!this.options.createParenthesizedExpressions)return this.addExtra(i,"parenthesized",!0),this.addExtra(i,"parenStart",e),i;const v=this.startNodeAt(e,s);return v.expression=i,this.finishNode(v,"ParenthesizedExpression"),v}shouldParseArrow(){return!this.canInsertSemicolon()}parseArrow(t){if(this.eat(d.arrow))return t}parseParenItem(t,e,s){return t}parseNew(){const t=this.startNode();let e=this.startNode();if(this.next(),e=this.createIdentifier(e,"new"),this.eat(d.dot)){const s=this.parseMetaProperty(t,e,"target");if(!this.scope.inNonArrowFunction&&!this.scope.inClass){let t=ut.UnexpectedNewTarget;this.hasPlugin("classProperties")&&(t+=" or class properties"),this.raise(s.start,t)}return s}return t.callee=this.parseNoCallExpr(),"Import"===t.callee.type?this.raise(t.callee.start,ut.ImportCallNotNewExpression):"OptionalMemberExpression"===t.callee.type||"OptionalCallExpression"===t.callee.type?this.raise(this.state.lastTokEnd,ut.OptionalChainingNoNew):this.eat(d.questionDot)&&this.raise(this.state.start,ut.OptionalChainingNoNew),this.parseNewArguments(t),this.finishNode(t,"NewExpression")}parseNewArguments(t){if(this.eat(d.parenL)){const e=this.parseExprList(d.parenR);this.toReferencedList(e),t.arguments=e}else t.arguments=[]}parseTemplateElement(t){const e=this.startNode();return null===this.state.value&&(t||this.raise(this.state.start+1,ut.InvalidEscapeSequenceTemplate)),e.value={raw:this.input.slice(this.state.start,this.state.end).replace(/\r\n?/g,"\n"),cooked:this.state.value},this.next(),e.tail=this.match(d.backQuote),this.finishNode(e,"TemplateElement")}parseTemplate(t){const e=this.startNode();this.next(),e.expressions=[];let s=this.parseTemplateElement(t);e.quasis=[s];while(!s.tail)this.expect(d.dollarBraceL),e.expressions.push(this.parseExpression()),this.expect(d.braceR),e.quasis.push(s=this.parseTemplateElement(t));return this.next(),this.finishNode(e,"TemplateLiteral")}parseObj(t,e,s,i){const r=Object.create(null);let n=!0;const a=this.startNode();a.properties=[],this.next();while(!this.eat(t)){if(n)n=!1;else if(this.expect(d.comma),this.match(t)){this.addExtra(a,"trailingComma",this.state.lastTokStart),this.next();break}const o=this.parseObjectMember(e,i);e||this.checkProto(o,s,r,i),s&&"ObjectProperty"!==o.type&&"SpreadElement"!==o.type&&this.raise(o.start,ut.InvalidRecordProperty),o.shorthand&&this.addExtra(o,"shorthand",!0),a.properties.push(o)}let o="ObjectExpression";return e?o="ObjectPattern":s&&(o="RecordExpression"),this.finishNode(a,o)}isAsyncProp(t){return!t.computed&&"Identifier"===t.key.type&&"async"===t.key.name&&(this.isLiteralPropertyName()||this.match(d.bracketL)||this.match(d.star))&&!this.hasPrecedingLineBreak()}parseObjectMember(t,e){let s=[];if(this.match(d.at)){this.hasPlugin("decorators")&&this.raise(this.state.start,ut.UnsupportedPropertyDecorator);while(this.match(d.at))s.push(this.parseDecorator())}const i=this.startNode();let r,n,a=!1,o=!1;if(this.match(d.ellipsis))return s.length&&this.unexpected(),t?(this.next(),i.argument=this.parseIdentifier(),this.checkCommaAfterRest(125),this.finishNode(i,"RestElement")):this.parseSpread();s.length&&(i.decorators=s,s=[]),i.method=!1,(t||e)&&(r=this.state.start,n=this.state.startLoc),t||(a=this.eat(d.star));const c=this.state.containsEsc;return this.parsePropertyName(i,!1),t||c||a||!this.isAsyncProp(i)?o=!1:(o=!0,a=this.eat(d.star),this.parsePropertyName(i,!1)),this.parseObjPropValue(i,r,n,a,o,t,e,c),i}isGetterOrSetterMethod(t,e){return!e&&!t.computed&&"Identifier"===t.key.type&&("get"===t.key.name||"set"===t.key.name)&&(this.isLiteralPropertyName()||this.match(d.bracketL))}getGetterSetterExpectedParamCount(t){return"get"===t.kind?0:1}checkGetterSetterParams(t){const e=this.getGetterSetterExpectedParamCount(t),s=t.start;t.params.length!==e&&("get"===t.kind?this.raise(s,ut.BadGetterArity):this.raise(s,ut.BadSetterArity)),"set"===t.kind&&"RestElement"===t.params[t.params.length-1].type&&this.raise(s,ut.BadSetterRestParameter)}parseObjectMethod(t,e,s,i,r){return s||e||this.match(d.parenL)?(i&&this.unexpected(),t.kind="method",t.method=!0,this.parseMethod(t,e,s,!1,!1,"ObjectMethod")):!r&&this.isGetterOrSetterMethod(t,i)?((e||s)&&this.unexpected(),t.kind=t.key.name,this.parsePropertyName(t,!1),this.parseMethod(t,!1,!1,!1,!1,"ObjectMethod"),this.checkGetterSetterParams(t),t):void 0}parseObjectProperty(t,e,s,i,r){return t.shorthand=!1,this.eat(d.colon)?(t.value=i?this.parseMaybeDefault(this.state.start,this.state.startLoc):this.parseMaybeAssign(!1,r),this.finishNode(t,"ObjectProperty")):t.computed||"Identifier"!==t.key.type?void 0:(this.checkReservedWord(t.key.name,t.key.start,!0,!0),i?t.value=this.parseMaybeDefault(e,s,t.key.__clone()):this.match(d.eq)&&r?(-1===r.shorthandAssign&&(r.shorthandAssign=this.state.start),t.value=this.parseMaybeDefault(e,s,t.key.__clone())):t.value=t.key.__clone(),t.shorthand=!0,this.finishNode(t,"ObjectProperty"))}parseObjPropValue(t,e,s,i,r,n,a,o){const c=this.parseObjectMethod(t,i,r,n,o)||this.parseObjectProperty(t,e,s,n,a);return c||this.unexpected(),c}parsePropertyName(t,e){if(this.eat(d.bracketL))t.computed=!0,t.key=this.parseMaybeAssign(),this.expect(d.bracketR);else{const s=this.state.inPropertyName;this.state.inPropertyName=!0,t.key=this.match(d.num)||this.match(d.string)||this.match(d.bigint)?this.parseExprAtom():this.parseMaybePrivateName(e),"PrivateName"!==t.key.type&&(t.computed=!1),this.state.inPropertyName=s}return t.key}initFunction(t,e){t.id=null,t.generator=!1,t.async=!!e}parseMethod(t,e,s,i,r,n,a=!1){const o=this.state.yieldPos,c=this.state.awaitPos;this.state.yieldPos=-1,this.state.awaitPos=-1,this.initFunction(t,s),t.generator=!!e;const h=i;return this.scope.enter(y|b|(a?w:0)|(r?v:0)),this.prodParam.enter(he(s,t.generator)),this.parseFunctionParams(t,h),this.parseFunctionBodyAndFinish(t,n,!0),this.prodParam.exit(),this.scope.exit(),this.state.yieldPos=o,this.state.awaitPos=c,t}parseArrowExpression(t,e,s,i){this.scope.enter(y|g),this.prodParam.enter(he(s,!1)),this.initFunction(t,s);const r=this.state.maybeInArrowParameters,n=this.state.yieldPos,a=this.state.awaitPos;return e&&(this.state.maybeInArrowParameters=!0,this.setArrowFunctionParameters(t,e,i)),this.state.maybeInArrowParameters=!1,this.state.yieldPos=-1,this.state.awaitPos=-1,this.parseFunctionBody(t,!0),this.prodParam.exit(),this.scope.exit(),this.state.maybeInArrowParameters=r,this.state.yieldPos=n,this.state.awaitPos=a,this.finishNode(t,"ArrowFunctionExpression")}setArrowFunctionParameters(t,e,s){t.params=this.toAssignableList(e,s)}parseFunctionBodyAndFinish(t,e,s=!1){this.parseFunctionBody(t,!1,s),this.finishNode(t,e)}parseFunctionBody(t,e,s=!1){const i=e&&!this.match(d.braceL),r=this.state.inParameters;if(this.state.inParameters=!1,i)t.body=this.parseMaybeAssign(),this.checkParams(t,!1,e,!1);else{const i=this.state.strict,r=this.state.labels;this.state.labels=[],this.prodParam.enter(this.prodParam.currentFlags()|oe),t.body=this.parseBlock(!0,!1,r=>{const n=!this.isSimpleParamList(t.params);if(r&&n){const e="method"!==t.kind&&"constructor"!==t.kind||!t.key?t.start:t.key.end;this.raise(e,ut.IllegalLanguageModeDirective)}const a=!i&&this.state.strict;this.checkParams(t,!this.state.strict&&!e&&!s&&!n,e,a),this.state.strict&&t.id&&this.checkLVal(t.id,H,void 0,"function name",void 0,a)}),this.prodParam.exit(),this.state.labels=r}this.state.inParameters=r}isSimpleParamList(t){for(let e=0,s=t.length;e=1}topicReferenceWasUsedInCurrentTopicContext(){return null!=this.state.topicContext.maxTopicIndex&&this.state.topicContext.maxTopicIndex>=0}parseFSharpPipelineBody(t,e){const s=this.state.start,i=this.state.startLoc;this.state.potentialArrowAt=this.state.start;const r=this.state.inFSharpPipelineDirectBody;this.state.inFSharpPipelineDirectBody=!0;const n=this.parseExprOp(this.parseMaybeUnary(),s,i,t,e);return this.state.inFSharpPipelineDirectBody=r,n}}const Ue={kind:"loop"},qe={kind:"switch"},Ve=0,He=1,ze=2,We=4;class Ke extends Be{parseTopLevel(t,e){if(e.sourceType=this.options.sourceType,e.interpreter=this.parseInterpreterDirective(),this.parseBlockBody(e,!0,!0,d.eof),this.inModule&&!this.options.allowUndeclaredExports&&this.scope.undefinedExports.size>0)for(let s=0,i=Array.from(this.scope.undefinedExports);sthis.parseStatement("do")),this.state.labels.pop(),this.expect(d._while),t.test=this.parseHeaderExpression(),this.eat(d.semi),this.finishNode(t,"DoWhileStatement")}parseForStatement(t){this.next(),this.state.labels.push(Ue);let e=-1;if(this.isAwaitAllowed()&&this.eatContextual("await")&&(e=this.state.lastTokStart),this.scope.enter(f),this.expect(d.parenL),this.match(d.semi))return e>-1&&this.unexpected(e),this.parseFor(t,null);const s=this.isLet();if(this.match(d._var)||this.match(d._const)||s){const i=this.startNode(),r=s?"let":this.state.value;return this.next(),this.parseVar(i,!0,r),this.finishNode(i,"VariableDeclaration"),(this.match(d._in)||this.isContextual("of"))&&1===i.declarations.length?this.parseForIn(t,i,e):(e>-1&&this.unexpected(e),this.parseFor(t,i))}const i=new Le,r=this.parseExpression(!0,i);if(this.match(d._in)||this.isContextual("of")){this.toAssignable(r);const s=this.isContextual("of")?"for-of statement":"for-in statement";return this.checkLVal(r,void 0,void 0,s),this.parseForIn(t,r,e)}return this.checkExpressionErrors(i,!0),e>-1&&this.unexpected(e),this.parseFor(t,r)}parseFunctionStatement(t,e,s){return this.next(),this.parseFunction(t,He|(s?0:ze),e)}parseIfStatement(t){return this.next(),t.test=this.parseHeaderExpression(),t.consequent=this.parseStatement("if"),t.alternate=this.eat(d._else)?this.parseStatement("if"):null,this.finishNode(t,"IfStatement")}parseReturnStatement(t){return this.prodParam.hasReturn||this.options.allowReturnOutsideFunction||this.raise(this.state.start,ut.IllegalReturn),this.next(),this.isLineTerminator()?t.argument=null:(t.argument=this.parseExpression(),this.semicolon()),this.finishNode(t,"ReturnStatement")}parseSwitchStatement(t){this.next(),t.discriminant=this.parseHeaderExpression();const e=t.cases=[];let s,i;for(this.expect(d.braceL),this.state.labels.push(qe),this.scope.enter(f);!this.match(d.braceR);)if(this.match(d._case)||this.match(d._default)){const t=this.match(d._case);s&&this.finishNode(s,"SwitchCase"),e.push(s=this.startNode()),s.consequent=[],this.next(),t?s.test=this.parseExpression():(i&&this.raise(this.state.lastTokStart,ut.MultipleDefaultsInSwitch),i=!0,s.test=null),this.expect(d.colon)}else s?s.consequent.push(this.parseStatement(null)):this.unexpected();return this.scope.exit(),s&&this.finishNode(s,"SwitchCase"),this.next(),this.state.labels.pop(),this.finishNode(t,"SwitchStatement")}parseThrowStatement(t){return this.next(),et.test(this.input.slice(this.state.lastTokEnd,this.state.start))&&this.raise(this.state.lastTokEnd,ut.NewlineAfterThrow),t.argument=this.parseExpression(),this.semicolon(),this.finishNode(t,"ThrowStatement")}parseTryStatement(t){if(this.next(),t.block=this.parseBlock(),t.handler=null,this.match(d._catch)){const e=this.startNode();if(this.next(),this.match(d.parenL)){this.expect(d.parenL),e.param=this.parseBindingAtom();const t="Identifier"===e.param.type;this.scope.enter(t?x:0),this.checkLVal(e.param,_,null,"catch clause"),this.expect(d.parenR)}else e.param=null,this.scope.enter(f);e.body=this.withTopicForbiddingContext(()=>this.parseBlock(!1,!1)),this.scope.exit(),t.handler=this.finishNode(e,"CatchClause")}return t.finalizer=this.eat(d._finally)?this.parseBlock():null,t.handler||t.finalizer||this.raise(t.start,ut.NoCatchOrFinally),this.finishNode(t,"TryStatement")}parseVarStatement(t,e){return this.next(),this.parseVar(t,!1,e),this.semicolon(),this.finishNode(t,"VariableDeclaration")}parseWhileStatement(t){return this.next(),t.test=this.parseHeaderExpression(),this.state.labels.push(Ue),t.body=this.withTopicForbiddingContext(()=>this.parseStatement("while")),this.state.labels.pop(),this.finishNode(t,"WhileStatement")}parseWithStatement(t){return this.state.strict&&this.raise(this.state.start,ut.StrictWith),this.next(),t.object=this.parseHeaderExpression(),t.body=this.withTopicForbiddingContext(()=>this.parseStatement("with")),this.finishNode(t,"WithStatement")}parseEmptyStatement(t){return this.next(),this.finishNode(t,"EmptyStatement")}parseLabeledStatement(t,e,s,i){for(let n=0,a=this.state.labels;n=0;n--){const e=this.state.labels[n];if(e.statementStart!==t.start)break;e.statementStart=this.state.start,e.kind=r}return this.state.labels.push({name:e,kind:r,statementStart:this.state.start}),t.body=this.parseStatement(i?-1===i.indexOf("label")?i+"label":i:"label"),this.state.labels.pop(),t.label=s,this.finishNode(t,"LabeledStatement")}parseExpressionStatement(t,e){return t.expression=e,this.semicolon(),this.finishNode(t,"ExpressionStatement")}parseBlock(t=!1,e=!0,s){const i=this.startNode();return this.expect(d.braceL),e&&this.scope.enter(f),this.parseBlockBody(i,t,!1,d.braceR,s),e&&this.scope.exit(),this.finishNode(i,"BlockStatement")}isValidDirective(t){return"ExpressionStatement"===t.type&&"StringLiteral"===t.expression.type&&!t.expression.extra.parenthesized}parseBlockBody(t,e,s,i,r){const n=t.body=[],a=t.directives=[];this.parseBlockOrModuleBlockBody(n,e?a:void 0,s,i,r)}parseBlockOrModuleBlockBody(t,e,s,i,r){const n=[],a=this.state.strict;let o=!1,c=!1;while(!this.match(i)){!c&&this.state.octalPositions.length&&n.push(...this.state.octalPositions);const i=this.parseStatement(null,s);if(e&&!c&&this.isValidDirective(i)){const t=this.stmtToDirective(i);e.push(t),o||"use strict"!==t.value.value||(o=!0,this.setStrict(!0))}else c=!0,t.push(i)}if(this.state.strict&&n.length)for(let h=0;hthis.parseStatement("for")),this.scope.exit(),this.state.labels.pop(),this.finishNode(t,"ForStatement")}parseForIn(t,e,s){const i=this.match(d._in);return this.next(),i?s>-1&&this.unexpected(s):t.await=s>-1,"VariableDeclaration"!==e.type||null==e.declarations[0].init||i&&!this.state.strict&&"var"===e.kind&&"Identifier"===e.declarations[0].id.type?"AssignmentPattern"===e.type&&this.raise(e.start,ut.InvalidLhs,"for-loop"):this.raise(e.start,ut.ForInOfLoopInitializer,i?"for-in":"for-of"),t.left=e,t.right=i?this.parseExpression():this.parseMaybeAssign(),this.expect(d.parenR),t.body=this.withTopicForbiddingContext(()=>this.parseStatement("for")),this.scope.exit(),this.state.labels.pop(),this.finishNode(t,i?"ForInStatement":"ForOfStatement")}parseVar(t,e,s){const i=t.declarations=[],r=this.hasPlugin("typescript");for(t.kind=s;;){const t=this.startNode();if(this.parseVarId(t,s),this.eat(d.eq)?t.init=this.parseMaybeAssign(e):("const"!==s||this.match(d._in)||this.isContextual("of")?"Identifier"===t.id.type||e&&(this.match(d._in)||this.isContextual("of"))||this.raise(this.state.lastTokEnd,ut.DeclarationMissingInitializer,"Complex binding patterns"):r||this.unexpected(),t.init=null),i.push(this.finishNode(t,"VariableDeclarator")),!this.eat(d.comma))break}return t}parseVarId(t,e){t.id=this.parseBindingAtom(),this.checkLVal(t.id,"var"===e?R:_,void 0,"variable declaration","var"!==e)}parseFunction(t,e=Ve,s=!1){const i=e&He,r=e&ze,n=!!i&&!(e&We);this.initFunction(t,s),this.match(d.star)&&r&&this.raise(this.state.start,ut.GeneratorInSingleStatementContext),t.generator=this.eat(d.star),i&&(t.id=this.parseFunctionId(n));const a=this.state.maybeInArrowParameters,o=this.state.yieldPos,c=this.state.awaitPos;return this.state.maybeInArrowParameters=!1,this.state.yieldPos=-1,this.state.awaitPos=-1,this.scope.enter(y),this.prodParam.enter(he(s,t.generator)),i||(t.id=this.parseFunctionId()),this.parseFunctionParams(t),this.withTopicForbiddingContext(()=>{this.parseFunctionBodyAndFinish(t,i?"FunctionDeclaration":"FunctionExpression")}),this.prodParam.exit(),this.scope.exit(),i&&!r&&this.registerFunctionStatementId(t),this.state.maybeInArrowParameters=a,this.state.yieldPos=o,this.state.awaitPos=c,t}parseFunctionId(t){return t||this.match(d.name)?this.parseIdentifier():null}parseFunctionParams(t,e){const s=this.state.inParameters;this.state.inParameters=!0,this.expect(d.parenL),t.params=this.parseBindingList(d.parenR,41,!1,e),this.state.inParameters=s,this.checkYieldAwaitInDefaultParams()}registerFunctionStatementId(t){t.id&&this.scope.declareName(t.id.name,this.state.strict||t.generator||t.async?this.scope.treatFunctionsAsVar?R:_:j,t.id.start)}parseClass(t,e,s){this.next(),this.takeDecorators(t);const i=this.state.strict;return this.state.strict=!0,this.parseClassId(t,e,s),this.parseClassSuper(t),t.body=this.parseClassBody(!!t.superClass,i),this.state.strict=i,this.finishNode(t,e?"ClassDeclaration":"ClassExpression")}isClassProperty(){return this.match(d.eq)||this.match(d.semi)||this.match(d.braceR)}isClassMethod(){return this.match(d.parenL)}isNonstaticConstructor(t){return!t.computed&&!t.static&&("constructor"===t.key.name||"constructor"===t.key.value)}parseClassBody(t,e){this.classScope.enter();const s={hadConstructor:!1};let i=[];const r=this.startNode();if(r.body=[],this.expect(d.braceL),this.withTopicForbiddingContext(()=>{while(!this.match(d.braceR)){if(this.eat(d.semi)){if(i.length>0)throw this.raise(this.state.lastTokEnd,ut.DecoratorSemicolon);continue}if(this.match(d.at)){i.push(this.parseDecorator());continue}const e=this.startNode();i.length&&(e.decorators=i,this.resetStartLocationFromNode(e,i[0]),i=[]),this.parseClassMember(r,e,s,t),"constructor"===e.kind&&e.decorators&&e.decorators.length>0&&this.raise(e.start,ut.DecoratorConstructor)}}),e||(this.state.strict=!1),this.next(),i.length)throw this.raise(this.state.start,ut.TrailingDecorator);return this.classScope.exit(),this.finishNode(r,"ClassBody")}parseClassMemberFromModifier(t,e){const s=this.state.containsEsc,i=this.parseIdentifier(!0);if(this.isClassMethod()){const s=e;return s.kind="method",s.computed=!1,s.key=i,s.static=!1,this.pushClassMethod(t,s,!1,!1,!1,!1),!0}if(this.isClassProperty()){const s=e;return s.computed=!1,s.key=i,s.static=!1,t.body.push(this.parseClassProperty(s)),!0}if(s)throw this.unexpected();return!1}parseClassMember(t,e,s,i){const r=this.isContextual("static");r&&this.parseClassMemberFromModifier(t,e)||this.parseClassMemberWithIsStatic(t,e,s,r,i)}parseClassMemberWithIsStatic(t,e,s,i,r){const n=e,a=e,o=e,c=e,h=n,l=n;if(e.static=i,this.eat(d.star))return h.kind="method",this.parseClassPropertyName(h),"PrivateName"===h.key.type?void this.pushClassPrivateMethod(t,a,!0,!1):(this.isNonstaticConstructor(n)&&this.raise(n.key.start,ut.ConstructorIsGenerator),void this.pushClassMethod(t,n,!0,!1,!1,!1));const p=this.state.containsEsc,u=this.parseClassPropertyName(e),f="PrivateName"===u.type,m="Identifier"===u.type,y=this.state.start;if(this.parsePostMemberNameModifiers(l),this.isClassMethod()){if(h.kind="method",f)return void this.pushClassPrivateMethod(t,a,!1,!1);const e=this.isNonstaticConstructor(n);let i=!1;e&&(n.kind="constructor",s.hadConstructor&&!this.hasPlugin("typescript")&&this.raise(u.start,ut.DuplicateConstructor),s.hadConstructor=!0,i=r),this.pushClassMethod(t,n,!1,!1,e,i)}else if(this.isClassProperty())f?this.pushClassPrivateProperty(t,c):this.pushClassProperty(t,o);else if(!m||"async"!==u.name||p||this.isLineTerminator())!m||"get"!==u.name&&"set"!==u.name||p||this.match(d.star)&&this.isLineTerminator()?this.isLineTerminator()?f?this.pushClassPrivateProperty(t,c):this.pushClassProperty(t,o):this.unexpected():(h.kind=u.name,this.parseClassPropertyName(n),"PrivateName"===h.key.type?this.pushClassPrivateMethod(t,a,!1,!1):(this.isNonstaticConstructor(n)&&this.raise(n.key.start,ut.ConstructorIsAccessor),this.pushClassMethod(t,n,!1,!1,!1,!1)),this.checkGetterSetterParams(n));else{const e=this.eat(d.star);l.optional&&this.unexpected(y),h.kind="method",this.parseClassPropertyName(h),this.parsePostMemberNameModifiers(l),"PrivateName"===h.key.type?this.pushClassPrivateMethod(t,a,e,!0):(this.isNonstaticConstructor(n)&&this.raise(n.key.start,ut.ConstructorIsAsync),this.pushClassMethod(t,n,e,!0,!1,!1))}}parseClassPropertyName(t){const e=this.parsePropertyName(t,!0);return t.computed||!t.static||"prototype"!==e.name&&"prototype"!==e.value||this.raise(e.start,ut.StaticPrototype),"PrivateName"===e.type&&"constructor"===e.id.name&&this.raise(e.start,ut.ConstructorClassPrivateField),e}pushClassProperty(t,e){e.computed||"constructor"!==e.key.name&&"constructor"!==e.key.value||this.raise(e.key.start,ut.ConstructorClassField),t.body.push(this.parseClassProperty(e))}pushClassPrivateProperty(t,e){this.expectPlugin("classPrivateProperties",e.key.start);const s=this.parseClassPrivateProperty(e);t.body.push(s),this.classScope.declarePrivateName(s.key.id.name,tt,s.key.start)}pushClassMethod(t,e,s,i,r,n){t.body.push(this.parseMethod(e,s,i,r,n,"ClassMethod",!0))}pushClassPrivateMethod(t,e,s,i){this.expectPlugin("classPrivateMethods",e.key.start);const r=this.parseMethod(e,s,i,!1,!1,"ClassPrivateMethod",!0);t.body.push(r);const n="get"===r.kind?r.static?Y:Q:"set"===r.kind?r.static?J:Z:tt;this.classScope.declarePrivateName(r.key.id.name,n,r.key.start)}parsePostMemberNameModifiers(t){}parseAccessModifier(){}parseClassPrivateProperty(t){return this.scope.enter(w|b),this.prodParam.enter(re),t.value=this.eat(d.eq)?this.parseMaybeAssign():null,this.semicolon(),this.prodParam.exit(),this.scope.exit(),this.finishNode(t,"ClassPrivateProperty")}parseClassProperty(t){return t.typeAnnotation||this.expectPlugin("classProperties"),this.scope.enter(w|b),this.prodParam.enter(re),this.match(d.eq)?(this.expectPlugin("classProperties"),this.next(),t.value=this.parseMaybeAssign()):t.value=null,this.semicolon(),this.prodParam.exit(),this.scope.exit(),this.finishNode(t,"ClassProperty")}parseClassId(t,e,s,i=L){this.match(d.name)?(t.id=this.parseIdentifier(),e&&this.checkLVal(t.id,i,void 0,"class name")):s||!e?t.id=null:this.unexpected(null,ut.MissingClassName)}parseClassSuper(t){t.superClass=this.eat(d._extends)?this.parseExprSubscripts():null}parseExport(t){const e=this.maybeParseExportDefaultSpecifier(t),s=!e||this.eat(d.comma),i=s&&this.eatExportStar(t),r=i&&this.maybeParseExportNamespaceSpecifier(t),n=s&&(!r||this.eat(d.comma)),a=e||i;if(i&&!r)return e&&this.unexpected(),this.parseExportFrom(t,!0),this.finishNode(t,"ExportAllDeclaration");const o=this.maybeParseExportNamedSpecifiers(t);if(e&&s&&!i&&!o||r&&n&&!o)throw this.unexpected(null,d.braceL);let c;if(a||o?(c=!1,this.parseExportFrom(t,a)):c=this.maybeParseExportDeclaration(t),a||o||c)return this.checkExport(t,!0,!1,!!t.source),this.finishNode(t,"ExportNamedDeclaration");if(this.eat(d._default))return t.declaration=this.parseExportDefaultExpression(),this.checkExport(t,!0,!0),this.finishNode(t,"ExportDefaultDeclaration");throw this.unexpected(null,d.braceL)}eatExportStar(t){return this.eat(d.star)}maybeParseExportDefaultSpecifier(t){if(this.isExportDefaultSpecifier()){this.expectPlugin("exportDefaultFrom");const e=this.startNode();return e.exported=this.parseIdentifier(!0),t.specifiers=[this.finishNode(e,"ExportDefaultSpecifier")],!0}return!1}maybeParseExportNamespaceSpecifier(t){if(this.isContextual("as")){t.specifiers||(t.specifiers=[]);const e=this.startNodeAt(this.state.lastTokStart,this.state.lastTokStartLoc);return this.next(),e.exported=this.parseIdentifier(!0),t.specifiers.push(this.finishNode(e,"ExportNamespaceSpecifier")),!0}return!1}maybeParseExportNamedSpecifiers(t){return!!this.match(d.braceL)&&(t.specifiers||(t.specifiers=[]),t.specifiers.push(...this.parseExportSpecifiers()),t.source=null,t.declaration=null,!0)}maybeParseExportDeclaration(t){if(this.shouldParseExportDeclaration()){if(this.isContextual("async")){const t=this.nextTokenStart();this.isUnparsedContextual(t,"function")||this.unexpected(t,d._function)}return t.specifiers=[],t.source=null,t.declaration=this.parseExportDeclaration(t),!0}return!1}isAsyncFunction(){if(!this.isContextual("async"))return!1;const t=this.nextTokenStart();return!et.test(this.input.slice(this.state.pos,t))&&this.isUnparsedContextual(t,"function")}parseExportDefaultExpression(){const t=this.startNode(),e=this.isAsyncFunction();if(this.match(d._function)||e)return this.next(),e&&this.next(),this.parseFunction(t,He|We,e);if(this.match(d._class))return this.parseClass(t,!0,!0);if(this.match(d.at))return this.hasPlugin("decorators")&&this.getPluginOption("decorators","decoratorsBeforeExport")&&this.raise(this.state.start,ut.DecoratorBeforeExport),this.parseDecorators(!1),this.parseClass(t,!0,!0);if(this.match(d._const)||this.match(d._var)||this.isLet())throw this.raise(this.state.start,ut.UnsupportedDefaultExport);{const t=this.parseMaybeAssign();return this.semicolon(),t}}parseExportDeclaration(t){return this.parseStatement(null)}isExportDefaultSpecifier(){if(this.match(d.name)){const t=this.state.value;if("async"===t||"let"===t)return!1;if(("type"===t||"interface"===t)&&!this.state.containsEsc){const t=this.lookahead();if(t.type===d.name&&"from"!==t.value||t.type===d.braceL)return this.expectOnePlugin(["flow","typescript"]),!1}}else if(!this.match(d._default))return!1;const t=this.nextTokenStart(),e=this.isUnparsedContextual(t,"from");if(44===this.input.charCodeAt(t)||this.match(d.name)&&e)return!0;if(this.match(d._default)&&e){const e=this.input.charCodeAt(this.nextTokenStartSince(t+4));return 34===e||39===e}return!1}parseExportFrom(t,e){this.eatContextual("from")?(t.source=this.parseImportSource(),this.checkExport(t)):e?this.unexpected():t.source=null,this.semicolon()}shouldParseExportDeclaration(){if(this.match(d.at)&&(this.expectOnePlugin(["decorators","decorators-legacy"]),this.hasPlugin("decorators"))){if(!this.getPluginOption("decorators","decoratorsBeforeExport"))return!0;this.unexpected(this.state.start,ut.DecoratorBeforeExport)}return"var"===this.state.type.keyword||"const"===this.state.type.keyword||"function"===this.state.type.keyword||"class"===this.state.type.keyword||this.isLet()||this.isAsyncFunction()}checkExport(t,e,s,i){if(e)if(s){if(this.checkDuplicateExports(t,"default"),this.hasPlugin("exportDefaultFrom")){var r;const e=t.declaration;"Identifier"!==e.type||"from"!==e.name||e.end-e.start!==4||(null==(r=e.extra)?void 0:r.parenthesized)||this.raise(e.start,ut.ExportDefaultFromAsIdentifier)}}else if(t.specifiers&&t.specifiers.length)for(let a=0,o=t.specifiers;a-1&&this.raise(t.start,"default"===e?ut.DuplicateDefaultExport:ut.DuplicateExport,e),this.state.exportedIdentifiers.push(e)}parseExportSpecifiers(){const t=[];let e=!0;this.expect(d.braceL);while(!this.eat(d.braceR)){if(e)e=!1;else if(this.expect(d.comma),this.eat(d.braceR))break;const s=this.startNode();s.local=this.parseIdentifier(!0),s.exported=this.eatContextual("as")?this.parseIdentifier(!0):s.local.__clone(),t.push(this.finishNode(s,"ExportSpecifier"))}return t}parseImport(t){if(t.specifiers=[],!this.match(d.string)){const e=this.maybeParseDefaultImportSpecifier(t),s=!e||this.eat(d.comma),i=s&&this.maybeParseStarImportSpecifier(t);s&&!i&&this.parseNamedImportSpecifiers(t),this.expectContextual("from")}t.source=this.parseImportSource();const e=this.maybeParseModuleAttributes();return e&&(t.attributes=e),this.semicolon(),this.finishNode(t,"ImportDeclaration")}parseImportSource(){return this.match(d.string)||this.unexpected(),this.parseExprAtom()}shouldParseDefaultImport(t){return this.match(d.name)}parseImportSpecifierLocal(t,e,s,i){e.local=this.parseIdentifier(),this.checkLVal(e.local,_,void 0,i),t.specifiers.push(this.finishNode(e,s))}maybeParseModuleAttributes(){if(!this.match(d._with)||this.hasPrecedingLineBreak())return this.hasPlugin("moduleAttributes")?[]:null;this.expectPlugin("moduleAttributes"),this.next();const t=[],e=new Set;do{const s=this.startNode();if(s.key=this.parseIdentifier(!0),"type"!==s.key.name&&this.raise(s.key.start,ut.ModuleAttributeDifferentFromType,s.key.name),e.has(s.key.name)&&this.raise(s.key.start,ut.ModuleAttributesWithDuplicateKeys,s.key.name),e.add(s.key.name),this.expect(d.colon),!this.match(d.string))throw this.unexpected(this.state.start,ut.ModuleAttributeInvalidValue);s.value=this.parseLiteral(this.state.value,"StringLiteral"),this.finishNode(s,"ImportAttribute"),t.push(s)}while(this.eat(d.comma));return t}maybeParseDefaultImportSpecifier(t){return!!this.shouldParseDefaultImport(t)&&(this.parseImportSpecifierLocal(t,this.startNode(),"ImportDefaultSpecifier","default import specifier"),!0)}maybeParseStarImportSpecifier(t){if(this.match(d.star)){const e=this.startNode();return this.next(),this.expectContextual("as"),this.parseImportSpecifierLocal(t,e,"ImportNamespaceSpecifier","import namespace specifier"),!0}return!1}parseNamedImportSpecifiers(t){let e=!0;this.expect(d.braceL);while(!this.eat(d.braceR)){if(e)e=!1;else{if(this.eat(d.colon))throw this.raise(this.state.start,ut.DestructureNamedImport);if(this.expect(d.comma),this.eat(d.braceR))break}this.parseImportSpecifier(t)}}parseImportSpecifier(t){const e=this.startNode();e.imported=this.parseIdentifier(!0),this.eatContextual("as")?e.local=this.parseIdentifier():(this.checkReservedWord(e.imported.name,e.start,!0,!0),e.local=e.imported.__clone()),this.checkLVal(e.local,_,void 0,"import specifier"),t.specifiers.push(this.finishNode(e,"ImportSpecifier"))}}class $e{constructor(){this.privateNames=new Set,this.loneAccessors=new Map,this.undefinedPrivateNames=new Map}}class Xe{constructor(t){this.stack=[],this.undefinedPrivateNames=new Map,this.raise=t}current(){return this.stack[this.stack.length-1]}enter(){this.stack.push(new $e)}exit(){const t=this.stack.pop(),e=this.current();for(let s=0,i=Array.from(t.undefinedPrivateNames);sge(t,e)),s=e.join("/");let i=ts[s];if(!i){i=Ge;for(let t=0;t=51||!i((function(){var e=[],s=e.constructor={};return s[a]=function(){return{foo:1}},1!==e[t](Boolean).foo}))}},"21a1":function(t,e,s){(function(e){(function(e,s){t.exports=s()})(0,(function(){"use strict";"undefined"!==typeof window?window:"undefined"!==typeof e||"undefined"!==typeof self&&self;function t(t,e){return e={exports:{}},t(e,e.exports),e.exports}var s=t((function(t,e){(function(e,s){t.exports=s()})(0,(function(){function t(t){var e=t&&"object"===typeof t;return e&&"[object RegExp]"!==Object.prototype.toString.call(t)&&"[object Date]"!==Object.prototype.toString.call(t)}function e(t){return Array.isArray(t)?[]:{}}function s(s,i){var r=i&&!0===i.clone;return r&&t(s)?n(e(s),s,i):s}function i(e,i,r){var a=e.slice();return i.forEach((function(i,o){"undefined"===typeof a[o]?a[o]=s(i,r):t(i)?a[o]=n(e[o],i,r):-1===e.indexOf(i)&&a.push(s(i,r))})),a}function r(e,i,r){var a={};return t(e)&&Object.keys(e).forEach((function(t){a[t]=s(e[t],r)})),Object.keys(i).forEach((function(o){t(i[o])&&e[o]?a[o]=n(e[o],i[o],r):a[o]=s(i[o],r)})),a}function n(t,e,n){var a=Array.isArray(e),o=n||{arrayMerge:i},c=o.arrayMerge||i;return a?Array.isArray(t)?c(t,e,n):s(e,n):r(t,e,n)}return n.all=function(t,e){if(!Array.isArray(t)||t.length<2)throw new Error("first argument should be an array with at least two elements");return t.reduce((function(t,s){return n(t,s,e)}))},n}))}));function i(t){return t=t||Object.create(null),{on:function(e,s){(t[e]||(t[e]=[])).push(s)},off:function(e,s){t[e]&&t[e].splice(t[e].indexOf(s)>>>0,1)},emit:function(e,s){(t[e]||[]).map((function(t){t(s)})),(t["*"]||[]).map((function(t){t(e,s)}))}}}var r=t((function(t,e){var s={svg:{name:"xmlns",uri:"http://www.w3.org/2000/svg"},xlink:{name:"xmlns:xlink",uri:"http://www.w3.org/1999/xlink"}};e.default=s,t.exports=e.default})),n=function(t){return Object.keys(t).map((function(e){var s=t[e].toString().replace(/"/g,""");return e+'="'+s+'"'})).join(" ")},a=r.svg,o=r.xlink,c={};c[a.name]=a.uri,c[o.name]=o.uri;var h,l=function(t,e){void 0===t&&(t="");var i=s(c,e||{}),r=n(i);return""+t+""},p=r.svg,u=r.xlink,d={attrs:(h={style:["position: absolute","width: 0","height: 0"].join("; "),"aria-hidden":"true"},h[p.name]=p.uri,h[u.name]=u.uri,h)},f=function(t){this.config=s(d,t||{}),this.symbols=[]};f.prototype.add=function(t){var e=this,s=e.symbols,i=this.find(t.id);return i?(s[s.indexOf(i)]=t,!1):(s.push(t),!0)},f.prototype.remove=function(t){var e=this,s=e.symbols,i=this.find(t);return!!i&&(s.splice(s.indexOf(i),1),i.destroy(),!0)},f.prototype.find=function(t){return this.symbols.filter((function(e){return e.id===t}))[0]||null},f.prototype.has=function(t){return null!==this.find(t)},f.prototype.stringify=function(){var t=this.config,e=t.attrs,s=this.symbols.map((function(t){return t.stringify()})).join("");return l(s,e)},f.prototype.toString=function(){return this.stringify()},f.prototype.destroy=function(){this.symbols.forEach((function(t){return t.destroy()}))};var m=function(t){var e=t.id,s=t.viewBox,i=t.content;this.id=e,this.viewBox=s,this.content=i};m.prototype.stringify=function(){return this.content},m.prototype.toString=function(){return this.stringify()},m.prototype.destroy=function(){var t=this;["id","viewBox","content"].forEach((function(e){return delete t[e]}))};var y=function(t){var e=!!document.importNode,s=(new DOMParser).parseFromString(t,"image/svg+xml").documentElement;return e?document.importNode(s,!0):s},g=function(t){function e(){t.apply(this,arguments)}t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e;var s={isMounted:{}};return s.isMounted.get=function(){return!!this.node},e.createFromExistingNode=function(t){return new e({id:t.getAttribute("id"),viewBox:t.getAttribute("viewBox"),content:t.outerHTML})},e.prototype.destroy=function(){this.isMounted&&this.unmount(),t.prototype.destroy.call(this)},e.prototype.mount=function(t){if(this.isMounted)return this.node;var e="string"===typeof t?document.querySelector(t):t,s=this.render();return this.node=s,e.appendChild(s),s},e.prototype.render=function(){var t=this.stringify();return y(l(t)).childNodes[0]},e.prototype.unmount=function(){this.node.parentNode.removeChild(this.node)},Object.defineProperties(e.prototype,s),e}(m),x={autoConfigure:!0,mountTo:"body",syncUrlsWithBaseTag:!1,listenLocationChangeEvent:!0,locationChangeEvent:"locationChange",locationChangeAngularEmitter:!1,usagesToUpdate:"use[*|href]",moveGradientsOutsideSymbol:!1},b=function(t){return Array.prototype.slice.call(t,0)},v={isChrome:function(){return/chrome/i.test(navigator.userAgent)},isFirefox:function(){return/firefox/i.test(navigator.userAgent)},isIE:function(){return/msie/i.test(navigator.userAgent)||/trident/i.test(navigator.userAgent)},isEdge:function(){return/edge/i.test(navigator.userAgent)}},w=function(t,e){var s=document.createEvent("CustomEvent");s.initCustomEvent(t,!1,!1,e),window.dispatchEvent(s)},P=function(t){var e=[];return b(t.querySelectorAll("style")).forEach((function(t){t.textContent+="",e.push(t)})),e},T=function(t){return(t||window.location.href).split("#")[0]},E=function(t){angular.module("ng").run(["$rootScope",function(e){e.$on("$locationChangeSuccess",(function(e,s,i){w(t,{oldUrl:i,newUrl:s})}))}])},A="linearGradient, radialGradient, pattern, mask, clipPath",S=function(t,e){return void 0===e&&(e=A),b(t.querySelectorAll("symbol")).forEach((function(t){b(t.querySelectorAll(e)).forEach((function(e){t.parentNode.insertBefore(e,t)}))})),t};function C(t,e){var s=b(t).reduce((function(t,s){if(!s.attributes)return t;var i=b(s.attributes),r=e?i.filter(e):i;return t.concat(r)}),[]);return s}var k=r.xlink.uri,N="xlink:href",I=/[{}|\\\^\[\]`"<>]/g;function O(t){return t.replace(I,(function(t){return"%"+t[0].charCodeAt(0).toString(16).toUpperCase()}))}function D(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function M(t,e,s){return b(t).forEach((function(t){var i=t.getAttribute(N);if(i&&0===i.indexOf(e)){var r=i.replace(e,s);t.setAttributeNS(k,N,r)}})),t}var L,_=["clipPath","colorProfile","src","cursor","fill","filter","marker","markerStart","markerMid","markerEnd","mask","stroke","style"],R=_.map((function(t){return"["+t+"]"})).join(","),j=function(t,e,s,i){var r=O(s),n=O(i),a=t.querySelectorAll(R),o=C(a,(function(t){var e=t.localName,s=t.value;return-1!==_.indexOf(e)&&-1!==s.indexOf("url("+r)}));o.forEach((function(t){return t.value=t.value.replace(new RegExp(D(r),"g"),n)})),M(e,r,n)},F={MOUNT:"mount",SYMBOL_MOUNT:"symbol_mount"},B=function(t){function e(e){var r=this;void 0===e&&(e={}),t.call(this,s(x,e));var n=i();this._emitter=n,this.node=null;var a=this,o=a.config;if(o.autoConfigure&&this._autoConfigure(e),o.syncUrlsWithBaseTag){var c=document.getElementsByTagName("base")[0].getAttribute("href");n.on(F.MOUNT,(function(){return r.updateUrls("#",c)}))}var h=this._handleLocationChange.bind(this);this._handleLocationChange=h,o.listenLocationChangeEvent&&window.addEventListener(o.locationChangeEvent,h),o.locationChangeAngularEmitter&&E(o.locationChangeEvent),n.on(F.MOUNT,(function(t){o.moveGradientsOutsideSymbol&&S(t)})),n.on(F.SYMBOL_MOUNT,(function(t){o.moveGradientsOutsideSymbol&&S(t.parentNode),(v.isIE()||v.isEdge())&&P(t)}))}t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e;var r={isMounted:{}};return r.isMounted.get=function(){return!!this.node},e.prototype._autoConfigure=function(t){var e=this,s=e.config;"undefined"===typeof t.syncUrlsWithBaseTag&&(s.syncUrlsWithBaseTag="undefined"!==typeof document.getElementsByTagName("base")[0]),"undefined"===typeof t.locationChangeAngularEmitter&&(s.locationChangeAngularEmitter="undefined"!==typeof window.angular),"undefined"===typeof t.moveGradientsOutsideSymbol&&(s.moveGradientsOutsideSymbol=v.isFirefox())},e.prototype._handleLocationChange=function(t){var e=t.detail,s=e.oldUrl,i=e.newUrl;this.updateUrls(s,i)},e.prototype.add=function(e){var s=this,i=t.prototype.add.call(this,e);return this.isMounted&&i&&(e.mount(s.node),this._emitter.emit(F.SYMBOL_MOUNT,e.node)),i},e.prototype.attach=function(t){var e=this,s=this;if(s.isMounted)return s.node;var i="string"===typeof t?document.querySelector(t):t;return s.node=i,this.symbols.forEach((function(t){t.mount(s.node),e._emitter.emit(F.SYMBOL_MOUNT,t.node)})),b(i.querySelectorAll("symbol")).forEach((function(t){var e=g.createFromExistingNode(t);e.node=t,s.add(e)})),this._emitter.emit(F.MOUNT,i),i},e.prototype.destroy=function(){var t=this,e=t.config,s=t.symbols,i=t._emitter;s.forEach((function(t){return t.destroy()})),i.off("*"),window.removeEventListener(e.locationChangeEvent,this._handleLocationChange),this.isMounted&&this.unmount()},e.prototype.mount=function(t,e){void 0===t&&(t=this.config.mountTo),void 0===e&&(e=!1);var s=this;if(s.isMounted)return s.node;var i="string"===typeof t?document.querySelector(t):t,r=s.render();return this.node=r,e&&i.childNodes[0]?i.insertBefore(r,i.childNodes[0]):i.appendChild(r),this._emitter.emit(F.MOUNT,r),r},e.prototype.render=function(){return y(this.stringify())},e.prototype.unmount=function(){this.node.parentNode.removeChild(this.node)},e.prototype.updateUrls=function(t,e){if(!this.isMounted)return!1;var s=document.querySelectorAll(this.config.usagesToUpdate);return j(this.node,s,T(t)+"#",T(e)+"#"),!0},Object.defineProperties(e.prototype,r),e}(f),U=t((function(t){ +/*! + * domready (c) Dustin Diaz 2014 - License MIT + */ +!function(e,s){t.exports=s()}(0,(function(){var t,e=[],s=document,i=s.documentElement.doScroll,r="DOMContentLoaded",n=(i?/^loaded|^c/:/^loaded|^i|^c/).test(s.readyState);return n||s.addEventListener(r,t=function(){s.removeEventListener(r,t),n=1;while(t=e.shift())t()}),function(t){n?setTimeout(t,0):e.push(t)}}))})),q="__SVG_SPRITE_NODE__",V="__SVG_SPRITE__",H=!!window[V];H?L=window[V]:(L=new B({attrs:{id:q}}),window[V]=L);var z=function(){var t=document.getElementById(q);t?L.attach(t):L.mount(document.body,!0)};document.body?z():U(z);var W=L;return W}))}).call(this,s("c8ba"))},"21a6":function(t,e,s){(function(s){var i,r,n;(function(s,a){r=[],i=a,n="function"===typeof i?i.apply(e,r):i,void 0===n||(t.exports=n)})(0,(function(){"use strict";function e(t,e){return"undefined"==typeof e?e={autoBom:!1}:"object"!=typeof e&&(console.warn("Deprecated: Expected third argument to be a object"),e={autoBom:!e}),e.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(t.type)?new Blob(["\ufeff",t],{type:t.type}):t}function i(t,e,s){var i=new XMLHttpRequest;i.open("GET",t),i.responseType="blob",i.onload=function(){o(i.response,e,s)},i.onerror=function(){console.error("could not download file")},i.send()}function r(t){var e=new XMLHttpRequest;e.open("HEAD",t,!1);try{e.send()}catch(t){}return 200<=e.status&&299>=e.status}function n(t){try{t.dispatchEvent(new MouseEvent("click"))}catch(i){var e=document.createEvent("MouseEvents");e.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),t.dispatchEvent(e)}}var a="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof s&&s.global===s?s:void 0,o=a.saveAs||("object"!=typeof window||window!==a?function(){}:"download"in HTMLAnchorElement.prototype?function(t,e,s){var o=a.URL||a.webkitURL,c=document.createElement("a");e=e||t.name||"download",c.download=e,c.rel="noopener","string"==typeof t?(c.href=t,c.origin===location.origin?n(c):r(c.href)?i(t,e,s):n(c,c.target="_blank")):(c.href=o.createObjectURL(t),setTimeout((function(){o.revokeObjectURL(c.href)}),4e4),setTimeout((function(){n(c)}),0))}:"msSaveOrOpenBlob"in navigator?function(t,s,a){if(s=s||t.name||"download","string"!=typeof t)navigator.msSaveOrOpenBlob(e(t,a),s);else if(r(t))i(t,s,a);else{var o=document.createElement("a");o.href=t,o.target="_blank",setTimeout((function(){n(o)}))}}:function(t,e,s,r){if(r=r||open("","_blank"),r&&(r.document.title=r.document.body.innerText="downloading..."),"string"==typeof t)return i(t,e,s);var n="application/octet-stream"===t.type,o=/constructor/i.test(a.HTMLElement)||a.safari,c=/CriOS\/[\d]+/.test(navigator.userAgent);if((c||n&&o)&&"object"==typeof FileReader){var h=new FileReader;h.onloadend=function(){var t=h.result;t=c?t:t.replace(/^data:[^;]*;/,"data:attachment/file;"),r?r.location.href=t:location=t,r=null},h.readAsDataURL(t)}else{var l=a.URL||a.webkitURL,p=l.createObjectURL(t);r?r.location=p:location.href=p,r=null,setTimeout((function(){l.revokeObjectURL(p)}),4e4)}});a.saveAs=o.saveAs=o,t.exports=o}))}).call(this,s("c8ba"))},2266:function(t,e,s){var i=s("825a"),r=s("e95a"),n=s("50c4"),a=s("0366"),o=s("35a1"),c=s("9bdd"),h=function(t,e){this.stopped=t,this.result=e},l=t.exports=function(t,e,s,l,p){var u,d,f,m,y,g,x,b=a(e,s,l?2:1);if(p)u=t;else{if(d=o(t),"function"!=typeof d)throw TypeError("Target is not iterable");if(r(d)){for(f=0,m=n(t.length);m>f;f++)if(y=l?b(i(x=t[f])[0],x[1]):b(t[f]),y&&y instanceof h)return y;return new h(!1)}u=d.call(t)}g=u.next;while(!(x=g.call(u)).done)if(y=c(u,b,x.value,l),"object"==typeof y&&y&&y instanceof h)return y;return new h(!1)};l.stop=function(t){return new h(!0,t)}},"23cb":function(t,e,s){var i=s("a691"),r=Math.max,n=Math.min;t.exports=function(t,e){var s=i(t);return s<0?r(s+e,0):n(s,e)}},"23e7":function(t,e,s){var i=s("da84"),r=s("06cf").f,n=s("9112"),a=s("6eeb"),o=s("ce4e"),c=s("e893"),h=s("94ca");t.exports=function(t,e){var s,l,p,u,d,f,m=t.target,y=t.global,g=t.stat;if(l=y?i:g?i[m]||o(m,{}):(i[m]||{}).prototype,l)for(p in e){if(d=e[p],t.noTargetGet?(f=r(l,p),u=f&&f.value):u=l[p],s=h(y?p:m+(g?".":"#")+p,t.forced),!s&&void 0!==u){if(typeof d===typeof u)continue;c(d,u)}(t.sham||u&&u.sham)&&n(d,"sham",!0),a(l,p,d,t)}}},"241c":function(t,e,s){var i=s("ca84"),r=s("7839"),n=r.concat("length","prototype");e.f=Object.getOwnPropertyNames||function(t){return i(t,n)}},"25f0":function(t,e,s){"use strict";var i=s("6eeb"),r=s("825a"),n=s("d039"),a=s("ad6d"),o="toString",c=RegExp.prototype,h=c[o],l=n((function(){return"/a/b"!=h.call({source:"a",flags:"b"})})),p=h.name!=o;(l||p)&&i(RegExp.prototype,o,(function(){var t=r(this),e=String(t.source),s=t.flags,i=String(void 0===s&&t instanceof RegExp&&!("flags"in c)?a.call(t):s);return"/"+e+"/"+i}),{unsafe:!0})},2626:function(t,e,s){"use strict";var i=s("d066"),r=s("9bf2"),n=s("b622"),a=s("83ab"),o=n("species");t.exports=function(t){var e=i(t),s=r.f;a&&e&&!e[o]&&s(e,o,{configurable:!0,get:function(){return this}})}},2877:function(t,e,s){"use strict";function i(t,e,s,i,r,n,a,o){var c,h="function"===typeof t?t.options:t;if(e&&(h.render=e,h.staticRenderFns=s,h._compiled=!0),i&&(h.functional=!0),n&&(h._scopeId="data-v-"+n),a?(c=function(t){t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,t||"undefined"===typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),r&&r.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(a)},h._ssrRegister=c):r&&(c=o?function(){r.call(this,(h.functional?this.parent:this).$root.$options.shadowRoot)}:r),c)if(h.functional){h._injectStyles=c;var l=h.render;h.render=function(t,e){return c.call(e),l(t,e)}}else{var p=h.beforeCreate;h.beforeCreate=p?[].concat(p,c):[c]}return{exports:t,options:h}}s.d(e,"a",(function(){return i}))},"28a0":function(t,e){"function"===typeof Object.create?t.exports=function(t,e){t.super_=e,t.prototype=Object.create(e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}})}:t.exports=function(t,e){t.super_=e;var s=function(){};s.prototype=e.prototype,t.prototype=new s,t.prototype.constructor=t}},"2cf4":function(t,e,s){var i,r,n,a=s("da84"),o=s("d039"),c=s("c6b6"),h=s("0366"),l=s("1be4"),p=s("cc12"),u=s("1cdc"),d=a.location,f=a.setImmediate,m=a.clearImmediate,y=a.process,g=a.MessageChannel,x=a.Dispatch,b=0,v={},w="onreadystatechange",P=function(t){if(v.hasOwnProperty(t)){var e=v[t];delete v[t],e()}},T=function(t){return function(){P(t)}},E=function(t){P(t.data)},A=function(t){a.postMessage(t+"",d.protocol+"//"+d.host)};f&&m||(f=function(t){var e=[],s=1;while(arguments.length>s)e.push(arguments[s++]);return v[++b]=function(){("function"==typeof t?t:Function(t)).apply(void 0,e)},i(b),b},m=function(t){delete v[t]},"process"==c(y)?i=function(t){y.nextTick(T(t))}:x&&x.now?i=function(t){x.now(T(t))}:g&&!u?(r=new g,n=r.port2,r.port1.onmessage=E,i=h(n.postMessage,n,1)):!a.addEventListener||"function"!=typeof postMessage||a.importScripts||o(A)||"file:"===d.protocol?i=w in p("script")?function(t){l.appendChild(p("script"))[w]=function(){l.removeChild(this),P(t)}}:function(t){setTimeout(T(t),0)}:(i=A,a.addEventListener("message",E,!1))),t.exports={set:f,clear:m}},"2d00":function(t,e,s){var i,r,n=s("da84"),a=s("342f"),o=n.process,c=o&&o.versions,h=c&&c.v8;h?(i=h.split("."),r=i[0]+i[1]):a&&(i=a.match(/Edge\/(\d+)/),(!i||i[1]>=74)&&(i=a.match(/Chrome\/(\d+)/),i&&(r=i[1]))),t.exports=r&&+r},3022:function(t,e,s){(function(t){var i=Object.getOwnPropertyDescriptors||function(t){for(var e=Object.keys(t),s={},i=0;i=n)return t;switch(t){case"%s":return String(i[s++]);case"%d":return Number(i[s++]);case"%j":try{return JSON.stringify(i[s++])}catch(e){return"[Circular]"}default:return t}})),c=i[s];s=3&&(i.depth=arguments[2]),arguments.length>=4&&(i.colors=arguments[3]),x(s)?i.showHidden=s:s&&e._extend(i,s),E(i.showHidden)&&(i.showHidden=!1),E(i.depth)&&(i.depth=2),E(i.colors)&&(i.colors=!1),E(i.customInspect)&&(i.customInspect=!0),i.colors&&(i.stylize=c),p(i,t,i.depth)}function c(t,e){var s=o.styles[e];return s?"["+o.colors[s][0]+"m"+t+"["+o.colors[s][1]+"m":t}function h(t,e){return t}function l(t){var e={};return t.forEach((function(t,s){e[t]=!0})),e}function p(t,s,i){if(t.customInspect&&s&&N(s.inspect)&&s.inspect!==e.inspect&&(!s.constructor||s.constructor.prototype!==s)){var r=s.inspect(i,t);return P(r)||(r=p(t,r,i)),r}var n=u(t,s);if(n)return n;var a=Object.keys(s),o=l(a);if(t.showHidden&&(a=Object.getOwnPropertyNames(s)),k(s)&&(a.indexOf("message")>=0||a.indexOf("description")>=0))return d(s);if(0===a.length){if(N(s)){var c=s.name?": "+s.name:"";return t.stylize("[Function"+c+"]","special")}if(A(s))return t.stylize(RegExp.prototype.toString.call(s),"regexp");if(C(s))return t.stylize(Date.prototype.toString.call(s),"date");if(k(s))return d(s)}var h,x="",b=!1,v=["{","}"];if(g(s)&&(b=!0,v=["[","]"]),N(s)){var w=s.name?": "+s.name:"";x=" [Function"+w+"]"}return A(s)&&(x=" "+RegExp.prototype.toString.call(s)),C(s)&&(x=" "+Date.prototype.toUTCString.call(s)),k(s)&&(x=" "+d(s)),0!==a.length||b&&0!=s.length?i<0?A(s)?t.stylize(RegExp.prototype.toString.call(s),"regexp"):t.stylize("[Object]","special"):(t.seen.push(s),h=b?f(t,s,i,o,a):a.map((function(e){return m(t,s,i,o,e,b)})),t.seen.pop(),y(h,x,v)):v[0]+x+v[1]}function u(t,e){if(E(e))return t.stylize("undefined","undefined");if(P(e)){var s="'"+JSON.stringify(e).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return t.stylize(s,"string")}return w(e)?t.stylize(""+e,"number"):x(e)?t.stylize(""+e,"boolean"):b(e)?t.stylize("null","null"):void 0}function d(t){return"["+Error.prototype.toString.call(t)+"]"}function f(t,e,s,i,r){for(var n=[],a=0,o=e.length;a-1&&(o=n?o.split("\n").map((function(t){return" "+t})).join("\n").substr(2):"\n"+o.split("\n").map((function(t){return" "+t})).join("\n"))):o=t.stylize("[Circular]","special")),E(a)){if(n&&r.match(/^\d+$/))return o;a=JSON.stringify(""+r),a.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(a=a.substr(1,a.length-2),a=t.stylize(a,"name")):(a=a.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),a=t.stylize(a,"string"))}return a+": "+o}function y(t,e,s){var i=t.reduce((function(t,e){return e.indexOf("\n")>=0&&0,t+e.replace(/\u001b\[\d\d?m/g,"").length+1}),0);return i>60?s[0]+(""===e?"":e+"\n ")+" "+t.join(",\n ")+" "+s[1]:s[0]+e+" "+t.join(", ")+" "+s[1]}function g(t){return Array.isArray(t)}function x(t){return"boolean"===typeof t}function b(t){return null===t}function v(t){return null==t}function w(t){return"number"===typeof t}function P(t){return"string"===typeof t}function T(t){return"symbol"===typeof t}function E(t){return void 0===t}function A(t){return S(t)&&"[object RegExp]"===O(t)}function S(t){return"object"===typeof t&&null!==t}function C(t){return S(t)&&"[object Date]"===O(t)}function k(t){return S(t)&&("[object Error]"===O(t)||t instanceof Error)}function N(t){return"function"===typeof t}function I(t){return null===t||"boolean"===typeof t||"number"===typeof t||"string"===typeof t||"symbol"===typeof t||"undefined"===typeof t}function O(t){return Object.prototype.toString.call(t)}function D(t){return t<10?"0"+t.toString(10):t.toString(10)}e.debuglog=function(s){if(E(n)&&(n=Object({NODE_ENV:"production",BASE_URL:"/form-generator/"}).NODE_DEBUG||""),s=s.toUpperCase(),!a[s])if(new RegExp("\\b"+s+"\\b","i").test(n)){var i=t.pid;a[s]=function(){var t=e.format.apply(e,arguments);console.error("%s %d: %s",s,i,t)}}else a[s]=function(){};return a[s]},e.inspect=o,o.colors={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]},o.styles={special:"cyan",number:"yellow",boolean:"yellow",undefined:"grey",null:"bold",string:"green",date:"magenta",regexp:"red"},e.isArray=g,e.isBoolean=x,e.isNull=b,e.isNullOrUndefined=v,e.isNumber=w,e.isString=P,e.isSymbol=T,e.isUndefined=E,e.isRegExp=A,e.isObject=S,e.isDate=C,e.isError=k,e.isFunction=N,e.isPrimitive=I,e.isBuffer=s("d60a");var M=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function L(){var t=new Date,e=[D(t.getHours()),D(t.getMinutes()),D(t.getSeconds())].join(":");return[t.getDate(),M[t.getMonth()],e].join(" ")}function _(t,e){return Object.prototype.hasOwnProperty.call(t,e)}e.log=function(){console.log("%s - %s",L(),e.format.apply(e,arguments))},e.inherits=s("28a0"),e._extend=function(t,e){if(!e||!S(e))return t;var s=Object.keys(e),i=s.length;while(i--)t[s[i]]=e[s[i]];return t};var R="undefined"!==typeof Symbol?Symbol("util.promisify.custom"):void 0;function j(t,e){if(!t){var s=new Error("Promise was rejected with a falsy value");s.reason=t,t=s}return e(t)}function F(e){if("function"!==typeof e)throw new TypeError('The "original" argument must be of type Function');function s(){for(var s=[],i=0;i=h?t?"":void 0:(n=o.charCodeAt(c),n<55296||n>56319||c+1===h||(a=o.charCodeAt(c+1))<56320||a>57343?t?o.charAt(c):n:t?o.slice(c,c+2):a-56320+(n-55296<<10)+65536)}}},"0390":function(t,e,s){"use strict";var i=s("02f4")(!0);t.exports=function(t,e,s){return e+(s?i(t,e).length:1)}},"0bfb":function(t,e,s){"use strict";var i=s("cb7c");t.exports=function(){var t=i(this),e="";return t.global&&(e+="g"),t.ignoreCase&&(e+="i"),t.multiline&&(e+="m"),t.unicode&&(e+="u"),t.sticky&&(e+="y"),e}},"0d58":function(t,e,s){var i=s("ce10"),r=s("e11e");t.exports=Object.keys||function(t){return i(t,r)}},1495:function(t,e,s){var i=s("86cc"),r=s("cb7c"),n=s("0d58");t.exports=s("9e1e")?Object.defineProperties:function(t,e){r(t);var s,a=n(e),o=a.length,c=0;while(o>c)i.f(t,s=a[c++],e[s]);return t}},"214f":function(t,e,s){"use strict";s("b0c5");var i=s("2aba"),r=s("32e9"),n=s("79e5"),a=s("be13"),o=s("2b4c"),c=s("520a"),h=o("species"),l=!n((function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$")})),p=function(){var t=/(?:)/,e=t.exec;t.exec=function(){return e.apply(this,arguments)};var s="ab".split(t);return 2===s.length&&"a"===s[0]&&"b"===s[1]}();t.exports=function(t,e,s){var u=o(t),d=!n((function(){var e={};return e[u]=function(){return 7},7!=""[t](e)})),f=d?!n((function(){var e=!1,s=/a/;return s.exec=function(){return e=!0,null},"split"===t&&(s.constructor={},s.constructor[h]=function(){return s}),s[u](""),!e})):void 0;if(!d||!f||"replace"===t&&!l||"split"===t&&!p){var m=/./[u],y=s(a,u,""[t],(function(t,e,s,i,r){return e.exec===c?d&&!r?{done:!0,value:m.call(e,s,i)}:{done:!0,value:t.call(s,e,i)}:{done:!1}})),g=y[0],x=y[1];i(String.prototype,t,g),r(RegExp.prototype,u,2==e?function(t,e){return x.call(t,this,e)}:function(t){return x.call(t,this)})}}},"230e":function(t,e,s){var i=s("d3f4"),r=s("7726").document,n=i(r)&&i(r.createElement);t.exports=function(t){return n?r.createElement(t):{}}},"23c6":function(t,e,s){var i=s("2d95"),r=s("2b4c")("toStringTag"),n="Arguments"==i(function(){return arguments}()),a=function(t,e){try{return t[e]}catch(s){}};t.exports=function(t){var e,s,o;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(s=a(e=Object(t),r))?s:n?i(e):"Object"==(o=i(e))&&"function"==typeof e.callee?"Arguments":o}},2621:function(t,e){e.f=Object.getOwnPropertySymbols},"2aba":function(t,e,s){var i=s("7726"),r=s("32e9"),n=s("69a8"),a=s("ca5a")("src"),o=s("fa5b"),c="toString",h=(""+o).split(c);s("8378").inspectSource=function(t){return o.call(t)},(t.exports=function(t,e,s,o){var c="function"==typeof s;c&&(n(s,"name")||r(s,"name",e)),t[e]!==s&&(c&&(n(s,a)||r(s,a,t[e]?""+t[e]:h.join(String(e)))),t===i?t[e]=s:o?t[e]?t[e]=s:r(t,e,s):(delete t[e],r(t,e,s)))})(Function.prototype,c,(function(){return"function"==typeof this&&this[a]||o.call(this)}))},"2aeb":function(t,e,s){var i=s("cb7c"),r=s("1495"),n=s("e11e"),a=s("613b")("IE_PROTO"),o=function(){},c="prototype",h=function(){var t,e=s("230e")("iframe"),i=n.length,r="<",a=">";e.style.display="none",s("fab2").appendChild(e),e.src="javascript:",t=e.contentWindow.document,t.open(),t.write(r+"script"+a+"document.F=Object"+r+"/script"+a),t.close(),h=t.F;while(i--)delete h[c][n[i]];return h()};t.exports=Object.create||function(t,e){var s;return null!==t?(o[c]=i(t),s=new o,o[c]=null,s[a]=t):s=h(),void 0===e?s:r(s,e)}},"2b4c":function(t,e,s){var i=s("5537")("wks"),r=s("ca5a"),n=s("7726").Symbol,a="function"==typeof n,o=t.exports=function(t){return i[t]||(i[t]=a&&n[t]||(a?n:r)("Symbol."+t))};o.store=i},"2d00":function(t,e){t.exports=!1},"2d95":function(t,e){var s={}.toString;t.exports=function(t){return s.call(t).slice(8,-1)}},"2fdb":function(t,e,s){"use strict";var i=s("5ca1"),r=s("d2c8"),n="includes";i(i.P+i.F*s("5147")(n),"String",{includes:function(t){return!!~r(this,t,n).indexOf(t,arguments.length>1?arguments[1]:void 0)}})},"32e9":function(t,e,s){var i=s("86cc"),r=s("4630");t.exports=s("9e1e")?function(t,e,s){return i.f(t,e,r(1,s))}:function(t,e,s){return t[e]=s,t}},"38fd":function(t,e,s){var i=s("69a8"),r=s("4bf8"),n=s("613b")("IE_PROTO"),a=Object.prototype;t.exports=Object.getPrototypeOf||function(t){return t=r(t),i(t,n)?t[n]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?a:null}},"41a0":function(t,e,s){"use strict";var i=s("2aeb"),r=s("4630"),n=s("7f20"),a={};s("32e9")(a,s("2b4c")("iterator"),(function(){return this})),t.exports=function(t,e,s){t.prototype=i(a,{next:r(1,s)}),n(t,e+" Iterator")}},"456d":function(t,e,s){var i=s("4bf8"),r=s("0d58");s("5eda")("keys",(function(){return function(t){return r(i(t))}}))},4588:function(t,e){var s=Math.ceil,i=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?i:s)(t)}},4630:function(t,e){t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},"4bf8":function(t,e,s){var i=s("be13");t.exports=function(t){return Object(i(t))}},5147:function(t,e,s){var i=s("2b4c")("match");t.exports=function(t){var e=/./;try{"/./"[t](e)}catch(s){try{return e[i]=!1,!"/./"[t](e)}catch(r){}}return!0}},"520a":function(t,e,s){"use strict";var i=s("0bfb"),r=RegExp.prototype.exec,n=String.prototype.replace,a=r,o="lastIndex",c=function(){var t=/a/,e=/b*/g;return r.call(t,"a"),r.call(e,"a"),0!==t[o]||0!==e[o]}(),h=void 0!==/()??/.exec("")[1],l=c||h;l&&(a=function(t){var e,s,a,l,p=this;return h&&(s=new RegExp("^"+p.source+"$(?!\\s)",i.call(p))),c&&(e=p[o]),a=r.call(p,t),c&&a&&(p[o]=p.global?a.index+a[0].length:e),h&&a&&a.length>1&&n.call(a[0],s,(function(){for(l=1;l1?arguments[1]:void 0)}}),s("9c6c")("includes")},6821:function(t,e,s){var i=s("626a"),r=s("be13");t.exports=function(t){return i(r(t))}},"69a8":function(t,e){var s={}.hasOwnProperty;t.exports=function(t,e){return s.call(t,e)}},"6a99":function(t,e,s){var i=s("d3f4");t.exports=function(t,e){if(!i(t))return t;var s,r;if(e&&"function"==typeof(s=t.toString)&&!i(r=s.call(t)))return r;if("function"==typeof(s=t.valueOf)&&!i(r=s.call(t)))return r;if(!e&&"function"==typeof(s=t.toString)&&!i(r=s.call(t)))return r;throw TypeError("Can't convert object to primitive value")}},7333:function(t,e,s){"use strict";var i=s("9e1e"),r=s("0d58"),n=s("2621"),a=s("52a7"),o=s("4bf8"),c=s("626a"),h=Object.assign;t.exports=!h||s("79e5")((function(){var t={},e={},s=Symbol(),i="abcdefghijklmnopqrst";return t[s]=7,i.split("").forEach((function(t){e[t]=t})),7!=h({},t)[s]||Object.keys(h({},e)).join("")!=i}))?function(t,e){var s=o(t),h=arguments.length,l=1,p=n.f,u=a.f;while(h>l){var d,f=c(arguments[l++]),m=p?r(f).concat(p(f)):r(f),y=m.length,g=0;while(y>g)d=m[g++],i&&!u.call(f,d)||(s[d]=f[d])}return s}:h},7726:function(t,e){var s=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=s)},"77f1":function(t,e,s){var i=s("4588"),r=Math.max,n=Math.min;t.exports=function(t,e){return t=i(t),t<0?r(t+e,0):n(t,e)}},"79e5":function(t,e){t.exports=function(t){try{return!!t()}catch(e){return!0}}},"7f20":function(t,e,s){var i=s("86cc").f,r=s("69a8"),n=s("2b4c")("toStringTag");t.exports=function(t,e,s){t&&!r(t=s?t:t.prototype,n)&&i(t,n,{configurable:!0,value:e})}},8378:function(t,e){var s=t.exports={version:"2.6.11"};"number"==typeof __e&&(__e=s)},"84f2":function(t,e){t.exports={}},"86cc":function(t,e,s){var i=s("cb7c"),r=s("c69a"),n=s("6a99"),a=Object.defineProperty;e.f=s("9e1e")?Object.defineProperty:function(t,e,s){if(i(t),e=n(e,!0),i(s),r)try{return a(t,e,s)}catch(o){}if("get"in s||"set"in s)throw TypeError("Accessors not supported!");return"value"in s&&(t[e]=s.value),t}},"9b43":function(t,e,s){var i=s("d8e8");t.exports=function(t,e,s){if(i(t),void 0===e)return t;switch(s){case 1:return function(s){return t.call(e,s)};case 2:return function(s,i){return t.call(e,s,i)};case 3:return function(s,i,r){return t.call(e,s,i,r)}}return function(){return t.apply(e,arguments)}}},"9c6c":function(t,e,s){var i=s("2b4c")("unscopables"),r=Array.prototype;void 0==r[i]&&s("32e9")(r,i,{}),t.exports=function(t){r[i][t]=!0}},"9def":function(t,e,s){var i=s("4588"),r=Math.min;t.exports=function(t){return t>0?r(i(t),9007199254740991):0}},"9e1e":function(t,e,s){t.exports=!s("79e5")((function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a}))},a352:function(t,e){t.exports=s("aa47")},a481:function(t,e,s){"use strict";var i=s("cb7c"),r=s("4bf8"),n=s("9def"),a=s("4588"),o=s("0390"),c=s("5f1b"),h=Math.max,l=Math.min,p=Math.floor,u=/\$([$&`']|\d\d?|<[^>]*>)/g,d=/\$([$&`']|\d\d?)/g,f=function(t){return void 0===t?t:String(t)};s("214f")("replace",2,(function(t,e,s,m){return[function(i,r){var n=t(this),a=void 0==i?void 0:i[e];return void 0!==a?a.call(i,n,r):s.call(String(n),i,r)},function(t,e){var r=m(s,t,this,e);if(r.done)return r.value;var p=i(t),u=String(this),d="function"===typeof e;d||(e=String(e));var g=p.global;if(g){var x=p.unicode;p.lastIndex=0}var b=[];while(1){var v=c(p,u);if(null===v)break;if(b.push(v),!g)break;var w=String(v[0]);""===w&&(p.lastIndex=o(u,n(p.lastIndex),x))}for(var P="",T=0,E=0;E=T&&(P+=u.slice(T,S)+O,T=S+A.length)}return P+u.slice(T)}];function y(t,e,i,n,a,o){var c=i+t.length,h=n.length,l=d;return void 0!==a&&(a=r(a),l=u),s.call(o,l,(function(s,r){var o;switch(r.charAt(0)){case"$":return"$";case"&":return t;case"`":return e.slice(0,i);case"'":return e.slice(c);case"<":o=a[r.slice(1,-1)];break;default:var l=+r;if(0===l)return s;if(l>h){var u=p(l/10);return 0===u?s:u<=h?void 0===n[u-1]?r.charAt(1):n[u-1]+r.charAt(1):s}o=n[l-1]}return void 0===o?"":o}))}}))},aae3:function(t,e,s){var i=s("d3f4"),r=s("2d95"),n=s("2b4c")("match");t.exports=function(t){var e;return i(t)&&(void 0!==(e=t[n])?!!e:"RegExp"==r(t))}},ac6a:function(t,e,s){for(var i=s("cadf"),r=s("0d58"),n=s("2aba"),a=s("7726"),o=s("32e9"),c=s("84f2"),h=s("2b4c"),l=h("iterator"),p=h("toStringTag"),u=c.Array,d={CSSRuleList:!0,CSSStyleDeclaration:!1,CSSValueList:!1,ClientRectList:!1,DOMRectList:!1,DOMStringList:!1,DOMTokenList:!0,DataTransferItemList:!1,FileList:!1,HTMLAllCollection:!1,HTMLCollection:!1,HTMLFormElement:!1,HTMLSelectElement:!1,MediaList:!0,MimeTypeArray:!1,NamedNodeMap:!1,NodeList:!0,PaintRequestList:!1,Plugin:!1,PluginArray:!1,SVGLengthList:!1,SVGNumberList:!1,SVGPathSegList:!1,SVGPointList:!1,SVGStringList:!1,SVGTransformList:!1,SourceBufferList:!1,StyleSheetList:!0,TextTrackCueList:!1,TextTrackList:!1,TouchList:!1},f=r(d),m=0;ml)if(o=c[l++],o!=o)return!0}else for(;h>l;l++)if((t||l in c)&&c[l]===s)return t||l||0;return!t&&-1}}},c649:function(t,e,s){"use strict";(function(t){s.d(e,"c",(function(){return h})),s.d(e,"a",(function(){return o})),s.d(e,"b",(function(){return r})),s.d(e,"d",(function(){return c}));s("a481");function i(){return"undefined"!==typeof window?window.console:t.console}var r=i();function n(t){var e=Object.create(null);return function(s){var i=e[s];return i||(e[s]=t(s))}}var a=/-(\w)/g,o=n((function(t){return t.replace(a,(function(t,e){return e?e.toUpperCase():""}))}));function c(t){null!==t.parentElement&&t.parentElement.removeChild(t)}function h(t,e,s){var i=0===s?t.children[0]:t.children[s-1].nextSibling;t.insertBefore(e,i)}}).call(this,s("c8ba"))},c69a:function(t,e,s){t.exports=!s("9e1e")&&!s("79e5")((function(){return 7!=Object.defineProperty(s("230e")("div"),"a",{get:function(){return 7}}).a}))},c8ba:function(t,e){var s;s=function(){return this}();try{s=s||new Function("return this")()}catch(i){"object"===typeof window&&(s=window)}t.exports=s},ca5a:function(t,e){var s=0,i=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++s+i).toString(36))}},cadf:function(t,e,s){"use strict";var i=s("9c6c"),r=s("d53b"),n=s("84f2"),a=s("6821");t.exports=s("01f9")(Array,"Array",(function(t,e){this._t=a(t),this._i=0,this._k=e}),(function(){var t=this._t,e=this._k,s=this._i++;return!t||s>=t.length?(this._t=void 0,r(1)):r(0,"keys"==e?s:"values"==e?t[s]:[s,t[s]])}),"values"),n.Arguments=n.Array,i("keys"),i("values"),i("entries")},cb7c:function(t,e,s){var i=s("d3f4");t.exports=function(t){if(!i(t))throw TypeError(t+" is not an object!");return t}},ce10:function(t,e,s){var i=s("69a8"),r=s("6821"),n=s("c366")(!1),a=s("613b")("IE_PROTO");t.exports=function(t,e){var s,o=r(t),c=0,h=[];for(s in o)s!=a&&i(o,s)&&h.push(s);while(e.length>c)i(o,s=e[c++])&&(~n(h,s)||h.push(s));return h}},d2c8:function(t,e,s){var i=s("aae3"),r=s("be13");t.exports=function(t,e,s){if(i(e))throw TypeError("String#"+s+" doesn't accept regex!");return String(r(t))}},d3f4:function(t,e){t.exports=function(t){return"object"===typeof t?null!==t:"function"===typeof t}},d53b:function(t,e){t.exports=function(t,e){return{value:e,done:!!t}}},d8e8:function(t,e){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},e11e:function(t,e){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},f559:function(t,e,s){"use strict";var i=s("5ca1"),r=s("9def"),n=s("d2c8"),a="startsWith",o=""[a];i(i.P+i.F*s("5147")(a),"String",{startsWith:function(t){var e=n(this,t,a),s=r(Math.min(arguments.length>1?arguments[1]:void 0,e.length)),i=String(t);return o?o.call(e,i,s):e.slice(s,s+i.length)===i}})},f6fd:function(t,e){(function(t){var e="currentScript",s=t.getElementsByTagName("script");e in t||Object.defineProperty(t,e,{get:function(){try{throw new Error}catch(i){var t,e=(/.*at [^\(]*\((.*):.+:.+\)$/gi.exec(i.stack)||[!1])[1];for(t in s)if(s[t].src==e||"interactive"==s[t].readyState)return s[t];return null}}})})(document)},f751:function(t,e,s){var i=s("5ca1");i(i.S+i.F,"Object",{assign:s("7333")})},fa5b:function(t,e,s){t.exports=s("5537")("native-function-to-string",Function.toString)},fab2:function(t,e,s){var i=s("7726").document;t.exports=i&&i.documentElement},fb15:function(t,e,s){"use strict";var i;(s.r(e),"undefined"!==typeof window)&&(s("f6fd"),(i=window.document.currentScript)&&(i=i.src.match(/(.+\/)[^/]+\.js(\?.*)?$/))&&(s.p=i[1]));s("f751"),s("f559"),s("ac6a"),s("cadf"),s("456d");function r(t){if(Array.isArray(t))return t}function n(t,e){if("undefined"!==typeof Symbol&&Symbol.iterator in Object(t)){var s=[],i=!0,r=!1,n=void 0;try{for(var a,o=t[Symbol.iterator]();!(i=(a=o.next()).done);i=!0)if(s.push(a.value),e&&s.length===e)break}catch(c){r=!0,n=c}finally{try{i||null==o["return"]||o["return"]()}finally{if(r)throw n}}return s}}function a(t,e){(null==e||e>t.length)&&(e=t.length);for(var s=0,i=new Array(e);s=n?r.length:r.indexOf(t)}));return s?a.filter((function(t){return-1!==t})):a}function v(t,e){var s=this;this.$nextTick((function(){return s.$emit(t.toLowerCase(),e)}))}function w(t){var e=this;return function(s){null!==e.realList&&e["onDrag"+t](s),v.call(e,t,s)}}function P(t){return["transition-group","TransitionGroup"].includes(t)}function T(t){if(!t||1!==t.length)return!1;var e=h(t,1),s=e[0].componentOptions;return!!s&&P(s.tag)}function E(t,e,s){return t[s]||(e[s]?e[s]():void 0)}function A(t,e,s){var i=0,r=0,n=E(e,s,"header");n&&(i=n.length,t=t?[].concat(d(n),d(t)):d(n));var a=E(e,s,"footer");return a&&(r=a.length,t=t?[].concat(d(t),d(a)):d(a)),{children:t,headerOffset:i,footerOffset:r}}function S(t,e){var s=null,i=function(t,e){s=g(s,t,e)},r=Object.keys(t).filter((function(t){return"id"===t||t.startsWith("data-")})).reduce((function(e,s){return e[s]=t[s],e}),{});if(i("attrs",r),!e)return s;var n=e.on,a=e.props,o=e.attrs;return i("on",n),i("props",a),Object.assign(s.attrs,o),s}var C=["Start","Add","Remove","Update","End"],k=["Choose","Unchoose","Sort","Filter","Clone"],N=["Move"].concat(C,k).map((function(t){return"on"+t})),I=null,O={options:Object,list:{type:Array,required:!1,default:null},value:{type:Array,required:!1,default:null},noTransitionOnDrag:{type:Boolean,default:!1},clone:{type:Function,default:function(t){return t}},element:{type:String,default:"div"},tag:{type:String,default:null},move:{type:Function,default:null},componentData:{type:Object,required:!1,default:null}},D={name:"draggable",inheritAttrs:!1,props:O,data:function(){return{transitionMode:!1,noneFunctionalComponentMode:!1}},render:function(t){var e=this.$slots.default;this.transitionMode=T(e);var s=A(e,this.$slots,this.$scopedSlots),i=s.children,r=s.headerOffset,n=s.footerOffset;this.headerOffset=r,this.footerOffset=n;var a=S(this.$attrs,this.componentData);return t(this.getTag(),a,i)},created:function(){null!==this.list&&null!==this.value&&y["b"].error("Value and list props are mutually exclusive! Please set one or another."),"div"!==this.element&&y["b"].warn("Element props is deprecated please use tag props instead. See https://github.com/SortableJS/Vue.Draggable/blob/master/documentation/migrate.md#element-props"),void 0!==this.options&&y["b"].warn("Options props is deprecated, add sortable options directly as vue.draggable item, or use v-bind. See https://github.com/SortableJS/Vue.Draggable/blob/master/documentation/migrate.md#options-props")},mounted:function(){var t=this;if(this.noneFunctionalComponentMode=this.getTag().toLowerCase()!==this.$el.nodeName.toLowerCase()&&!this.getIsFunctional(),this.noneFunctionalComponentMode&&this.transitionMode)throw new Error("Transition-group inside component is not supported. Please alter tag value or remove transition-group. Current tag value: ".concat(this.getTag()));var e={};C.forEach((function(s){e["on"+s]=w.call(t,s)})),k.forEach((function(s){e["on"+s]=v.bind(t,s)}));var s=Object.keys(this.$attrs).reduce((function(e,s){return e[Object(y["a"])(s)]=t.$attrs[s],e}),{}),i=Object.assign({},this.options,s,e,{onMove:function(e,s){return t.onDragMove(e,s)}});!("draggable"in i)&&(i.draggable=">*"),this._sortable=new m.a(this.rootContainer,i),this.computeIndexes()},beforeDestroy:function(){void 0!==this._sortable&&this._sortable.destroy()},computed:{rootContainer:function(){return this.transitionMode?this.$el.children[0]:this.$el},realList:function(){return this.list?this.list:this.value}},watch:{options:{handler:function(t){this.updateOptions(t)},deep:!0},$attrs:{handler:function(t){this.updateOptions(t)},deep:!0},realList:function(){this.computeIndexes()}},methods:{getIsFunctional:function(){var t=this._vnode.fnOptions;return t&&t.functional},getTag:function(){return this.tag||this.element},updateOptions:function(t){for(var e in t){var s=Object(y["a"])(e);-1===N.indexOf(s)&&this._sortable.option(s,t[e])}},getChildrenNodes:function(){if(this.noneFunctionalComponentMode)return this.$children[0].$slots.default;var t=this.$slots.default;return this.transitionMode?t[0].child.$slots.default:t},computeIndexes:function(){var t=this;this.$nextTick((function(){t.visibleIndexes=b(t.getChildrenNodes(),t.rootContainer.children,t.transitionMode,t.footerOffset)}))},getUnderlyingVm:function(t){var e=x(this.getChildrenNodes()||[],t);if(-1===e)return null;var s=this.realList[e];return{index:e,element:s}},getUnderlyingPotencialDraggableComponent:function(t){var e=t.__vue__;return e&&e.$options&&P(e.$options._componentTag)?e.$parent:!("realList"in e)&&1===e.$children.length&&"realList"in e.$children[0]?e.$children[0]:e},emitChanges:function(t){var e=this;this.$nextTick((function(){e.$emit("change",t)}))},alterList:function(t){if(this.list)t(this.list);else{var e=d(this.value);t(e),this.$emit("input",e)}},spliceList:function(){var t=arguments,e=function(e){return e.splice.apply(e,d(t))};this.alterList(e)},updatePosition:function(t,e){var s=function(s){return s.splice(e,0,s.splice(t,1)[0])};this.alterList(s)},getRelatedContextFromMoveEvent:function(t){var e=t.to,s=t.related,i=this.getUnderlyingPotencialDraggableComponent(e);if(!i)return{component:i};var r=i.realList,n={list:r,component:i};if(e!==s&&r&&i.getUnderlyingVm){var a=i.getUnderlyingVm(s);if(a)return Object.assign(a,n)}return n},getVmIndex:function(t){var e=this.visibleIndexes,s=e.length;return t>s-1?s:e[t]},getComponent:function(){return this.$slots.default[0].componentInstance},resetTransitionData:function(t){if(this.noTransitionOnDrag&&this.transitionMode){var e=this.getChildrenNodes();e[t].data=null;var s=this.getComponent();s.children=[],s.kept=void 0}},onDragStart:function(t){this.context=this.getUnderlyingVm(t.item),t.item._underlying_vm_=this.clone(this.context.element),I=t.item},onDragAdd:function(t){var e=t.item._underlying_vm_;if(void 0!==e){Object(y["d"])(t.item);var s=this.getVmIndex(t.newIndex);this.spliceList(s,0,e),this.computeIndexes();var i={element:e,newIndex:s};this.emitChanges({added:i})}},onDragRemove:function(t){if(Object(y["c"])(this.rootContainer,t.item,t.oldIndex),"clone"!==t.pullMode){var e=this.context.index;this.spliceList(e,1);var s={element:this.context.element,oldIndex:e};this.resetTransitionData(e),this.emitChanges({removed:s})}else Object(y["d"])(t.clone)},onDragUpdate:function(t){Object(y["d"])(t.item),Object(y["c"])(t.from,t.item,t.oldIndex);var e=this.context.index,s=this.getVmIndex(t.newIndex);this.updatePosition(e,s);var i={element:this.context.element,oldIndex:e,newIndex:s};this.emitChanges({moved:i})},updateProperty:function(t,e){t.hasOwnProperty(e)&&(t[e]+=this.headerOffset)},computeFutureIndex:function(t,e){if(!t.element)return 0;var s=d(e.to.children).filter((function(t){return"none"!==t.style["display"]})),i=s.indexOf(e.related),r=t.component.getVmIndex(i),n=-1!==s.indexOf(I);return n||!e.willInsertAfter?r:r+1},onDragMove:function(t,e){var s=this.move;if(!s||!this.realList)return!0;var i=this.getRelatedContextFromMoveEvent(t),r=this.context,n=this.computeFutureIndex(i,t);Object.assign(r,{futureIndex:n});var a=Object.assign({},t,{relatedContext:i,draggedContext:r});return s(a,e)},onDragEnd:function(){this.computeIndexes(),I=null}}};"undefined"!==typeof window&&"Vue"in window&&window.Vue.component("draggable",D);var M=D;e["default"]=M}})["default"]},"342f":function(t,e,s){var i=s("d066");t.exports=i("navigator","userAgent")||""},"35a1":function(t,e,s){var i=s("f5df"),r=s("3f8c"),n=s("b622"),a=n("iterator");t.exports=function(t){if(void 0!=t)return t[a]||t["@@iterator"]||r[i(t)]}},"37e8":function(t,e,s){var i=s("83ab"),r=s("9bf2"),n=s("825a"),a=s("df75");t.exports=i?Object.defineProperties:function(t,e){n(t);var s,i=a(e),o=i.length,c=0;while(o>c)r.f(t,s=i[c++],e[s]);return t}},"3bbe":function(t,e,s){var i=s("861d");t.exports=function(t){if(!i(t)&&null!==t)throw TypeError("Can't set "+String(t)+" as a prototype");return t}},"3ca3":function(t,e,s){"use strict";var i=s("6547").charAt,r=s("69f3"),n=s("7dd0"),a="String Iterator",o=r.set,c=r.getterFor(a);n(String,"String",(function(t){o(this,{type:a,string:String(t),index:0})}),(function(){var t,e=c(this),s=e.string,r=e.index;return r>=s.length?{value:void 0,done:!0}:(t=i(s,r),e.index+=t.length,{value:t,done:!1})}))},"3f8c":function(t,e){t.exports={}},4160:function(t,e,s){"use strict";var i=s("23e7"),r=s("17c2");i({target:"Array",proto:!0,forced:[].forEach!=r},{forEach:r})},"428f":function(t,e,s){var i=s("da84");t.exports=i},4362:function(t,e,s){e.nextTick=function(t){var e=Array.prototype.slice.call(arguments);e.shift(),setTimeout((function(){t.apply(null,e)}),0)},e.platform=e.arch=e.execPath=e.title="browser",e.pid=1,e.browser=!0,e.env={},e.argv=[],e.binding=function(t){throw new Error("No such module. (Possibly not yet loaded)")},function(){var t,i="/";e.cwd=function(){return i},e.chdir=function(e){t||(t=s("df7c")),i=t.resolve(e,i)}}(),e.exit=e.kill=e.umask=e.dlopen=e.uptime=e.memoryUsage=e.uvCounters=function(){},e.features={}},"44ad":function(t,e,s){var i=s("d039"),r=s("c6b6"),n="".split;t.exports=i((function(){return!Object("z").propertyIsEnumerable(0)}))?function(t){return"String"==r(t)?n.call(t,""):Object(t)}:Object},"44d2":function(t,e,s){var i=s("b622"),r=s("7c73"),n=s("9bf2"),a=i("unscopables"),o=Array.prototype;void 0==o[a]&&n.f(o,a,{configurable:!0,value:r(null)}),t.exports=function(t){o[a][t]=!0}},"44de":function(t,e,s){var i=s("da84");t.exports=function(t,e){var s=i.console;s&&s.error&&(1===arguments.length?s.error(t):s.error(t,e))}},"44e7":function(t,e,s){var i=s("861d"),r=s("c6b6"),n=s("b622"),a=n("match");t.exports=function(t){var e;return i(t)&&(void 0!==(e=t[a])?!!e:"RegExp"==r(t))}},"45fc":function(t,e,s){"use strict";var i=s("23e7"),r=s("b727").some,n=s("a640"),a=s("ae40"),o=n("some"),c=a("some");i({target:"Array",proto:!0,forced:!o||!c},{some:function(t){return r(this,t,arguments.length>1?arguments[1]:void 0)}})},4840:function(t,e,s){var i=s("825a"),r=s("1c0b"),n=s("b622"),a=n("species");t.exports=function(t,e){var s,n=i(t).constructor;return void 0===n||void 0==(s=i(n)[a])?e:r(s)}},4930:function(t,e,s){var i=s("d039");t.exports=!!Object.getOwnPropertySymbols&&!i((function(){return!String(Symbol())}))},"4d63":function(t,e,s){var i=s("83ab"),r=s("da84"),n=s("94ca"),a=s("7156"),o=s("9bf2").f,c=s("241c").f,h=s("44e7"),l=s("ad6d"),p=s("9f7f"),u=s("6eeb"),d=s("d039"),f=s("69f3").set,m=s("2626"),y=s("b622"),g=y("match"),x=r.RegExp,b=x.prototype,v=/a/g,w=/a/g,P=new x(v)!==v,T=p.UNSUPPORTED_Y,E=i&&n("RegExp",!P||T||d((function(){return w[g]=!1,x(v)!=v||x(w)==w||"/a/i"!=x(v,"i")})));if(E){var A=function(t,e){var s,i=this instanceof A,r=h(t),n=void 0===e;if(!i&&r&&t.constructor===A&&n)return t;P?r&&!n&&(t=t.source):t instanceof A&&(n&&(e=l.call(t)),t=t.source),T&&(s=!!e&&e.indexOf("y")>-1,s&&(e=e.replace(/y/g,"")));var o=a(P?new x(t,e):x(t,e),i?this:b,A);return T&&s&&f(o,{sticky:s}),o},S=function(t){t in A||o(A,t,{configurable:!0,get:function(){return x[t]},set:function(e){x[t]=e}})},C=c(x),k=0;while(C.length>k)S(C[k++]);b.constructor=A,A.prototype=b,u(r,"RegExp",A)}m("RegExp")},"4d64":function(t,e,s){var i=s("fc6a"),r=s("50c4"),n=s("23cb"),a=function(t){return function(e,s,a){var o,c=i(e),h=r(c.length),l=n(a,h);if(t&&s!=s){while(h>l)if(o=c[l++],o!=o)return!0}else for(;h>l;l++)if((t||l in c)&&c[l]===s)return t||l||0;return!t&&-1}};t.exports={includes:a(!0),indexOf:a(!1)}},"4de4":function(t,e,s){"use strict";var i=s("23e7"),r=s("b727").filter,n=s("1dde"),a=s("ae40"),o=n("filter"),c=a("filter");i({target:"Array",proto:!0,forced:!o||!c},{filter:function(t){return r(this,t,arguments.length>1?arguments[1]:void 0)}})},"50c4":function(t,e,s){var i=s("a691"),r=Math.min;t.exports=function(t){return t>0?r(i(t),9007199254740991):0}},5135:function(t,e){var s={}.hasOwnProperty;t.exports=function(t,e){return s.call(t,e)}},5319:function(t,e,s){"use strict";var i=s("d784"),r=s("825a"),n=s("7b0b"),a=s("50c4"),o=s("a691"),c=s("1d80"),h=s("8aa5"),l=s("14c3"),p=Math.max,u=Math.min,d=Math.floor,f=/\$([$&'`]|\d\d?|<[^>]*>)/g,m=/\$([$&'`]|\d\d?)/g,y=function(t){return void 0===t?t:String(t)};i("replace",2,(function(t,e,s,i){var g=i.REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE,x=i.REPLACE_KEEPS_$0,b=g?"$":"$0";return[function(s,i){var r=c(this),n=void 0==s?void 0:s[t];return void 0!==n?n.call(s,r,i):e.call(String(r),s,i)},function(t,i){if(!g&&x||"string"===typeof i&&-1===i.indexOf(b)){var n=s(e,t,this,i);if(n.done)return n.value}var c=r(t),d=String(this),f="function"===typeof i;f||(i=String(i));var m=c.global;if(m){var w=c.unicode;c.lastIndex=0}var P=[];while(1){var T=l(c,d);if(null===T)break;if(P.push(T),!m)break;var E=String(T[0]);""===E&&(c.lastIndex=h(d,a(c.lastIndex),w))}for(var A="",S=0,C=0;C=S&&(A+=d.slice(S,N)+L,S=N+k.length)}return A+d.slice(S)}];function v(t,s,i,r,a,o){var c=i+t.length,h=r.length,l=m;return void 0!==a&&(a=n(a),l=f),e.call(o,l,(function(e,n){var o;switch(n.charAt(0)){case"$":return"$";case"&":return t;case"`":return s.slice(0,i);case"'":return s.slice(c);case"<":o=a[n.slice(1,-1)];break;default:var l=+n;if(0===l)return e;if(l>h){var p=d(l/10);return 0===p?e:p<=h?void 0===r[p-1]?n.charAt(1):r[p-1]+n.charAt(1):e}o=r[l-1]}return void 0===o?"":o}))}}))},"53ca":function(t,e,s){"use strict";s.d(e,"a",(function(){return i}));s("a4d3"),s("e01a"),s("d28b"),s("d3b7"),s("3ca3"),s("ddb0");function i(t){return i="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"===typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i(t)}},5530:function(t,e,s){"use strict";s.d(e,"a",(function(){return n}));s("a4d3"),s("4de4"),s("4160"),s("e439"),s("dbb4"),s("b64b"),s("159b");var i=s("ade3");function r(t,e){var s=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),s.push.apply(s,i)}return s}function n(t){for(var e=1;el){var d,f=h(arguments[l++]),m=p?n(f).concat(p(f)):n(f),y=m.length,g=0;while(y>g)d=m[g++],i&&!u.call(f,d)||(s[d]=f[d])}return s}:l},6547:function(t,e,s){var i=s("a691"),r=s("1d80"),n=function(t){return function(e,s){var n,a,o=String(r(e)),c=i(s),h=o.length;return c<0||c>=h?t?"":void 0:(n=o.charCodeAt(c),n<55296||n>56319||c+1===h||(a=o.charCodeAt(c+1))<56320||a>57343?t?o.charAt(c):n:t?o.slice(c,c+2):a-56320+(n-55296<<10)+65536)}};t.exports={codeAt:n(!1),charAt:n(!0)}},"65f0":function(t,e,s){var i=s("861d"),r=s("e8b5"),n=s("b622"),a=n("species");t.exports=function(t,e){var s;return r(t)&&(s=t.constructor,"function"!=typeof s||s!==Array&&!r(s.prototype)?i(s)&&(s=s[a],null===s&&(s=void 0)):s=void 0),new(void 0===s?Array:s)(0===e?0:e)}},"69f3":function(t,e,s){var i,r,n,a=s("7f9a"),o=s("da84"),c=s("861d"),h=s("9112"),l=s("5135"),p=s("f772"),u=s("d012"),d=o.WeakMap,f=function(t){return n(t)?r(t):i(t,{})},m=function(t){return function(e){var s;if(!c(e)||(s=r(e)).type!==t)throw TypeError("Incompatible receiver, "+t+" required");return s}};if(a){var y=new d,g=y.get,x=y.has,b=y.set;i=function(t,e){return b.call(y,t,e),e},r=function(t){return g.call(y,t)||{}},n=function(t){return x.call(y,t)}}else{var v=p("state");u[v]=!0,i=function(t,e){return h(t,v,e),e},r=function(t){return l(t,v)?t[v]:{}},n=function(t){return l(t,v)}}t.exports={set:i,get:r,has:n,enforce:f,getterFor:m}},"6eeb":function(t,e,s){var i=s("da84"),r=s("9112"),n=s("5135"),a=s("ce4e"),o=s("8925"),c=s("69f3"),h=c.get,l=c.enforce,p=String(String).split("String");(t.exports=function(t,e,s,o){var c=!!o&&!!o.unsafe,h=!!o&&!!o.enumerable,u=!!o&&!!o.noTargetGet;"function"==typeof s&&("string"!=typeof e||n(s,"name")||r(s,"name",e),l(s).source=p.join("string"==typeof e?e:"")),t!==i?(c?!u&&t[e]&&(h=!0):delete t[e],h?t[e]=s:r(t,e,s)):h?t[e]=s:a(e,s)})(Function.prototype,"toString",(function(){return"function"==typeof this&&h(this).source||o(this)}))},7156:function(t,e,s){var i=s("861d"),r=s("d2bb");t.exports=function(t,e,s){var n,a;return r&&"function"==typeof(n=e.constructor)&&n!==s&&i(a=n.prototype)&&a!==s.prototype&&r(t,a),t}},7418:function(t,e){e.f=Object.getOwnPropertySymbols},"746f":function(t,e,s){var i=s("428f"),r=s("5135"),n=s("e538"),a=s("9bf2").f;t.exports=function(t){var e=i.Symbol||(i.Symbol={});r(e,t)||a(e,t,{value:n.f(t)})}},7839:function(t,e){t.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},"7b0b":function(t,e,s){var i=s("1d80");t.exports=function(t){return Object(i(t))}},"7c73":function(t,e,s){var i,r=s("825a"),n=s("37e8"),a=s("7839"),o=s("d012"),c=s("1be4"),h=s("cc12"),l=s("f772"),p=">",u="<",d="prototype",f="script",m=l("IE_PROTO"),y=function(){},g=function(t){return u+f+p+t+u+"/"+f+p},x=function(t){t.write(g("")),t.close();var e=t.parentWindow.Object;return t=null,e},b=function(){var t,e=h("iframe"),s="java"+f+":";return e.style.display="none",c.appendChild(e),e.src=String(s),t=e.contentWindow.document,t.open(),t.write(g("document.F=Object")),t.close(),t.F},v=function(){try{i=document.domain&&new ActiveXObject("htmlfile")}catch(e){}v=i?x(i):b();var t=a.length;while(t--)delete v[d][a[t]];return v()};o[m]=!0,t.exports=Object.create||function(t,e){var s;return null!==t?(y[d]=r(t),s=new y,y[d]=null,s[m]=t):s=v(),void 0===e?s:n(s,e)}},"7db0":function(t,e,s){"use strict";var i=s("23e7"),r=s("b727").find,n=s("44d2"),a=s("ae40"),o="find",c=!0,h=a(o);o in[]&&Array(1)[o]((function(){c=!1})),i({target:"Array",proto:!0,forced:c||!h},{find:function(t){return r(this,t,arguments.length>1?arguments[1]:void 0)}}),n(o)},"7dd0":function(t,e,s){"use strict";var i=s("23e7"),r=s("9ed3"),n=s("e163"),a=s("d2bb"),o=s("d44e"),c=s("9112"),h=s("6eeb"),l=s("b622"),p=s("c430"),u=s("3f8c"),d=s("ae93"),f=d.IteratorPrototype,m=d.BUGGY_SAFARI_ITERATORS,y=l("iterator"),g="keys",x="values",b="entries",v=function(){return this};t.exports=function(t,e,s,l,d,w,P){r(s,e,l);var T,E,A,S=function(t){if(t===d&&O)return O;if(!m&&t in N)return N[t];switch(t){case g:return function(){return new s(this,t)};case x:return function(){return new s(this,t)};case b:return function(){return new s(this,t)}}return function(){return new s(this)}},C=e+" Iterator",k=!1,N=t.prototype,I=N[y]||N["@@iterator"]||d&&N[d],O=!m&&I||S(d),D="Array"==e&&N.entries||I;if(D&&(T=n(D.call(new t)),f!==Object.prototype&&T.next&&(p||n(T)===f||(a?a(T,f):"function"!=typeof T[y]&&c(T,y,v)),o(T,C,!0,!0),p&&(u[C]=v))),d==x&&I&&I.name!==x&&(k=!0,O=function(){return I.call(this)}),p&&!P||N[y]===O||c(N,y,O),u[e]=O,d)if(E={values:S(x),keys:w?O:S(g),entries:S(b)},P)for(A in E)(m||k||!(A in N))&&h(N,A,E[A]);else i({target:e,proto:!0,forced:m||k},E);return E}},"7f9a":function(t,e,s){var i=s("da84"),r=s("8925"),n=i.WeakMap;t.exports="function"===typeof n&&/native code/.test(r(n))},"825a":function(t,e,s){var i=s("861d");t.exports=function(t){if(!i(t))throw TypeError(String(t)+" is not an object");return t}},"83ab":function(t,e,s){var i=s("d039");t.exports=!i((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},8418:function(t,e,s){"use strict";var i=s("c04e"),r=s("9bf2"),n=s("5c6c");t.exports=function(t,e,s){var a=i(e);a in t?r.f(t,a,n(0,s)):t[a]=s}},"861d":function(t,e){t.exports=function(t){return"object"===typeof t?null!==t:"function"===typeof t}},8925:function(t,e,s){var i=s("c6cd"),r=Function.toString;"function"!=typeof i.inspectSource&&(i.inspectSource=function(t){return r.call(t)}),t.exports=i.inspectSource},"8a79":function(t,e,s){"use strict";var i=s("23e7"),r=s("06cf").f,n=s("50c4"),a=s("5a34"),o=s("1d80"),c=s("ab13"),h=s("c430"),l="".endsWith,p=Math.min,u=c("endsWith"),d=!h&&!u&&!!function(){var t=r(String.prototype,"endsWith");return t&&!t.writable}();i({target:"String",proto:!0,forced:!d&&!u},{endsWith:function(t){var e=String(o(this));a(t);var s=arguments.length>1?arguments[1]:void 0,i=n(e.length),r=void 0===s?i:p(n(s),i),c=String(t);return l?l.call(e,c,r):e.slice(r-c.length,r)===c}})},"8aa5":function(t,e,s){"use strict";var i=s("6547").charAt;t.exports=function(t,e,s){return e+(s?i(t,e).length:1)}},"90e3":function(t,e){var s=0,i=Math.random();t.exports=function(t){return"Symbol("+String(void 0===t?"":t)+")_"+(++s+i).toString(36)}},9112:function(t,e,s){var i=s("83ab"),r=s("9bf2"),n=s("5c6c");t.exports=i?function(t,e,s){return r.f(t,e,n(1,s))}:function(t,e,s){return t[e]=s,t}},9263:function(t,e,s){"use strict";var i=s("ad6d"),r=s("9f7f"),n=RegExp.prototype.exec,a=String.prototype.replace,o=n,c=function(){var t=/a/,e=/b*/g;return n.call(t,"a"),n.call(e,"a"),0!==t.lastIndex||0!==e.lastIndex}(),h=r.UNSUPPORTED_Y||r.BROKEN_CARET,l=void 0!==/()??/.exec("")[1],p=c||l||h;p&&(o=function(t){var e,s,r,o,p=this,u=h&&p.sticky,d=i.call(p),f=p.source,m=0,y=t;return u&&(d=d.replace("y",""),-1===d.indexOf("g")&&(d+="g"),y=String(t).slice(p.lastIndex),p.lastIndex>0&&(!p.multiline||p.multiline&&"\n"!==t[p.lastIndex-1])&&(f="(?: "+f+")",y=" "+y,m++),s=new RegExp("^(?:"+f+")",d)),l&&(s=new RegExp("^"+f+"$(?!\\s)",d)),c&&(e=p.lastIndex),r=n.call(u?s:p,y),u?r?(r.input=r.input.slice(m),r[0]=r[0].slice(m),r.index=p.lastIndex,p.lastIndex+=r[0].length):p.lastIndex=0:c&&r&&(p.lastIndex=p.global?r.index+r[0].length:e),l&&r&&r.length>1&&a.call(r[0],s,(function(){for(o=1;o=51||!r((function(){var t=[];return t[f]=!1,t.concat()[0]!==t})),x=p("concat"),b=function(t){if(!a(t))return!1;var e=t[f];return void 0!==e?!!e:n(t)},v=!g||!x;i({target:"Array",proto:!0,forced:v},{concat:function(t){var e,s,i,r,n,a=o(this),p=l(a,0),u=0;for(e=-1,i=arguments.length;em)throw TypeError(y);for(s=0;s=m)throw TypeError(y);h(p,u++,n)}return p.length=u,p}})},"9bdd":function(t,e,s){var i=s("825a");t.exports=function(t,e,s,r){try{return r?e(i(s)[0],s[1]):e(s)}catch(a){var n=t["return"];throw void 0!==n&&i(n.call(t)),a}}},"9bf2":function(t,e,s){var i=s("83ab"),r=s("0cfb"),n=s("825a"),a=s("c04e"),o=Object.defineProperty;e.f=i?o:function(t,e,s){if(n(t),e=a(e,!0),n(s),r)try{return o(t,e,s)}catch(i){}if("get"in s||"set"in s)throw TypeError("Accessors not supported");return"value"in s&&(t[e]=s.value),t}},"9ed3":function(t,e,s){"use strict";var i=s("ae93").IteratorPrototype,r=s("7c73"),n=s("5c6c"),a=s("d44e"),o=s("3f8c"),c=function(){return this};t.exports=function(t,e,s){var h=e+" Iterator";return t.prototype=r(i,{next:n(1,s)}),a(t,h,!1,!0),o[h]=c,t}},"9f7f":function(t,e,s){"use strict";var i=s("d039");function r(t,e){return RegExp(t,e)}e.UNSUPPORTED_Y=i((function(){var t=r("a","y");return t.lastIndex=2,null!=t.exec("abcd")})),e.BROKEN_CARET=i((function(){var t=r("^r","gy");return t.lastIndex=2,null!=t.exec("str")}))},a15b:function(t,e,s){"use strict";var i=s("23e7"),r=s("44ad"),n=s("fc6a"),a=s("a640"),o=[].join,c=r!=Object,h=a("join",",");i({target:"Array",proto:!0,forced:c||!h},{join:function(t){return o.call(n(this),void 0===t?",":t)}})},a434:function(t,e,s){"use strict";var i=s("23e7"),r=s("23cb"),n=s("a691"),a=s("50c4"),o=s("7b0b"),c=s("65f0"),h=s("8418"),l=s("1dde"),p=s("ae40"),u=l("splice"),d=p("splice",{ACCESSORS:!0,0:0,1:2}),f=Math.max,m=Math.min,y=9007199254740991,g="Maximum allowed length exceeded";i({target:"Array",proto:!0,forced:!u||!d},{splice:function(t,e){var s,i,l,p,u,d,x=o(this),b=a(x.length),v=r(t,b),w=arguments.length;if(0===w?s=i=0:1===w?(s=0,i=b-v):(s=w-2,i=m(f(n(e),0),b-v)),b+s-i>y)throw TypeError(g);for(l=c(x,i),p=0;pb-i+s;p--)delete x[p-1]}else if(s>i)for(p=b-i;p>v;p--)u=p+i-1,d=p+s-1,u in x?x[d]=x[u]:delete x[d];for(p=0;pn)r.push(arguments[n++]);if(i=e,(d(e)||void 0!==t)&&!ot(t))return u(e)||(e=function(t,e){if("function"==typeof i&&(e=i.call(this,t,e)),!ot(e))return e}),r[1]=e,$.apply(null,r)}})}K[q][V]||C(K[q],V,K[q].valueOf),R(K,U),O[B]=!0},a640:function(t,e,s){"use strict";var i=s("d039");t.exports=function(t,e){var s=[][t];return!!s&&i((function(){s.call(null,e||function(){throw 1},1)}))}},a691:function(t,e){var s=Math.ceil,i=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?i:s)(t)}},a79d:function(t,e,s){"use strict";var i=s("23e7"),r=s("c430"),n=s("fea9"),a=s("d039"),o=s("d066"),c=s("4840"),h=s("cdf9"),l=s("6eeb"),p=!!n&&a((function(){n.prototype["finally"].call({then:function(){}},(function(){}))}));i({target:"Promise",proto:!0,real:!0,forced:p},{finally:function(t){var e=c(this,o("Promise")),s="function"==typeof t;return this.then(s?function(s){return h(e,t()).then((function(){return s}))}:t,s?function(s){return h(e,t()).then((function(){throw s}))}:t)}}),r||"function"!=typeof n||n.prototype["finally"]||l(n.prototype,"finally",o("Promise").prototype["finally"])},aa47:function(t,e,s){"use strict"; +/**! + * Sortable 1.10.2 + * @author RubaXa + * @author owenm + * @license MIT + */ +function i(t){return i="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"===typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i(t)}function r(t,e,s){return e in t?Object.defineProperty(t,e,{value:s,enumerable:!0,configurable:!0,writable:!0}):t[e]=s,t}function n(){return n=Object.assign||function(t){for(var e=1;e=0||(r[s]=t[s]);return r}function c(t,e){if(null==t)return{};var s,i,r=o(t,e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(t,s)&&(r[s]=t[s])}return r}function h(t){return l(t)||p(t)||u()}function l(t){if(Array.isArray(t)){for(var e=0,s=new Array(t.length);e"===e[0]&&(e=e.substring(1)),t)try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch(s){return!1}return!1}}function A(t){return t.host&&t!==document&&t.host.nodeType?t.host:t.parentNode}function S(t,e,s,i){if(t){s=s||document;do{if(null!=e&&(">"===e[0]?t.parentNode===s&&E(t,e):E(t,e))||i&&t===s)return t;if(t===s)break}while(t=A(t))}return null}var C,k=/\s+/g;function N(t,e,s){if(t&&e)if(t.classList)t.classList[s?"add":"remove"](e);else{var i=(" "+t.className+" ").replace(k," ").replace(" "+e+" "," ");t.className=(i+(s?" "+e:"")).replace(k," ")}}function I(t,e,s){var i=t&&t.style;if(i){if(void 0===s)return document.defaultView&&document.defaultView.getComputedStyle?s=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(s=t.currentStyle),void 0===e?s:s[e];e in i||-1!==e.indexOf("webkit")||(e="-webkit-"+e),i[e]=s+("string"===typeof s?"":"px")}}function O(t,e){var s="";if("string"===typeof t)s=t;else do{var i=I(t,"transform");i&&"none"!==i&&(s=i+" "+s)}while(!e&&(t=t.parentNode));var r=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return r&&new r(s)}function D(t,e,s){if(t){var i=t.getElementsByTagName(e),r=0,n=i.length;if(s)for(;r=n:r<=n,!a)return i;if(i===M())break;i=q(i,!1)}return!1}function R(t,e,s){var i=0,r=0,n=t.children;while(r2&&void 0!==arguments[2]?arguments[2]:{},i=s.evt,r=c(s,["evt"]);st.pluginEvent.bind(Qt)(t,e,a({dragEl:at,parentEl:ot,ghostEl:ct,rootEl:ht,nextEl:lt,lastDownEl:pt,cloneEl:ut,cloneHidden:dt,dragStarted:St,putSortable:bt,activeSortable:Qt.active,originalEvent:i,oldIndex:ft,oldDraggableIndex:yt,newIndex:mt,newDraggableIndex:gt,hideGhostForTarget:Xt,unhideGhostForTarget:Gt,cloneNowHidden:function(){dt=!0},cloneNowShown:function(){dt=!1},dispatchSortableEvent:function(t){nt({sortable:e,name:t,originalEvent:i})}},r))};function nt(t){it(a({putSortable:bt,cloneEl:ut,targetEl:at,rootEl:ht,oldIndex:ft,oldDraggableIndex:yt,newIndex:mt,newDraggableIndex:gt},t))}var at,ot,ct,ht,lt,pt,ut,dt,ft,mt,yt,gt,xt,bt,vt,wt,Pt,Tt,Et,At,St,Ct,kt,Nt,It,Ot=!1,Dt=!1,Mt=[],Lt=!1,_t=!1,Rt=[],jt=!1,Ft=[],Bt="undefined"!==typeof document,Ut=b,qt=y||m?"cssFloat":"float",Vt=Bt&&!v&&!b&&"draggable"in document.createElement("div"),Ht=function(){if(Bt){if(m)return!1;var t=document.createElement("x");return t.style.cssText="pointer-events:auto","auto"===t.style.pointerEvents}}(),zt=function(t,e){var s=I(t),i=parseInt(s.width)-parseInt(s.paddingLeft)-parseInt(s.paddingRight)-parseInt(s.borderLeftWidth)-parseInt(s.borderRightWidth),r=R(t,0,e),n=R(t,1,e),a=r&&I(r),o=n&&I(n),c=a&&parseInt(a.marginLeft)+parseInt(a.marginRight)+L(r).width,h=o&&parseInt(o.marginLeft)+parseInt(o.marginRight)+L(n).width;if("flex"===s.display)return"column"===s.flexDirection||"column-reverse"===s.flexDirection?"vertical":"horizontal";if("grid"===s.display)return s.gridTemplateColumns.split(" ").length<=1?"vertical":"horizontal";if(r&&a["float"]&&"none"!==a["float"]){var l="left"===a["float"]?"left":"right";return!n||"both"!==o.clear&&o.clear!==l?"horizontal":"vertical"}return r&&("block"===a.display||"flex"===a.display||"table"===a.display||"grid"===a.display||c>=i&&"none"===s[qt]||n&&"none"===s[qt]&&c+h>i)?"vertical":"horizontal"},Wt=function(t,e,s){var i=s?t.left:t.top,r=s?t.right:t.bottom,n=s?t.width:t.height,a=s?e.left:e.top,o=s?e.right:e.bottom,c=s?e.width:e.height;return i===a||r===o||i+n/2===a+c/2},Kt=function(t,e){var s;return Mt.some((function(i){if(!j(i)){var r=L(i),n=i[Y].options.emptyInsertThreshold,a=t>=r.left-n&&t<=r.right+n,o=e>=r.top-n&&e<=r.bottom+n;return n&&a&&o?s=i:void 0}})),s},$t=function(t){function e(t,s){return function(i,r,n,a){var o=i.options.group.name&&r.options.group.name&&i.options.group.name===r.options.group.name;if(null==t&&(s||o))return!0;if(null==t||!1===t)return!1;if(s&&"clone"===t)return t;if("function"===typeof t)return e(t(i,r,n,a),s)(i,r,n,a);var c=(s?i:r).options.group.name;return!0===t||"string"===typeof t&&t===c||t.join&&t.indexOf(c)>-1}}var s={},r=t.group;r&&"object"==i(r)||(r={name:r}),s.name=r.name,s.checkPull=e(r.pull,!0),s.checkPut=e(r.put),s.revertClone=r.revertClone,t.group=s},Xt=function(){!Ht&&ct&&I(ct,"display","none")},Gt=function(){!Ht&&ct&&I(ct,"display","")};Bt&&document.addEventListener("click",(function(t){if(Dt)return t.preventDefault(),t.stopPropagation&&t.stopPropagation(),t.stopImmediatePropagation&&t.stopImmediatePropagation(),Dt=!1,!1}),!0);var Yt=function(t){if(at){t=t.touches?t.touches[0]:t;var e=Kt(t.clientX,t.clientY);if(e){var s={};for(var i in t)t.hasOwnProperty(i)&&(s[i]=t[i]);s.target=s.rootEl=e,s.preventDefault=void 0,s.stopPropagation=void 0,e[Y]._onDragOver(s)}}},Jt=function(t){at&&at.parentNode[Y]._isOutsideThisEl(t.target)};function Qt(t,e){if(!t||!t.nodeType||1!==t.nodeType)throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(t));this.el=t,this.options=e=n({},e),t[Y]=this;var s={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(t.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return zt(t,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==Qt.supportPointer&&"PointerEvent"in window,emptyInsertThreshold:5};for(var i in st.initializePlugins(this,t,s),s)!(i in e)&&(e[i]=s[i]);for(var r in $t(e),this)"_"===r.charAt(0)&&"function"===typeof this[r]&&(this[r]=this[r].bind(this));this.nativeDraggable=!e.forceFallback&&Vt,this.nativeDraggable&&(this.options.touchStartThreshold=1),e.supportPointer?P(t,"pointerdown",this._onTapStart):(P(t,"mousedown",this._onTapStart),P(t,"touchstart",this._onTapStart)),this.nativeDraggable&&(P(t,"dragover",this),P(t,"dragenter",this)),Mt.push(this.el),e.store&&e.store.get&&this.sort(e.store.get(this)||[]),n(this,J())}function Zt(t){t.dataTransfer&&(t.dataTransfer.dropEffect="move"),t.cancelable&&t.preventDefault()}function te(t,e,s,i,r,n,a,o){var c,h,l=t[Y],p=l.options.onMove;return!window.CustomEvent||m||y?(c=document.createEvent("Event"),c.initEvent("move",!0,!0)):c=new CustomEvent("move",{bubbles:!0,cancelable:!0}),c.to=e,c.from=t,c.dragged=s,c.draggedRect=i,c.related=r||e,c.relatedRect=n||L(e),c.willInsertAfter=o,c.originalEvent=a,t.dispatchEvent(c),p&&(h=p.call(l,c,a)),h}function ee(t){t.draggable=!1}function se(){jt=!1}function ie(t,e,s){var i=L(j(s.el,s.options.draggable)),r=10;return e?t.clientX>i.right+r||t.clientX<=i.right&&t.clientY>i.bottom&&t.clientX>=i.left:t.clientX>i.right&&t.clientY>i.top||t.clientX<=i.right&&t.clientY>i.bottom+r}function re(t,e,s,i,r,n,a,o){var c=i?t.clientY:t.clientX,h=i?s.height:s.width,l=i?s.top:s.left,p=i?s.bottom:s.right,u=!1;if(!a)if(o&&Ntl+h*n/2:cp-Nt)return-kt}else if(c>l+h*(1-r)/2&&cp-h*n/2)?c>l+h/2?1:-1:0}function ne(t){return F(at)=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){at&&ee(at),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var t=this.el.ownerDocument;T(t,"mouseup",this._disableDelayedDrag),T(t,"touchend",this._disableDelayedDrag),T(t,"touchcancel",this._disableDelayedDrag),T(t,"mousemove",this._delayedDragTouchMoveHandler),T(t,"touchmove",this._delayedDragTouchMoveHandler),T(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,e){e=e||"touch"==t.pointerType&&t,!this.nativeDraggable||e?this.options.supportPointer?P(document,"pointermove",this._onTouchMove):P(document,e?"touchmove":"mousemove",this._onTouchMove):(P(at,"dragend",this),P(ht,"dragstart",this._onDragStart));try{document.selection?ce((function(){document.selection.empty()})):window.getSelection().removeAllRanges()}catch(s){}},_dragStarted:function(t,e){if(Ot=!1,ht&&at){rt("dragStarted",this,{evt:e}),this.nativeDraggable&&P(document,"dragover",Jt);var s=this.options;!t&&N(at,s.dragClass,!1),N(at,s.ghostClass,!0),Qt.active=this,t&&this._appendGhost(),nt({sortable:this,name:"start",originalEvent:e})}else this._nulling()},_emulateDragOver:function(){if(wt){this._lastX=wt.clientX,this._lastY=wt.clientY,Xt();var t=document.elementFromPoint(wt.clientX,wt.clientY),e=t;while(t&&t.shadowRoot){if(t=t.shadowRoot.elementFromPoint(wt.clientX,wt.clientY),t===e)break;e=t}if(at.parentNode[Y]._isOutsideThisEl(t),e)do{if(e[Y]){var s=void 0;if(s=e[Y]._onDragOver({clientX:wt.clientX,clientY:wt.clientY,target:t,rootEl:e}),s&&!this.options.dragoverBubble)break}t=e}while(e=e.parentNode);Gt()}},_onTouchMove:function(t){if(vt){var e=this.options,s=e.fallbackTolerance,i=e.fallbackOffset,r=t.touches?t.touches[0]:t,n=ct&&O(ct,!0),a=ct&&n&&n.a,o=ct&&n&&n.d,c=Ut&&It&&B(It),h=(r.clientX-vt.clientX+i.x)/(a||1)+(c?c[0]-Rt[0]:0)/(a||1),l=(r.clientY-vt.clientY+i.y)/(o||1)+(c?c[1]-Rt[1]:0)/(o||1);if(!Qt.active&&!Ot){if(s&&Math.max(Math.abs(r.clientX-this._lastX),Math.abs(r.clientY-this._lastY))=0&&(nt({rootEl:ot,name:"add",toEl:ot,fromEl:ht,originalEvent:t}),nt({sortable:this,name:"remove",toEl:ot,originalEvent:t}),nt({rootEl:ot,name:"sort",toEl:ot,fromEl:ht,originalEvent:t}),nt({sortable:this,name:"sort",toEl:ot,originalEvent:t})),bt&&bt.save()):mt!==ft&&mt>=0&&(nt({sortable:this,name:"update",toEl:ot,originalEvent:t}),nt({sortable:this,name:"sort",toEl:ot,originalEvent:t})),Qt.active&&(null!=mt&&-1!==mt||(mt=ft,gt=yt),nt({sortable:this,name:"end",toEl:ot,originalEvent:t}),this.save())))),this._nulling()},_nulling:function(){rt("nulling",this),ht=at=ot=ct=lt=ut=pt=dt=vt=wt=St=mt=gt=ft=yt=Ct=kt=bt=xt=Qt.dragged=Qt.ghost=Qt.clone=Qt.active=null,Ft.forEach((function(t){t.checked=!0})),Ft.length=Pt=Tt=0},handleEvent:function(t){switch(t.type){case"drop":case"dragend":this._onDrop(t);break;case"dragenter":case"dragover":at&&(this._onDragOver(t),Zt(t));break;case"selectstart":t.preventDefault();break}},toArray:function(){for(var t,e=[],s=this.el.children,i=0,r=s.length,n=this.options;i1&&(Me.forEach((function(t){i.addAnimationState({target:t,rect:Re?L(t):r}),G(t),t.fromRect=r,e.removeAnimationState(t)})),Re=!1,Be(!this.options.removeCloneOnHide,s))},dragOverCompleted:function(t){var e=t.sortable,s=t.isOwner,i=t.insertion,r=t.activeSortable,n=t.parentEl,a=t.putSortable,o=this.options;if(i){if(s&&r._hideClone(),_e=!1,o.animation&&Me.length>1&&(Re||!s&&!r.options.sort&&!a)){var c=L(Ie,!1,!0,!0);Me.forEach((function(t){t!==Ie&&(X(t,c),n.appendChild(t))})),Re=!0}if(!s)if(Re||qe(),Me.length>1){var h=De;r._showClone(e),r.options.animation&&!De&&h&&Le.forEach((function(t){r.addAnimationState({target:t,rect:Oe}),t.fromRect=Oe,t.thisAnimationDuration=null}))}else r._showClone(e)}},dragOverAnimationCapture:function(t){var e=t.dragRect,s=t.isOwner,i=t.activeSortable;if(Me.forEach((function(t){t.thisAnimationDuration=null})),i.options.animation&&!s&&i.multiDrag.isMultiDrag){Oe=n({},e);var r=O(Ie,!0);Oe.top-=r.f,Oe.left-=r.e}},dragOverAnimationComplete:function(){Re&&(Re=!1,qe())},drop:function(t){var e=t.originalEvent,s=t.rootEl,i=t.parentEl,r=t.sortable,n=t.dispatchSortableEvent,a=t.oldIndex,o=t.putSortable,c=o||this.sortable;if(e){var h=this.options,l=i.children;if(!je)if(h.multiDragKey&&!this.multiDragKeyDown&&this._deselectMultiDrag(),N(Ie,h.selectedClass,!~Me.indexOf(Ie)),~Me.indexOf(Ie))Me.splice(Me.indexOf(Ie),1),ke=null,it({sortable:r,rootEl:s,name:"deselect",targetEl:Ie,originalEvt:e});else{if(Me.push(Ie),it({sortable:r,rootEl:s,name:"select",targetEl:Ie,originalEvt:e}),e.shiftKey&&ke&&r.el.contains(ke)){var p,u,d=F(ke),f=F(Ie);if(~d&&~f&&d!==f)for(f>d?(u=d,p=f):(u=f,p=d+1);u1){var m=L(Ie),y=F(Ie,":not(."+this.options.selectedClass+")");if(!_e&&h.animation&&(Ie.thisAnimationDuration=null),c.captureAnimationState(),!_e&&(h.animation&&(Ie.fromRect=m,Me.forEach((function(t){if(t.thisAnimationDuration=null,t!==Ie){var e=Re?L(t):m;t.fromRect=e,c.addAnimationState({target:t,rect:e})}}))),qe(),Me.forEach((function(t){l[y]?i.insertBefore(t,l[y]):i.appendChild(t),y++})),a===F(Ie))){var g=!1;Me.forEach((function(t){t.sortableIndex===F(t)||(g=!0)})),g&&n("update")}Me.forEach((function(t){G(t)})),c.animateAll()}Ne=c}(s===i||o&&"clone"!==o.lastPutMode)&&Le.forEach((function(t){t.parentNode&&t.parentNode.removeChild(t)}))}},nullingGlobal:function(){this.isMultiDrag=je=!1,Le.length=0},destroyGlobal:function(){this._deselectMultiDrag(),T(document,"pointerup",this._deselectMultiDrag),T(document,"mouseup",this._deselectMultiDrag),T(document,"touchend",this._deselectMultiDrag),T(document,"keydown",this._checkKeyDown),T(document,"keyup",this._checkKeyUp)},_deselectMultiDrag:function(t){if(("undefined"===typeof je||!je)&&Ne===this.sortable&&(!t||!S(t.target,this.options.draggable,this.sortable.el,!1))&&(!t||0===t.button))while(Me.length){var e=Me[0];N(e,this.options.selectedClass,!1),Me.shift(),it({sortable:this.sortable,rootEl:this.sortable.el,name:"deselect",targetEl:e,originalEvt:t})}},_checkKeyDown:function(t){t.key===this.options.multiDragKey&&(this.multiDragKeyDown=!0)},_checkKeyUp:function(t){t.key===this.options.multiDragKey&&(this.multiDragKeyDown=!1)}},n(t,{pluginName:"multiDrag",utils:{select:function(t){var e=t.parentNode[Y];e&&e.options.multiDrag&&!~Me.indexOf(t)&&(Ne&&Ne!==e&&(Ne.multiDrag._deselectMultiDrag(),Ne=e),N(t,e.options.selectedClass,!0),Me.push(t))},deselect:function(t){var e=t.parentNode[Y],s=Me.indexOf(t);e&&e.options.multiDrag&&~s&&(N(t,e.options.selectedClass,!1),Me.splice(s,1))}},eventProperties:function(){var t=this,e=[],s=[];return Me.forEach((function(i){var r;e.push({multiDragElement:i,index:i.sortableIndex}),r=Re&&i!==Ie?-1:Re?F(i,":not(."+t.options.selectedClass+")"):F(i),s.push({multiDragElement:i,index:r})})),{items:h(Me),clones:[].concat(Le),oldIndicies:e,newIndicies:s}},optionListeners:{multiDragKey:function(t){return t=t.toLowerCase(),"ctrl"===t?t="Control":t.length>1&&(t=t.charAt(0).toUpperCase()+t.substr(1)),t}}})}function Be(t,e){Me.forEach((function(s,i){var r=e.children[s.sortableIndex+(t?Number(i):0)];r?e.insertBefore(s,r):e.appendChild(s)}))}function Ue(t,e){Le.forEach((function(s,i){var r=e.children[s.sortableIndex+(t?Number(i):0)];r?e.insertBefore(s,r):e.appendChild(s)}))}function qe(){Me.forEach((function(t){t!==Ie&&t.parentNode&&t.parentNode.removeChild(t)}))}Qt.mount(new xe),Qt.mount(Ae,Ee),e["default"]=Qt},ab13:function(t,e,s){var i=s("b622"),r=i("match");t.exports=function(t){var e=/./;try{"/./"[t](e)}catch(s){try{return e[r]=!1,"/./"[t](e)}catch(i){}}return!1}},ac1f:function(t,e,s){"use strict";var i=s("23e7"),r=s("9263");i({target:"RegExp",proto:!0,forced:/./.exec!==r},{exec:r})},ad6d:function(t,e,s){"use strict";var i=s("825a");t.exports=function(){var t=i(this),e="";return t.global&&(e+="g"),t.ignoreCase&&(e+="i"),t.multiline&&(e+="m"),t.dotAll&&(e+="s"),t.unicode&&(e+="u"),t.sticky&&(e+="y"),e}},ade3:function(t,e,s){"use strict";function i(t,e,s){return e in t?Object.defineProperty(t,e,{value:s,enumerable:!0,configurable:!0,writable:!0}):t[e]=s,t}s.d(e,"a",(function(){return i}))},ae40:function(t,e,s){var i=s("83ab"),r=s("d039"),n=s("5135"),a=Object.defineProperty,o={},c=function(t){throw t};t.exports=function(t,e){if(n(o,t))return o[t];e||(e={});var s=[][t],h=!!n(e,"ACCESSORS")&&e.ACCESSORS,l=n(e,0)?e[0]:c,p=n(e,1)?e[1]:void 0;return o[t]=!!s&&!r((function(){if(h&&!i)return!0;var t={length:-1};h?a(t,1,{enumerable:!0,get:c}):t[1]=1,s.call(t,l,p)}))}},ae93:function(t,e,s){"use strict";var i,r,n,a=s("e163"),o=s("9112"),c=s("5135"),h=s("b622"),l=s("c430"),p=h("iterator"),u=!1,d=function(){return this};[].keys&&(n=[].keys(),"next"in n?(r=a(a(n)),r!==Object.prototype&&(i=r)):u=!0),void 0==i&&(i={}),l||c(i,p)||o(i,p,d),t.exports={IteratorPrototype:i,BUGGY_SAFARI_ITERATORS:u}},b041:function(t,e,s){"use strict";var i=s("00ee"),r=s("f5df");t.exports=i?{}.toString:function(){return"[object "+r(this)+"]"}},b0c0:function(t,e,s){var i=s("83ab"),r=s("9bf2").f,n=Function.prototype,a=n.toString,o=/^\s*function ([^ (]*)/,c="name";i&&!(c in n)&&r(n,c,{configurable:!0,get:function(){try{return a.call(this).match(o)[1]}catch(t){return""}}})},b311:function(t,e,s){ +/*! + * clipboard.js v2.0.6 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +(function(e,s){t.exports=s()})(0,(function(){return function(t){var e={};function s(i){if(e[i])return e[i].exports;var r=e[i]={i:i,l:!1,exports:{}};return t[i].call(r.exports,r,r.exports,s),r.l=!0,r.exports}return s.m=t,s.c=e,s.d=function(t,e,i){s.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:i})},s.r=function(t){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},s.t=function(t,e){if(1&e&&(t=s(t)),8&e)return t;if(4&e&&"object"===typeof t&&t&&t.__esModule)return t;var i=Object.create(null);if(s.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)s.d(i,r,function(e){return t[e]}.bind(null,r));return i},s.n=function(t){var e=t&&t.__esModule?function(){return t["default"]}:function(){return t};return s.d(e,"a",e),e},s.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},s.p="",s(s.s=6)}([function(t,e){function s(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var s=t.hasAttribute("readonly");s||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),s||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var i=window.getSelection(),r=document.createRange();r.selectNodeContents(t),i.removeAllRanges(),i.addRange(r),e=i.toString()}return e}t.exports=s},function(t,e){function s(){}s.prototype={on:function(t,e,s){var i=this.e||(this.e={});return(i[t]||(i[t]=[])).push({fn:e,ctx:s}),this},once:function(t,e,s){var i=this;function r(){i.off(t,r),e.apply(s,arguments)}return r._=e,this.on(t,r,s)},emit:function(t){var e=[].slice.call(arguments,1),s=((this.e||(this.e={}))[t]||[]).slice(),i=0,r=s.length;for(i;i0&&void 0!==arguments[0]?arguments[0]:{};this.action=t.action,this.container=t.container,this.emitter=t.emitter,this.target=t.target,this.text=t.text,this.trigger=t.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function(){var t=this,e="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[e?"right":"left"]="-9999px";var s=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=s+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.container.appendChild(this.fakeElem),this.selectedText=r()(this.fakeElem),this.copyText()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=r()(this.target),this.copyText()}},{key:"copyText",value:function(){var t=void 0;try{t=document.execCommand(this.action)}catch(e){t=!1}this.handleResult(t)}},{key:"handleResult",value:function(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),document.activeElement.blur(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"copy";if(this._action=t,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(t){if(void 0!==t){if(!t||"object"!==("undefined"===typeof t?"undefined":n(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function(){return this._target}}]),t}(),h=c,l=s(1),p=s.n(l),u=s(2),d=s.n(u),f="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"===typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},m=function(){function t(t,e){for(var s=0;s0&&void 0!==arguments[0]?arguments[0]:{};this.action="function"===typeof t.action?t.action:this.defaultAction,this.target="function"===typeof t.target?t.target:this.defaultTarget,this.text="function"===typeof t.text?t.text:this.defaultText,this.container="object"===f(t.container)?t.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=d()(t,"click",(function(t){return e.onClick(t)}))}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new h({action:this.action(e),target:this.target(e),text:this.text(e),container:this.container,trigger:e,emitter:this})}},{key:"defaultAction",value:function(t){return v("action",t)}},{key:"defaultTarget",value:function(t){var e=v("target",t);if(e)return document.querySelector(e)}},{key:"defaultText",value:function(t){return v("text",t)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["copy","cut"],e="string"===typeof t?[t]:t,s=!!document.queryCommandSupported;return e.forEach((function(t){s=s&&!!document.queryCommandSupported(t)})),s}}]),e}(p.a);function v(t,e){var s="data-clipboard-"+t;if(e.hasAttribute(s))return e.getAttribute(s)}e["default"]=b}])["default"]}))},b575:function(t,e,s){var i,r,n,a,o,c,h,l,p=s("da84"),u=s("06cf").f,d=s("c6b6"),f=s("2cf4").set,m=s("1cdc"),y=p.MutationObserver||p.WebKitMutationObserver,g=p.process,x=p.Promise,b="process"==d(g),v=u(p,"queueMicrotask"),w=v&&v.value;w||(i=function(){var t,e;b&&(t=g.domain)&&t.exit();while(r){e=r.fn,r=r.next;try{e()}catch(s){throw r?a():n=void 0,s}}n=void 0,t&&t.enter()},b?a=function(){g.nextTick(i)}:y&&!m?(o=!0,c=document.createTextNode(""),new y(i).observe(c,{characterData:!0}),a=function(){c.data=o=!o}):x&&x.resolve?(h=x.resolve(void 0),l=h.then,a=function(){l.call(h,i)}):a=function(){f.call(p,i)}),t.exports=w||function(t){var e={fn:t,next:void 0};n&&(n.next=e),r||(r=e,a()),n=e}},b622:function(t,e,s){var i=s("da84"),r=s("5692"),n=s("5135"),a=s("90e3"),o=s("4930"),c=s("fdbf"),h=r("wks"),l=i.Symbol,p=c?l:l&&l.withoutSetter||a;t.exports=function(t){return n(h,t)||(o&&n(l,t)?h[t]=l[t]:h[t]=p("Symbol."+t)),h[t]}},b64b:function(t,e,s){var i=s("23e7"),r=s("7b0b"),n=s("df75"),a=s("d039"),o=a((function(){n(1)}));i({target:"Object",stat:!0,forced:o},{keys:function(t){return n(r(t))}})},b727:function(t,e,s){var i=s("0366"),r=s("44ad"),n=s("7b0b"),a=s("50c4"),o=s("65f0"),c=[].push,h=function(t){var e=1==t,s=2==t,h=3==t,l=4==t,p=6==t,u=5==t||p;return function(d,f,m,y){for(var g,x,b=n(d),v=r(b),w=i(f,m,3),P=a(v.length),T=0,E=y||o,A=e?E(d,P):s?E(d,0):void 0;P>T;T++)if((u||T in v)&&(g=v[T],x=w(g,T,b),t))if(e)A[T]=x;else if(x)switch(t){case 3:return!0;case 5:return g;case 6:return T;case 2:c.call(A,g)}else if(l)return!1;return p?-1:h||l?l:A}};t.exports={forEach:h(0),map:h(1),filter:h(2),some:h(3),every:h(4),find:h(5),findIndex:h(6)}},c04e:function(t,e,s){var i=s("861d");t.exports=function(t,e){if(!i(t))return t;var s,r;if(e&&"function"==typeof(s=t.toString)&&!i(r=s.call(t)))return r;if("function"==typeof(s=t.valueOf)&&!i(r=s.call(t)))return r;if(!e&&"function"==typeof(s=t.toString)&&!i(r=s.call(t)))return r;throw TypeError("Can't convert object to primitive value")}},c430:function(t,e){t.exports=!1},c6b6:function(t,e){var s={}.toString;t.exports=function(t){return s.call(t).slice(8,-1)}},c6cd:function(t,e,s){var i=s("da84"),r=s("ce4e"),n="__core-js_shared__",a=i[n]||r(n,{});t.exports=a},c740:function(t,e,s){"use strict";var i=s("23e7"),r=s("b727").findIndex,n=s("44d2"),a=s("ae40"),o="findIndex",c=!0,h=a(o);o in[]&&Array(1)[o]((function(){c=!1})),i({target:"Array",proto:!0,forced:c||!h},{findIndex:function(t){return r(this,t,arguments.length>1?arguments[1]:void 0)}}),n(o)},c8ba:function(t,e){var s;s=function(){return this}();try{s=s||new Function("return this")()}catch(i){"object"===typeof window&&(s=window)}t.exports=s},c975:function(t,e,s){"use strict";var i=s("23e7"),r=s("4d64").indexOf,n=s("a640"),a=s("ae40"),o=[].indexOf,c=!!o&&1/[1].indexOf(1,-0)<0,h=n("indexOf"),l=a("indexOf",{ACCESSORS:!0,1:0});i({target:"Array",proto:!0,forced:c||!h||!l},{indexOf:function(t){return c?o.apply(this,arguments)||0:r(this,t,arguments.length>1?arguments[1]:void 0)}})},ca84:function(t,e,s){var i=s("5135"),r=s("fc6a"),n=s("4d64").indexOf,a=s("d012");t.exports=function(t,e){var s,o=r(t),c=0,h=[];for(s in o)!i(a,s)&&i(o,s)&&h.push(s);while(e.length>c)i(o,s=e[c++])&&(~n(h,s)||h.push(s));return h}},caad:function(t,e,s){"use strict";var i=s("23e7"),r=s("4d64").includes,n=s("44d2"),a=s("ae40"),o=a("indexOf",{ACCESSORS:!0,1:0});i({target:"Array",proto:!0,forced:!o},{includes:function(t){return r(this,t,arguments.length>1?arguments[1]:void 0)}}),n("includes")},cc06:function(t,e,s){"use strict";function i(t,e,s,i){var r,n=!1,a=0;function o(){r&&clearTimeout(r)}function c(){o(),n=!0}function h(){for(var c=arguments.length,h=new Array(c),l=0;lt?d():!0!==e&&(r=setTimeout(i?f:d,void 0===i?t-u:t)))}return"boolean"!==typeof e&&(i=s,s=e,e=void 0),h.cancel=c,h}function r(t,e,s){return void 0===s?i(t,e,!1):i(t,s,!1!==e)}s.d(e,"a",(function(){return r}))},cc12:function(t,e,s){var i=s("da84"),r=s("861d"),n=i.document,a=r(n)&&r(n.createElement);t.exports=function(t){return a?n.createElement(t):{}}},cca6:function(t,e,s){var i=s("23e7"),r=s("60da");i({target:"Object",stat:!0,forced:Object.assign!==r},{assign:r})},cdf9:function(t,e,s){var i=s("825a"),r=s("861d"),n=s("f069");t.exports=function(t,e){if(i(t),r(e)&&e.constructor===t)return e;var s=n.f(t),a=s.resolve;return a(e),s.promise}},ce4e:function(t,e,s){var i=s("da84"),r=s("9112");t.exports=function(t,e){try{r(i,t,e)}catch(s){i[t]=e}return e}},d012:function(t,e){t.exports={}},d039:function(t,e){t.exports=function(t){try{return!!t()}catch(e){return!0}}},d066:function(t,e,s){var i=s("428f"),r=s("da84"),n=function(t){return"function"==typeof t?t:void 0};t.exports=function(t,e){return arguments.length<2?n(i[t])||n(r[t]):i[t]&&i[t][e]||r[t]&&r[t][e]}},d1e7:function(t,e,s){"use strict";var i={}.propertyIsEnumerable,r=Object.getOwnPropertyDescriptor,n=r&&!i.call({1:2},1);e.f=n?function(t){var e=r(this,t);return!!e&&e.enumerable}:i},d28b:function(t,e,s){var i=s("746f");i("iterator")},d2bb:function(t,e,s){var i=s("825a"),r=s("3bbe");t.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var t,e=!1,s={};try{t=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set,t.call(s,[]),e=s instanceof Array}catch(n){}return function(s,n){return i(s),r(n),e?t.call(s,n):s.__proto__=n,s}}():void 0)},d3b7:function(t,e,s){var i=s("00ee"),r=s("6eeb"),n=s("b041");i||r(Object.prototype,"toString",n,{unsafe:!0})},d44e:function(t,e,s){var i=s("9bf2").f,r=s("5135"),n=s("b622"),a=n("toStringTag");t.exports=function(t,e,s){t&&!r(t=s?t:t.prototype,a)&&i(t,a,{configurable:!0,value:e})}},d60a:function(t,e){t.exports=function(t){return t&&"object"===typeof t&&"function"===typeof t.copy&&"function"===typeof t.fill&&"function"===typeof t.readUInt8}},d784:function(t,e,s){"use strict";s("ac1f");var i=s("6eeb"),r=s("d039"),n=s("b622"),a=s("9263"),o=s("9112"),c=n("species"),h=!r((function(){var t=/./;return t.exec=function(){var t=[];return t.groups={a:"7"},t},"7"!=="".replace(t,"$")})),l=function(){return"$0"==="a".replace(/./,"$0")}(),p=n("replace"),u=function(){return!!/./[p]&&""===/./[p]("a","$0")}(),d=!r((function(){var t=/(?:)/,e=t.exec;t.exec=function(){return e.apply(this,arguments)};var s="ab".split(t);return 2!==s.length||"a"!==s[0]||"b"!==s[1]}));t.exports=function(t,e,s,p){var f=n(t),m=!r((function(){var e={};return e[f]=function(){return 7},7!=""[t](e)})),y=m&&!r((function(){var e=!1,s=/a/;return"split"===t&&(s={},s.constructor={},s.constructor[c]=function(){return s},s.flags="",s[f]=/./[f]),s.exec=function(){return e=!0,null},s[f](""),!e}));if(!m||!y||"replace"===t&&(!h||!l||u)||"split"===t&&!d){var g=/./[f],x=s(f,""[t],(function(t,e,s,i,r){return e.exec===a?m&&!r?{done:!0,value:g.call(e,s,i)}:{done:!0,value:t.call(s,e,i)}:{done:!1}}),{REPLACE_KEEPS_$0:l,REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE:u}),b=x[0],v=x[1];i(String.prototype,t,b),i(RegExp.prototype,f,2==e?function(t,e){return v.call(t,this,e)}:function(t){return v.call(t,this)})}p&&o(RegExp.prototype[f],"sham",!0)}},d81d:function(t,e,s){"use strict";var i=s("23e7"),r=s("b727").map,n=s("1dde"),a=s("ae40"),o=n("map"),c=a("map");i({target:"Array",proto:!0,forced:!o||!c},{map:function(t){return r(this,t,arguments.length>1?arguments[1]:void 0)}})},da84:function(t,e,s){(function(e){var s=function(t){return t&&t.Math==Math&&t};t.exports=s("object"==typeof globalThis&&globalThis)||s("object"==typeof window&&window)||s("object"==typeof self&&self)||s("object"==typeof e&&e)||Function("return this")()}).call(this,s("c8ba"))},dbb4:function(t,e,s){var i=s("23e7"),r=s("83ab"),n=s("56ef"),a=s("fc6a"),o=s("06cf"),c=s("8418");i({target:"Object",stat:!0,sham:!r},{getOwnPropertyDescriptors:function(t){var e,s,i=a(t),r=o.f,h=n(i),l={},p=0;while(h.length>p)s=r(i,e=h[p++]),void 0!==s&&c(l,e,s);return l}})},ddb0:function(t,e,s){var i=s("da84"),r=s("fdbc"),n=s("e260"),a=s("9112"),o=s("b622"),c=o("iterator"),h=o("toStringTag"),l=n.values;for(var p in r){var u=i[p],d=u&&u.prototype;if(d){if(d[c]!==l)try{a(d,c,l)}catch(m){d[c]=l}if(d[h]||a(d,h,p),r[p])for(var f in n)if(d[f]!==n[f])try{a(d,f,n[f])}catch(m){d[f]=n[f]}}}},df75:function(t,e,s){var i=s("ca84"),r=s("7839");t.exports=Object.keys||function(t){return i(t,r)}},df7c:function(t,e,s){(function(t){function s(t,e){for(var s=0,i=t.length-1;i>=0;i--){var r=t[i];"."===r?t.splice(i,1):".."===r?(t.splice(i,1),s++):s&&(t.splice(i,1),s--)}if(e)for(;s--;s)t.unshift("..");return t}function i(t){"string"!==typeof t&&(t+="");var e,s=0,i=-1,r=!0;for(e=t.length-1;e>=0;--e)if(47===t.charCodeAt(e)){if(!r){s=e+1;break}}else-1===i&&(r=!1,i=e+1);return-1===i?"":t.slice(s,i)}function r(t,e){if(t.filter)return t.filter(e);for(var s=[],i=0;i=-1&&!i;n--){var a=n>=0?arguments[n]:t.cwd();if("string"!==typeof a)throw new TypeError("Arguments to path.resolve must be strings");a&&(e=a+"/"+e,i="/"===a.charAt(0))}return e=s(r(e.split("/"),(function(t){return!!t})),!i).join("/"),(i?"/":"")+e||"."},e.normalize=function(t){var i=e.isAbsolute(t),a="/"===n(t,-1);return t=s(r(t.split("/"),(function(t){return!!t})),!i).join("/"),t||i||(t="."),t&&a&&(t+="/"),(i?"/":"")+t},e.isAbsolute=function(t){return"/"===t.charAt(0)},e.join=function(){var t=Array.prototype.slice.call(arguments,0);return e.normalize(r(t,(function(t,e){if("string"!==typeof t)throw new TypeError("Arguments to path.join must be strings");return t})).join("/"))},e.relative=function(t,s){function i(t){for(var e=0;e=0;s--)if(""!==t[s])break;return e>s?[]:t.slice(e,s-e+1)}t=e.resolve(t).substr(1),s=e.resolve(s).substr(1);for(var r=i(t.split("/")),n=i(s.split("/")),a=Math.min(r.length,n.length),o=a,c=0;c=1;--n)if(e=t.charCodeAt(n),47===e){if(!r){i=n;break}}else r=!1;return-1===i?s?"/":".":s&&1===i?"/":t.slice(0,i)},e.basename=function(t,e){var s=i(t);return e&&s.substr(-1*e.length)===e&&(s=s.substr(0,s.length-e.length)),s},e.extname=function(t){"string"!==typeof t&&(t+="");for(var e=-1,s=0,i=-1,r=!0,n=0,a=t.length-1;a>=0;--a){var o=t.charCodeAt(a);if(47!==o)-1===i&&(r=!1,i=a+1),46===o?-1===e?e=a:1!==n&&(n=1):-1!==e&&(n=-1);else if(!r){s=a+1;break}}return-1===e||-1===i||0===n||1===n&&e===i-1&&e===s+1?"":t.slice(e,i)};var n="b"==="ab".substr(-1)?function(t,e,s){return t.substr(e,s)}:function(t,e,s){return e<0&&(e=t.length+e),t.substr(e,s)}}).call(this,s("4362"))},e017:function(t,e,s){(function(e){(function(e,s){t.exports=s()})(0,(function(){"use strict";var t=function(t){var e=t.id,s=t.viewBox,i=t.content;this.id=e,this.viewBox=s,this.content=i};t.prototype.stringify=function(){return this.content},t.prototype.toString=function(){return this.stringify()},t.prototype.destroy=function(){var t=this;["id","viewBox","content"].forEach((function(e){return delete t[e]}))};var s=function(t){var e=!!document.importNode,s=(new DOMParser).parseFromString(t,"image/svg+xml").documentElement;return e?document.importNode(s,!0):s};"undefined"!==typeof window?window:"undefined"!==typeof e||"undefined"!==typeof self&&self;function i(t,e){return e={exports:{}},t(e,e.exports),e.exports}var r=i((function(t,e){(function(e,s){t.exports=s()})(0,(function(){function t(t){var e=t&&"object"===typeof t;return e&&"[object RegExp]"!==Object.prototype.toString.call(t)&&"[object Date]"!==Object.prototype.toString.call(t)}function e(t){return Array.isArray(t)?[]:{}}function s(s,i){var r=i&&!0===i.clone;return r&&t(s)?n(e(s),s,i):s}function i(e,i,r){var a=e.slice();return i.forEach((function(i,o){"undefined"===typeof a[o]?a[o]=s(i,r):t(i)?a[o]=n(e[o],i,r):-1===e.indexOf(i)&&a.push(s(i,r))})),a}function r(e,i,r){var a={};return t(e)&&Object.keys(e).forEach((function(t){a[t]=s(e[t],r)})),Object.keys(i).forEach((function(o){t(i[o])&&e[o]?a[o]=n(e[o],i[o],r):a[o]=s(i[o],r)})),a}function n(t,e,n){var a=Array.isArray(e),o=n||{arrayMerge:i},c=o.arrayMerge||i;return a?Array.isArray(t)?c(t,e,n):s(e,n):r(t,e,n)}return n.all=function(t,e){if(!Array.isArray(t)||t.length<2)throw new Error("first argument should be an array with at least two elements");return t.reduce((function(t,s){return n(t,s,e)}))},n}))})),n=i((function(t,e){var s={svg:{name:"xmlns",uri:"http://www.w3.org/2000/svg"},xlink:{name:"xmlns:xlink",uri:"http://www.w3.org/1999/xlink"}};e.default=s,t.exports=e.default})),a=function(t){return Object.keys(t).map((function(e){var s=t[e].toString().replace(/"/g,""");return e+'="'+s+'"'})).join(" ")},o=n.svg,c=n.xlink,h={};h[o.name]=o.uri,h[c.name]=c.uri;var l=function(t,e){void 0===t&&(t="");var s=r(h,e||{}),i=a(s);return""+t+""},p=function(t){function e(){t.apply(this,arguments)}t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e;var i={isMounted:{}};return i.isMounted.get=function(){return!!this.node},e.createFromExistingNode=function(t){return new e({id:t.getAttribute("id"),viewBox:t.getAttribute("viewBox"),content:t.outerHTML})},e.prototype.destroy=function(){this.isMounted&&this.unmount(),t.prototype.destroy.call(this)},e.prototype.mount=function(t){if(this.isMounted)return this.node;var e="string"===typeof t?document.querySelector(t):t,s=this.render();return this.node=s,e.appendChild(s),s},e.prototype.render=function(){var t=this.stringify();return s(l(t)).childNodes[0]},e.prototype.unmount=function(){this.node.parentNode.removeChild(this.node)},Object.defineProperties(e.prototype,i),e}(t);return p}))}).call(this,s("c8ba"))},e01a:function(t,e,s){"use strict";var i=s("23e7"),r=s("83ab"),n=s("da84"),a=s("5135"),o=s("861d"),c=s("9bf2").f,h=s("e893"),l=n.Symbol;if(r&&"function"==typeof l&&(!("description"in l.prototype)||void 0!==l().description)){var p={},u=function(){var t=arguments.length<1||void 0===arguments[0]?void 0:String(arguments[0]),e=this instanceof u?new l(t):void 0===t?l():l(t);return""===t&&(p[e]=!0),e};h(u,l);var d=u.prototype=l.prototype;d.constructor=u;var f=d.toString,m="Symbol(test)"==String(l("test")),y=/^Symbol\((.*)\)[^)]+$/;c(d,"description",{configurable:!0,get:function(){var t=o(this)?this.valueOf():this,e=f.call(t);if(a(p,t))return"";var s=m?e.slice(7,-1):e.replace(y,"$1");return""===s?void 0:s}}),i({global:!0,forced:!0},{Symbol:u})}},e163:function(t,e,s){var i=s("5135"),r=s("7b0b"),n=s("f772"),a=s("e177"),o=n("IE_PROTO"),c=Object.prototype;t.exports=a?Object.getPrototypeOf:function(t){return t=r(t),i(t,o)?t[o]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?c:null}},e177:function(t,e,s){var i=s("d039");t.exports=!i((function(){function t(){}return t.prototype.constructor=null,Object.getPrototypeOf(new t)!==t.prototype}))},e260:function(t,e,s){"use strict";var i=s("fc6a"),r=s("44d2"),n=s("3f8c"),a=s("69f3"),o=s("7dd0"),c="Array Iterator",h=a.set,l=a.getterFor(c);t.exports=o(Array,"Array",(function(t,e){h(this,{type:c,target:i(t),index:0,kind:e})}),(function(){var t=l(this),e=t.target,s=t.kind,i=t.index++;return!e||i>=e.length?(t.target=void 0,{value:void 0,done:!0}):"keys"==s?{value:i,done:!1}:"values"==s?{value:e[i],done:!1}:{value:[i,e[i]],done:!1}}),"values"),n.Arguments=n.Array,r("keys"),r("values"),r("entries")},e2cc:function(t,e,s){var i=s("6eeb");t.exports=function(t,e,s){for(var r in e)i(t,r,e[r],s);return t}},e439:function(t,e,s){var i=s("23e7"),r=s("d039"),n=s("fc6a"),a=s("06cf").f,o=s("83ab"),c=r((function(){a(1)})),h=!o||c;i({target:"Object",stat:!0,forced:h,sham:!o},{getOwnPropertyDescriptor:function(t,e){return a(n(t),e)}})},e538:function(t,e,s){var i=s("b622");e.f=i},e667:function(t,e){t.exports=function(t){try{return{error:!1,value:t()}}catch(e){return{error:!0,value:e}}}},e6cf:function(t,e,s){"use strict";var i,r,n,a,o=s("23e7"),c=s("c430"),h=s("da84"),l=s("d066"),p=s("fea9"),u=s("6eeb"),d=s("e2cc"),f=s("d44e"),m=s("2626"),y=s("861d"),g=s("1c0b"),x=s("19aa"),b=s("c6b6"),v=s("8925"),w=s("2266"),P=s("1c7e"),T=s("4840"),E=s("2cf4").set,A=s("b575"),S=s("cdf9"),C=s("44de"),k=s("f069"),N=s("e667"),I=s("69f3"),O=s("94ca"),D=s("b622"),M=s("2d00"),L=D("species"),_="Promise",R=I.get,j=I.set,F=I.getterFor(_),B=p,U=h.TypeError,q=h.document,V=h.process,H=l("fetch"),z=k.f,W=z,K="process"==b(V),$=!!(q&&q.createEvent&&h.dispatchEvent),X="unhandledrejection",G="rejectionhandled",Y=0,J=1,Q=2,Z=1,tt=2,et=O(_,(function(){var t=v(B)!==String(B);if(!t){if(66===M)return!0;if(!K&&"function"!=typeof PromiseRejectionEvent)return!0}if(c&&!B.prototype["finally"])return!0;if(M>=51&&/native code/.test(B))return!1;var e=B.resolve(1),s=function(t){t((function(){}),(function(){}))},i=e.constructor={};return i[L]=s,!(e.then((function(){}))instanceof s)})),st=et||!P((function(t){B.all(t)["catch"]((function(){}))})),it=function(t){var e;return!(!y(t)||"function"!=typeof(e=t.then))&&e},rt=function(t,e,s){if(!e.notified){e.notified=!0;var i=e.reactions;A((function(){var r=e.value,n=e.state==J,a=0;while(i.length>a){var o,c,h,l=i[a++],p=n?l.ok:l.fail,u=l.resolve,d=l.reject,f=l.domain;try{p?(n||(e.rejection===tt&&ct(t,e),e.rejection=Z),!0===p?o=r:(f&&f.enter(),o=p(r),f&&(f.exit(),h=!0)),o===l.promise?d(U("Promise-chain cycle")):(c=it(o))?c.call(o,u,d):u(o)):d(r)}catch(m){f&&!h&&f.exit(),d(m)}}e.reactions=[],e.notified=!1,s&&!e.rejection&&at(t,e)}))}},nt=function(t,e,s){var i,r;$?(i=q.createEvent("Event"),i.promise=e,i.reason=s,i.initEvent(t,!1,!0),h.dispatchEvent(i)):i={promise:e,reason:s},(r=h["on"+t])?r(i):t===X&&C("Unhandled promise rejection",s)},at=function(t,e){E.call(h,(function(){var s,i=e.value,r=ot(e);if(r&&(s=N((function(){K?V.emit("unhandledRejection",i,t):nt(X,t,i)})),e.rejection=K||ot(e)?tt:Z,s.error))throw s.value}))},ot=function(t){return t.rejection!==Z&&!t.parent},ct=function(t,e){E.call(h,(function(){K?V.emit("rejectionHandled",t):nt(G,t,e.value)}))},ht=function(t,e,s,i){return function(r){t(e,s,r,i)}},lt=function(t,e,s,i){e.done||(e.done=!0,i&&(e=i),e.value=s,e.state=Q,rt(t,e,!0))},pt=function(t,e,s,i){if(!e.done){e.done=!0,i&&(e=i);try{if(t===s)throw U("Promise can't be resolved itself");var r=it(s);r?A((function(){var i={done:!1};try{r.call(s,ht(pt,t,i,e),ht(lt,t,i,e))}catch(n){lt(t,i,n,e)}})):(e.value=s,e.state=J,rt(t,e,!1))}catch(n){lt(t,{done:!1},n,e)}}};et&&(B=function(t){x(this,B,_),g(t),i.call(this);var e=R(this);try{t(ht(pt,this,e),ht(lt,this,e))}catch(s){lt(this,e,s)}},i=function(t){j(this,{type:_,done:!1,notified:!1,parent:!1,reactions:[],rejection:!1,state:Y,value:void 0})},i.prototype=d(B.prototype,{then:function(t,e){var s=F(this),i=z(T(this,B));return i.ok="function"!=typeof t||t,i.fail="function"==typeof e&&e,i.domain=K?V.domain:void 0,s.parent=!0,s.reactions.push(i),s.state!=Y&&rt(this,s,!1),i.promise},catch:function(t){return this.then(void 0,t)}}),r=function(){var t=new i,e=R(t);this.promise=t,this.resolve=ht(pt,t,e),this.reject=ht(lt,t,e)},k.f=z=function(t){return t===B||t===n?new r(t):W(t)},c||"function"!=typeof p||(a=p.prototype.then,u(p.prototype,"then",(function(t,e){var s=this;return new B((function(t,e){a.call(s,t,e)})).then(t,e)}),{unsafe:!0}),"function"==typeof H&&o({global:!0,enumerable:!0,forced:!0},{fetch:function(t){return S(B,H.apply(h,arguments))}}))),o({global:!0,wrap:!0,forced:et},{Promise:B}),f(B,_,!1,!0),m(_),n=l(_),o({target:_,stat:!0,forced:et},{reject:function(t){var e=z(this);return e.reject.call(void 0,t),e.promise}}),o({target:_,stat:!0,forced:c||et},{resolve:function(t){return S(c&&this===n?B:this,t)}}),o({target:_,stat:!0,forced:st},{all:function(t){var e=this,s=z(e),i=s.resolve,r=s.reject,n=N((function(){var s=g(e.resolve),n=[],a=0,o=1;w(t,(function(t){var c=a++,h=!1;n.push(void 0),o++,s.call(e,t).then((function(t){h||(h=!0,n[c]=t,--o||i(n))}),r)})),--o||i(n)}));return n.error&&r(n.value),s.promise},race:function(t){var e=this,s=z(e),i=s.reject,r=N((function(){var r=g(e.resolve);w(t,(function(t){r.call(e,t).then(s.resolve,i)}))}));return r.error&&i(r.value),s.promise}})},e893:function(t,e,s){var i=s("5135"),r=s("56ef"),n=s("06cf"),a=s("9bf2");t.exports=function(t,e){for(var s=r(e),o=a.f,c=n.f,h=0;h
'});l.a.add(c);t["default"]=c},"064a":function(e,t,a){"use strict";a.r(t);var o=a("e017"),n=a.n(o),i=a("21a1"),l=a.n(i),c=new n.a({id:"icon-select",use:"icon-select-usage",viewBox:"0 0 1024 1024",content:''});l.a.add(c);t["default"]=c},"0f05":function(e,t,a){},"0f88":function(e,t,a){"use strict";a.r(t),t["default"]={"list-type":function(e,t,a){var o=[],n=t.__config__;return"picture-card"===t["list-type"]?o.push(e("i",{class:"el-icon-plus"})):o.push(e("el-button",{attrs:{size:"small",type:"primary",icon:"el-icon-upload"}},[n.buttonText])),n.showTip&&o.push(e("div",{slot:"tip",class:"el-upload__tip"},["只能上传不超过 ",n.fileSize,n.sizeUnit," 的",t.accept,"文件"])),o}}},"128d":function(e,t,a){"use strict";a.r(t);var o=a("e017"),n=a.n(o),i=a("21a1"),l=a.n(i),c=new n.a({id:"icon-textarea",use:"icon-textarea-usage",viewBox:"0 0 1024 1024",content:''});l.a.add(c);t["default"]=c},"167d":function(e,t,a){"use strict";a.r(t),t["default"]={prepend:function(e,t,a){return e("template",{slot:"prepend"},[t.__slot__[a]])},append:function(e,t,a){return e("template",{slot:"append"},[t.__slot__[a]])}}},"1fce":function(e,t,a){"use strict";a.r(t);var o=a("e017"),n=a.n(o),i=a("21a1"),l=a.n(i),c=new n.a({id:"icon-number",use:"icon-number-usage",viewBox:"0 0 1024 1024",content:''});l.a.add(c);t["default"]=c},"235f":function(e,t,a){"use strict";a.r(t);var o=a("e017"),n=a.n(o),i=a("21a1"),l=a.n(i),c=new n.a({id:"icon-date",use:"icon-date-usage",viewBox:"0 0 1024 1024",content:''});l.a.add(c);t["default"]=c},2384:function(e,t,a){"use strict";a.r(t);var o=a("e017"),n=a.n(o),i=a("21a1"),l=a.n(i),c=new n.a({id:"icon-switch",use:"icon-switch-usage",viewBox:"0 0 1024 1024",content:''});l.a.add(c);t["default"]=c},"2a3d":function(e,t,a){"use strict";a.r(t);var o=a("e017"),n=a.n(o),i=a("21a1"),l=a.n(i),c=new n.a({id:"icon-password",use:"icon-password-usage",viewBox:"0 0 1024 1024",content:''});l.a.add(c);t["default"]=c},"2cfa":function(e,t,a){"use strict";a.r(t);a("4160"),a("159b");t["default"]={options:function(e,t,a){var o=[];return t.__slot__.options.forEach((function(a){"button"===t.__config__.optionType?o.push(e("el-radio-button",{attrs:{label:a.value}},[a.label])):o.push(e("el-radio",{attrs:{label:a.value,border:t.border}},[a.label]))})),o}}},"2db0":function(e,t,a){},"2dba":function(e,t,a){"use strict";var o=a("6f47"),n=a.n(o);n.a},"31c6":function(e,t,a){"use strict";var o,n=function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("textarea",{staticStyle:{visibility:"hidden"},attrs:{id:e.tinymceId}})},i=[],l=(a("99af"),a("d3b7"),a("25f0"),a("c88b")),c=a("5f72"),r=a.n(c);function s(e){if(o)e(o);else{var t=r.a.Loading.service({fullscreen:!0,lock:!0,text:"富文本资源加载中...",spinner:"el-icon-loading",background:"rgba(255, 255, 255, 0.5)"});Object(l["a"])("https://lib.baomitu.com/tinymce/5.3.2/tinymce.min.js",(function(){t.close(),o=tinymce,e(o)}))}}var u=["advlist anchor autolink autosave code codesample directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textpattern visualblocks visualchars wordcount"],d=["code searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote removeformat subscript superscript codesample hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen"],_=a("cc06"),p=1,m={props:{id:{type:String,default:function(){return 1e4===p&&(p=1),"tinymce".concat(+new Date).concat(p++)}},value:{default:""}},data:function(){return{tinymceId:this.id}},mounted:function(){var e=this;s((function(t){a("afc4");var o={selector:"#".concat(e.tinymceId),language:"zh_CN",menubar:"file edit insert view format table",plugins:u,toolbar:d,height:300,branding:!1,object_resizing:!1,end_container_on_empty_block:!0,powerpaste_word_import:"clean",code_dialog_height:450,code_dialog_width:1e3,advlist_bullet_styles:"square",advlist_number_styles:"default",default_link_target:"_blank",link_title:!1,nonbreaking_force_tab:!0};o=Object.assign(o,e.$attrs),o.init_instance_callback=function(t){e.value&&t.setContent(e.value),e.vModel(t)},t.init(o)}))},destroyed:function(){this.destroyTinymce()},methods:{vModel:function(e){var t=this,a=Object(_["a"])(250,e.setContent);this.$watch("value",(function(t,o){e&&t!==o&&t!==e.getContent()&&("string"!==typeof t&&(t=t.toString()),a.call(e,t))})),e.on("change keyup undo redo",(function(){t.$emit("input",e.getContent())}))},destroyTinymce:function(){if(window.tinymce){var e=window.tinymce.get(this.tinymceId);e&&e.destroy()}}}},f=m,v=a("2877"),h=Object(v["a"])(f,n,i,!1,null,null,null);t["a"]=h.exports},"3add":function(e,t,a){"use strict";a.r(t);var o=a("e017"),n=a.n(o),i=a("21a1"),l=a.n(i),c=new n.a({id:"icon-time",use:"icon-time-usage",viewBox:"0 0 1024 1024",content:''});l.a.add(c);t["default"]=c},4758:function(e,t,a){"use strict";a("4160"),a("b64b"),a("d3b7"),a("ac1f"),a("5319"),a("159b"),a("ddb0");var o=a("5530"),n=a("ed08"),i={},l=a("9977"),c=l.keys()||[];function r(e,t){var a=this;e.props.value=t,e.on.input=function(e){a.$emit("input",e)}}function s(e,t,a){var o=i[t.__config__.tag];o&&Object.keys(o).forEach((function(n){var i=o[n];t.__slot__&&t.__slot__[n]&&a.push(i(e,t,n))}))}function u(e){var t=this;["on","nativeOn"].forEach((function(a){var o=Object.keys(e[a]||{});o.forEach((function(o){var n=e[a][o];"string"===typeof n&&(e[a][o]=function(e){return t.$emit(n,e)})}))}))}function d(e,t){var a=this;Object.keys(e).forEach((function(n){var i=e[n];"__vModel__"===n?r.call(a,t,e.__config__.defaultValue):t[n]?t[n]=Object(o["a"])(Object(o["a"])({},t[n]),i):t.attrs[n]=i})),_(t)}function _(e){delete e.attrs.__config__,delete e.attrs.__slot__,delete e.attrs.__methods__}function p(){return{attrs:{},props:{},nativeOn:{},on:{},style:{}}}c.forEach((function(e){var t=e.replace(/^\.\/(.*)\.\w+$/,"$1"),a=l(e).default;i[t]=a})),t["a"]={props:{conf:{type:Object,required:!0}},render:function(e){var t=p(),a=Object(n["b"])(this.conf),o=[];return s.call(this,e,a,o),u.call(this,a),d.call(this,a,t),e(this.conf.__config__.tag,t,o)}}},"475a":function(e,t,a){},"4ed4":function(e,t,a){"use strict";a.r(t);var o=a("e017"),n=a.n(o),i=a("21a1"),l=a.n(i),c=new n.a({id:"icon-button",use:"icon-button-usage",viewBox:"0 0 1024 1024",content:''});l.a.add(c);t["default"]=c},"51ff":function(e,t,a){var o={"./button.svg":"4ed4","./cascader.svg":"a393","./checkbox.svg":"8963","./color.svg":"03ab","./component.svg":"56d6","./date-range.svg":"e6df","./date.svg":"235f","./input.svg":"81d6","./number.svg":"1fce","./password.svg":"2a3d","./radio.svg":"d8dc","./rate.svg":"6786","./rich-text.svg":"c630","./row.svg":"c95d","./select.svg":"064a","./slider.svg":"eb1c","./switch.svg":"2384","./textarea.svg":"128d","./time-range.svg":"861c","./time.svg":"3add","./upload.svg":"9d82"};function n(e){var t=i(e);return a(t)}function i(e){if(!a.o(o,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return o[e]}n.keys=function(){return Object.keys(o)},n.resolve=i,e.exports=n,n.id="51ff"},"56d6":function(e,t,a){"use strict";a.r(t);var o=a("e017"),n=a.n(o),i=a("21a1"),l=a.n(i),c=new n.a({id:"icon-component",use:"icon-component-usage",viewBox:"0 0 1024 1024",content:''});l.a.add(c);t["default"]=c},"5f72":function(e,t){e.exports=ELEMENT},"627e":function(e,t,a){},6389:function(e,t){e.exports=VueRouter},"66a2":function(e,t,a){"use strict";var o=a("475a"),n=a.n(o);n.a},6786:function(e,t,a){"use strict";a.r(t);var o=a("e017"),n=a.n(o),i=a("21a1"),l=a.n(i),c=new n.a({id:"icon-rate",use:"icon-rate-usage",viewBox:"0 0 1069 1024",content:''});l.a.add(c);t["default"]=c},6828:function(e,t,a){"use strict";var o=a("627e"),n=a.n(o);n.a},"6f47":function(e,t,a){},"7f29":function(e,t,a){"use strict";a.r(t);a("4160"),a("159b");t["default"]={options:function(e,t,a){var o=[];return t.__slot__.options.forEach((function(t){o.push(e("el-option",{attrs:{label:t.label,value:t.value,disabled:t.disabled}}))})),o}}},"80e9":function(e,t,a){"use strict";var o=a("fca0"),n=a.n(o);n.a},"81d6":function(e,t,a){"use strict";a.r(t);var o=a("e017"),n=a.n(o),i=a("21a1"),l=a.n(i),c=new n.a({id:"icon-input",use:"icon-input-usage",viewBox:"0 0 1024 1024",content:''});l.a.add(c);t["default"]=c},"861c":function(e,t,a){"use strict";a.r(t);var o=a("e017"),n=a.n(o),i=a("21a1"),l=a.n(i),c=new n.a({id:"icon-time-range",use:"icon-time-range-usage",viewBox:"0 0 1024 1024",content:''});l.a.add(c);t["default"]=c},8963:function(e,t,a){"use strict";a.r(t);var o=a("e017"),n=a.n(o),i=a("21a1"),l=a.n(i),c=new n.a({id:"icon-checkbox",use:"icon-checkbox-usage",viewBox:"0 0 1024 1024",content:''});l.a.add(c);t["default"]=c},"8a8a":function(e,t,a){"use strict";a.r(t);a("e260"),a("e6cf"),a("cca6"),a("a79d");var o,n,i=a("8bbf"),l=a.n(i),c=function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("div",[a("router-view")],1)},r=[],s={mounted:function(){var e=document.querySelector("#pre-loader");e.style.display="none",document.body.ondrop=function(e){e.preventDefault(),e.stopPropagation()}}},u=s,d=a("2877"),_=Object(d["a"])(u,c,r,!1,null,null,null),p=_.exports,m=(a("d3b7"),a("6389")),f=a.n(m),v=function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("div",{staticClass:"container"},[a("div",{staticClass:"left-board"},[a("div",{staticClass:"logo-wrapper"},[a("div",{staticClass:"logo"},[a("img",{attrs:{src:e.logo,alt:"logo"}}),e._v(" 表单构建 ")])]),a("el-scrollbar",{staticClass:"left-scrollbar"},[a("div",{staticClass:"components-list"},e._l(e.leftComponents,(function(t,o){return a("div",{key:o},[a("div",{staticClass:"components-title"},[a("svg-icon",{attrs:{"icon-class":"component"}}),e._v(" "+e._s(t.title)+" ")],1),a("draggable",{staticClass:"components-draggable",attrs:{list:t.list,group:{name:"componentsGroup",pull:"clone",put:!1},clone:e.cloneComponent,draggable:".components-item",sort:!1},on:{end:e.onEnd}},e._l(t.list,(function(t,o){return a("div",{key:o,staticClass:"components-item",on:{click:function(a){return e.addComponent(t)}}},[a("div",{staticClass:"components-body"},[a("svg-icon",{attrs:{"icon-class":t.__config__.tagIcon}}),e._v(" "+e._s(t.__config__.label)+" ")],1)])})),0)],1)})),0)])],1),a("div",{staticClass:"center-board"},[a("div",{staticClass:"action-bar"},[a("el-button",{attrs:{icon:"el-icon-video-play",type:"text"},on:{click:e.run}},[e._v(" 运行 ")]),a("el-button",{attrs:{icon:"el-icon-view",type:"text"},on:{click:e.showJson}},[e._v(" 查看json ")]),a("el-button",{attrs:{icon:"el-icon-download",type:"text"},on:{click:e.download}},[e._v(" 导出vue文件 ")]),a("el-button",{staticClass:"copy-btn-main",attrs:{icon:"el-icon-document-copy",type:"text"},on:{click:e.copy}},[e._v(" 复制代码 ")]),a("el-button",{staticClass:"delete-btn",attrs:{icon:"el-icon-delete",type:"text"},on:{click:e.empty}},[e._v(" 清空 ")])],1),a("el-scrollbar",{staticClass:"center-scrollbar"},[a("el-row",{staticClass:"center-board-row",attrs:{gutter:e.formConf.gutter}},[a("el-form",{attrs:{size:e.formConf.size,"label-position":e.formConf.labelPosition,disabled:e.formConf.disabled,"label-width":e.formConf.labelWidth+"px"}},[a("draggable",{staticClass:"drawing-board",attrs:{list:e.drawingList,animation:340,group:"componentsGroup"}},e._l(e.drawingList,(function(t,o){return a("draggable-item",{key:t.renderKey,attrs:{"drawing-list":e.drawingList,element:t,index:o,"active-id":e.activeId,"form-conf":e.formConf},on:{activeItem:e.activeFormItem,copyItem:e.drawingItemCopy,deleteItem:e.drawingItemDelete}})})),1),a("div",{directives:[{name:"show",rawName:"v-show",value:!e.drawingList.length,expression:"!drawingList.length"}],staticClass:"empty-info"},[e._v(" 从左侧拖入或点选组件进行表单设计 ")])],1)],1)],1)],1),a("right-panel",{attrs:{"active-data":e.activeData,"form-conf":e.formConf,"show-field":!!e.drawingList.length},on:{"tag-change":e.tagChange}}),a("form-drawer",{attrs:{visible:e.drawerVisible,"form-data":e.formData,size:"100%","generate-conf":e.generateConf},on:{"update:visible":function(t){e.drawerVisible=t}}}),a("json-drawer",{attrs:{size:"60%",visible:e.jsonDrawerVisible,"json-str":JSON.stringify(e.formData)},on:{"update:visible":function(t){e.jsonDrawerVisible=t},refresh:e.refreshJson}}),a("code-type-dialog",{attrs:{visible:e.dialogVisible,title:"选择生成类型","show-file-name":e.showFileName},on:{"update:visible":function(t){e.dialogVisible=t},confirm:e.generate}}),a("input",{attrs:{id:"copyNode",type:"hidden"}})],1)},h=[],b=(a("c740"),a("4160"),a("d81d"),a("a434"),a("b64b"),a("ac1f"),a("5319"),a("159b"),a("53ca")),g=a("5530"),w=a("310e"),y=a.n(w),D=a("cc06"),x=a("21a6"),k=a("b311"),C=a.n(k),O=a("4758"),M=function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("div",[a("el-drawer",e._g(e._b({on:{opened:e.onOpen,close:e.onClose}},"el-drawer",e.$attrs,!1),e.$listeners),[a("div",{staticStyle:{height:"100%"}},[a("el-row",{staticStyle:{height:"100%",overflow:"auto"}},[a("el-col",{staticClass:"left-editor",attrs:{md:24,lg:12}},[a("div",{staticClass:"setting",attrs:{title:"资源引用"},on:{click:e.showResource}},[a("el-badge",{staticClass:"item",attrs:{"is-dot":!!e.resources.length}},[a("i",{staticClass:"el-icon-setting"})])],1),a("el-tabs",{staticClass:"editor-tabs",attrs:{type:"card"},model:{value:e.activeTab,callback:function(t){e.activeTab=t},expression:"activeTab"}},[a("el-tab-pane",{attrs:{name:"html"}},[a("span",{attrs:{slot:"label"},slot:"label"},["html"===e.activeTab?a("i",{staticClass:"el-icon-edit"}):a("i",{staticClass:"el-icon-document"}),e._v(" template ")])]),a("el-tab-pane",{attrs:{name:"js"}},[a("span",{attrs:{slot:"label"},slot:"label"},["js"===e.activeTab?a("i",{staticClass:"el-icon-edit"}):a("i",{staticClass:"el-icon-document"}),e._v(" script ")])]),a("el-tab-pane",{attrs:{name:"css"}},[a("span",{attrs:{slot:"label"},slot:"label"},["css"===e.activeTab?a("i",{staticClass:"el-icon-edit"}):a("i",{staticClass:"el-icon-document"}),e._v(" css ")])])],1),a("div",{directives:[{name:"show",rawName:"v-show",value:"html"===e.activeTab,expression:"activeTab==='html'"}],staticClass:"tab-editor",attrs:{id:"editorHtml"}}),a("div",{directives:[{name:"show",rawName:"v-show",value:"js"===e.activeTab,expression:"activeTab==='js'"}],staticClass:"tab-editor",attrs:{id:"editorJs"}}),a("div",{directives:[{name:"show",rawName:"v-show",value:"css"===e.activeTab,expression:"activeTab==='css'"}],staticClass:"tab-editor",attrs:{id:"editorCss"}})],1),a("el-col",{staticClass:"right-preview",attrs:{md:24,lg:12}},[a("div",{staticClass:"action-bar",style:{"text-align":"left"}},[a("span",{staticClass:"bar-btn",on:{click:e.runCode}},[a("i",{staticClass:"el-icon-refresh"}),e._v(" 刷新 ")]),a("span",{staticClass:"bar-btn",on:{click:e.exportFile}},[a("i",{staticClass:"el-icon-download"}),e._v(" 导出vue文件 ")]),a("span",{ref:"copyBtn",staticClass:"bar-btn copy-btn"},[a("i",{staticClass:"el-icon-document-copy"}),e._v(" 复制代码 ")]),a("span",{staticClass:"bar-btn delete-btn",on:{click:function(t){return e.$emit("update:visible",!1)}}},[a("i",{staticClass:"el-icon-circle-close"}),e._v(" 关闭 ")])]),a("iframe",{directives:[{name:"show",rawName:"v-show",value:e.isIframeLoaded,expression:"isIframeLoaded"}],ref:"previewPage",staticClass:"result-wrapper",attrs:{frameborder:"0",src:"preview.html"},on:{load:e.iframeLoad}}),a("div",{directives:[{name:"show",rawName:"v-show",value:!e.isIframeLoaded,expression:"!isIframeLoaded"},{name:"loading",rawName:"v-loading",value:!0,expression:"true"}],staticClass:"result-wrapper"})])],1)],1)]),a("resource-dialog",{attrs:{visible:e.resourceVisible,"origin-resource":e.resources},on:{"update:visible":function(t){e.resourceVisible=t},save:e.setResource}})],1)},E=[],I=(a("99af"),a("8a79"),a("1861")),L=(a("a15b"),a("45fc"),a("b0c0"),a("beaa"));function j(e){return'\n '.concat(e,'\n
\n 取消\n 确定\n
\n
')}function T(e){return"")}function z(e){return"
\ No newline at end of file diff --git a/stop.sh b/stop.sh new file mode 100644 index 0000000..6dd9dc4 --- /dev/null +++ b/stop.sh @@ -0,0 +1,4 @@ +#!/bin/bash +killall go-admin # kill go-admin service +echo "stop go-admin success" +ps -aux | grep go-admin \ No newline at end of file diff --git a/template/api_migrate.template b/template/api_migrate.template new file mode 100644 index 0000000..6f39164 --- /dev/null +++ b/template/api_migrate.template @@ -0,0 +1,326 @@ +package version + +import ( + "gorm.io/gorm" + "runtime" + "time" + + "github.com/go-admin-team/go-admin-core/sdk/pkg" + + "go-admin/cmd/migrate/migration" + common "go-admin/common/models" +) + +type Menu struct { + MenuId int `json:"menuId" gorm:"primaryKey;autoIncrement"` + MenuName string `json:"menuName" gorm:"size:128;"` + Title string `json:"title" gorm:"size:128;"` + Icon string `json:"icon" gorm:"size:128;"` + Path string `json:"path" gorm:"size:128;"` + Paths string `json:"paths" gorm:"size:128;"` + MenuType string `json:"menuType" gorm:"size:1;"` + Action string `json:"action" gorm:"size:16;"` + Permission string `json:"permission" gorm:"size:255;"` + ParentId int `json:"parentId" gorm:"size:11;"` + NoCache bool `json:"noCache" gorm:"size:8;"` + Breadcrumb string `json:"breadcrumb" gorm:"size:255;"` + Component string `json:"component" gorm:"size:255;"` + Sort int `json:"sort" gorm:"size:4;"` + Visible string `json:"visible" gorm:"size:1;"` + CreateBy string `json:"createBy" gorm:"size:128;"` + UpdateBy string `json:"updateBy" gorm:"size:128;"` + IsFrame string `json:"isFrame" gorm:"size:1;DEFAULT:0;"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt *time.Time `json:"deletedAt"` +} + +func (Menu) TableName() string { + return "sys_menu" +} + +func init() { + _, fileName, _, _ := runtime.Caller(0) + migration.Migrate.SetVersion(migration.GetFilename(fileName), _{{.GenerateTime}}Test) +} + +func _{{.GenerateTime}}Test(db *gorm.DB, version string) error { + return db.Transaction(func(tx *gorm.DB) error { + + timeNow := pkg.GetCurrentTime() + Mmenu := Menu{} + Mmenu.MenuName = "{{.TBName}}Manage" + Mmenu.Title = "{{.TableComment}}" + Mmenu.Icon = "pass" + Mmenu.Path = "/{{.TBName}}" + Mmenu.MenuType = "M" + Mmenu.Action = "无" + Mmenu.ParentId = 0 + Mmenu.NoCache = false + Mmenu.Component = "Layout" + Mmenu.Sort = 0 + Mmenu.Visible = "0" + Mmenu.IsFrame = "0" + Mmenu.CreateBy = "1" + Mmenu.UpdateBy = "1" + Mmenu.CreatedAt = timeNow + Mmenu.UpdatedAt = timeNow + // Mmenu.MenuId, err = Mmenu.Create(db) + err := tx.Create(&Mmenu).Error + if err != nil { + return err + } + Cmenu := Menu{} + Cmenu.MenuName = "{{.TBName}}" + Cmenu.Title = "{{.TableComment}}" + Cmenu.Icon = "pass" + Cmenu.Path = "{{.TBName}}" + Cmenu.MenuType = "C" + Cmenu.Action = "无" + Cmenu.Permission = "{{.PackageName}}:{{.BusinessName}}:list" + Cmenu.ParentId = Mmenu.MenuId + Cmenu.NoCache = false + Cmenu.Component = "/{{.BusinessName}}/index" + Cmenu.Sort = 0 + Cmenu.Visible = "0" + Cmenu.IsFrame = "0" + Cmenu.CreateBy = "1" + Cmenu.UpdateBy = "1" + Cmenu.CreatedAt = timeNow + Cmenu.UpdatedAt = timeNow + // Cmenu.MenuId, err = Cmenu.Create(db) + err = tx.Create(&Cmenu).Error + if err != nil { + return err + } + + MList := Menu{} + MList.MenuName = "" + MList.Title = "分页获取{{.TableComment}}" + MList.Icon = "" + MList.Path = "{{.TBName}}" + MList.MenuType = "F" + MList.Action = "无" + MList.Permission = "{{.PackageName}}:{{.BusinessName}}:query" + MList.ParentId = Cmenu.MenuId + MList.NoCache = false + MList.Sort = 0 + MList.Visible = "0" + MList.IsFrame = "0" + MList.CreateBy = "1" + MList.UpdateBy = "1" + MList.CreatedAt = timeNow + MList.UpdatedAt = timeNow + // MList.MenuId, err = MList.Create(db) + err = tx.Create(&MList).Error + if err != nil { + return err + } + + MCreate := Menu{} + MCreate.MenuName = "" + MCreate.Title = "创建{{.TableComment}}" + MCreate.Icon = "" + MCreate.Path = "{{.TBName}}" + MCreate.MenuType = "F" + MCreate.Action = "无" + MCreate.Permission = "{{.PackageName}}:{{.BusinessName}}:add" + MCreate.ParentId = Cmenu.MenuId + MCreate.NoCache = false + MCreate.Sort = 0 + MCreate.Visible = "0" + MCreate.IsFrame = "0" + MCreate.CreateBy = "1" + MCreate.UpdateBy = "1" + MCreate.CreatedAt = timeNow + MCreate.UpdatedAt = timeNow + // MCreate.MenuId, err = MCreate.Create(db) + err = tx.Create(&MCreate).Error + if err != nil { + return err + } + + MUpdate := Menu{} + MUpdate.MenuName = "" + MUpdate.Title = "修改{{.TableComment}}" + MUpdate.Icon = "" + MUpdate.Path = "{{.TBName}}" + MUpdate.MenuType = "F" + MUpdate.Action = "无" + MUpdate.Permission ="{{.PackageName}}:{{.BusinessName}}:edit" + MUpdate.ParentId = Cmenu.MenuId + MUpdate.NoCache = false + MUpdate.Sort = 0 + MUpdate.Visible = "0" + MUpdate.IsFrame = "0" + MUpdate.CreateBy = "1" + MUpdate.UpdateBy = "1" + MUpdate.CreatedAt = timeNow + MUpdate.UpdatedAt = timeNow + // MUpdate.MenuId, err = MUpdate.Create(db) + err = tx.Create(&MUpdate).Error + if err != nil { + return err + } + + MDelete := Menu{} + MDelete.MenuName = "" + MDelete.Title = "删除{{.TableComment}}" + MDelete.Icon = "" + MDelete.Path = "{{.TBName}}" + MDelete.MenuType = "F" + MDelete.Action = "无" + MDelete.Permission = "{{.PackageName}}:{{.BusinessName}}:remove" + MDelete.ParentId = Cmenu.MenuId + MDelete.NoCache = false + MDelete.Sort = 0 + MDelete.Visible = "0" + MDelete.IsFrame = "0" + MDelete.CreateBy = "1" + MDelete.UpdateBy = "1" + MDelete.CreatedAt = timeNow + MDelete.UpdatedAt = timeNow + // MDelete.MenuId, err = MDelete.Create(db) + err = tx.Create(&MDelete).Error + if err != nil { + return err + } + + var InterfaceId = 63 + Amenu := Menu{} + Amenu.MenuName = "{{.TBName}}" + Amenu.Title = "{{.TableComment}}" + Amenu.Icon = "bug" + Amenu.Path = "{{.TBName}}" + Amenu.MenuType = "M" + Amenu.Action = "无" + Amenu.ParentId = InterfaceId + Amenu.NoCache = false + Amenu.Sort = 0 + Amenu.Visible = "1" + Amenu.IsFrame = "0" + Amenu.CreateBy = "1" + Amenu.UpdateBy = "1" + Amenu.CreatedAt = timeNow + Amenu.UpdatedAt = timeNow + // Amenu.MenuId, err = Amenu.Create(db) + err = tx.Create(&Amenu).Error + if err != nil { + return err + } + + AList := Menu{} + AList.MenuName = "" + AList.Title = "分页获取{{.TableComment}}" + AList.Icon = "bug" + AList.Path = "/api/v1/{{.ModuleName}}" + AList.MenuType = "A" + AList.Action = "GET" + AList.ParentId = Amenu.MenuId + AList.NoCache = false + AList.Sort = 0 + AList.Visible = "1" + AList.IsFrame = "0" + AList.CreateBy = "1" + AList.UpdateBy = "1" + AList.CreatedAt = timeNow + AList.UpdatedAt = timeNow + // AList.MenuId, err = AList.Create(db) + err = tx.Create(&AList).Error + if err != nil { + return err + } + + AGet := Menu{} + AGet.MenuName = "" + AGet.Title = "根据id获取{{.TableComment}}" + AGet.Icon = "bug" + AGet.Path = "/api/v1/{{.ModuleName}}/:id" + AGet.MenuType = "A" + AGet.Action = "GET" + AGet.ParentId = Amenu.MenuId + AGet.NoCache = false + AGet.Sort = 0 + AGet.Visible = "1" + AGet.IsFrame = "0" + AGet.CreateBy = "1" + AGet.UpdateBy = "1" + AGet.CreatedAt = timeNow + AGet.UpdatedAt = timeNow + // AGet.MenuId, err = AGet.Create(db) + err = tx.Create(&AGet).Error + if err != nil { + return err + } + + ACreate := Menu{} + ACreate.MenuName = "" + ACreate.Title = "创建{{.TableComment}}" + ACreate.Icon = "bug" + ACreate.Path = "/api/v1/{{.ModuleName}}" + ACreate.MenuType = "A" + ACreate.Action = "POST" + ACreate.ParentId = Amenu.MenuId + ACreate.NoCache = false + ACreate.Sort = 0 + ACreate.Visible = "1" + ACreate.IsFrame = "0" + ACreate.CreateBy = "1" + ACreate.UpdateBy = "1" + ACreate.CreatedAt = timeNow + ACreate.UpdatedAt = timeNow + // ACreate.MenuId, err = ACreate.Create(db) + err = tx.Create(&ACreate).Error + if err != nil { + return err + } + + AUpdate := Menu{} + AUpdate.MenuName = "" + AUpdate.Title = "修改{{.TableComment}}" + AUpdate.Icon = "bug" + AUpdate.Path = "/api/v1/{{.ModuleName}}/:id" + AUpdate.MenuType = "A" + AUpdate.Action = "PUT" + AUpdate.ParentId = Amenu.MenuId + AUpdate.NoCache = false + AUpdate.Sort = 0 + AUpdate.Visible = "1" + AUpdate.IsFrame = "0" + AUpdate.CreateBy = "1" + AUpdate.UpdateBy = "1" + AUpdate.CreatedAt = timeNow + AUpdate.UpdatedAt = timeNow + // AUpdate.MenuId, err = AUpdate.Create(db) + err = tx.Create(&AUpdate).Error + if err != nil { + return err + } + + ADelete := Menu{} + ADelete.MenuName = "" + ADelete.Title = "删除{{.TableComment}}" + ADelete.Icon = "bug" + ADelete.Path = "/api/v1/{{.ModuleName}}" + ADelete.MenuType = "A" + ADelete.Action = "DELETE" + ADelete.ParentId = Amenu.MenuId + ADelete.NoCache = false + ADelete.Sort = 0 + ADelete.Visible = "1" + ADelete.IsFrame = "0" + ADelete.CreateBy = "1" + ADelete.UpdateBy = "1" + ADelete.CreatedAt = timeNow + ADelete.UpdatedAt = timeNow + //ADelete.MenuId, err = ADelete.Create(db) + err = tx.Create(&ADelete).Error + if err != nil { + return err + } + + return tx.Create(&common.Migration{ + Version: version, + }).Error + }) +} \ No newline at end of file diff --git a/template/cmd_api.template b/template/cmd_api.template new file mode 100644 index 0000000..f58e37f --- /dev/null +++ b/template/cmd_api.template @@ -0,0 +1,8 @@ +package api + +import "go-admin/app/{{.appName}}/router" + +func init() { + //注册路由 fixme 其他应用的路由,在本目录新建文件放在init方法 + AppRouters = append(AppRouters, router.InitRouter) +} \ No newline at end of file diff --git a/template/migrate.template b/template/migrate.template new file mode 100644 index 0000000..f30de78 --- /dev/null +++ b/template/migrate.template @@ -0,0 +1,40 @@ +package {{.Package}} + +import ( + "gorm.io/gorm" + "runtime" + + "go-admin/cmd/migrate/migration" + common "go-admin/common/models" +) + +func init() { + _, fileName, _, _ := runtime.Caller(0) + migration.Migrate.SetVersion(migration.GetFilename(fileName), _{{.GenerateTime}}Test) +} + +func _{{.GenerateTime}}Test(db *gorm.DB, version string) error { + return db.Transaction(func(tx *gorm.DB) error { + + // TODO: 这里开始写入要变更的内容 + + // TODO: 例如 修改表字段 使用过程中请删除此段代码 + //err := tx.Migrator().RenameColumn(&models.SysConfig{}, "config_id", "id") + //if err != nil { + // return err + //} + + // TODO: 例如 新增表结构 使用过程中请删除此段代码 + //err = tx.Debug().Migrator().AutoMigrate( + // new(models.CasbinRule), + // ) + //if err != nil { + // return err + //} + + + return tx.Create(&common.Migration{ + Version: version, + }).Error + }) +} diff --git a/template/router.template b/template/router.template new file mode 100644 index 0000000..b11e9b9 --- /dev/null +++ b/template/router.template @@ -0,0 +1,75 @@ +package router + + +import ( + "github.com/gin-gonic/gin" + _ "github.com/gin-gonic/gin" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk" + // "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + common "go-admin/common/middleware" + "os" +) + +var ( + routerNoCheckRole = make([]func(*gin.RouterGroup), 0) + routerCheckRole = make([]func(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware), 0) +) + +// InitRouter 路由初始化 +func InitRouter() { + var r *gin.Engine + h := sdk.Runtime.GetEngine() + if h == nil { + h = gin.New() + sdk.Runtime.SetEngine(h) + } + switch h.(type) { + case *gin.Engine: + r = h.(*gin.Engine) + default: + log.Fatal("not support other engine") + os.Exit(-1) + } + + // the jwt middleware + authMiddleware, err := common.AuthInit() + if err != nil { + log.Fatalf("JWT Init Error, %s", err.Error()) + } + + // 注册业务路由 + InitBusinessRouter(r, authMiddleware) +} + +func InitBusinessRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gin.Engine { + + // 无需认证的路由 + noCheckRoleRouter(r) + // 需要认证的路由 + checkRoleRouter(r, authMiddleware) + + return r +} + +// noCheckRoleRouter 无需认证的路由 +func noCheckRoleRouter(r *gin.Engine) { + // 可根据业务需求来设置接口版本 + v := r.Group("/api/v1") + + for _, f := range routerNoCheckRole { + f(v) + } +} + +// checkRoleRouter 需要认证的路由 +func checkRoleRouter(r *gin.Engine, authMiddleware *jwtauth.GinJWTMiddleware) { + // 可根据业务需求来设置接口版本 + v := r.Group("/api/v1") + + for _, f := range routerCheckRole { + f(v, authMiddleware) + } +} diff --git a/template/v4/actions/router_check_role.go.template b/template/v4/actions/router_check_role.go.template new file mode 100644 index 0000000..3c3c069 --- /dev/null +++ b/template/v4/actions/router_check_role.go.template @@ -0,0 +1,31 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/{{.PackageName}}/models" + "go-admin/app/{{.PackageName}}/service/dto" + "go-admin/common/actions" + "go-admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, register{{.ClassName}}Router) +} + +// 需认证的路由代码 +func register{{.ClassName}}Router(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + r := v1.Group("/{{.ModuleName}}").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + model := &models.{{.ClassName}}{} + r.GET("", actions.PermissionAction(), actions.IndexAction(model, new(dto.{{.ClassName}}Search), func() interface{} { + list := make([]models.{{.ClassName}}, 0) + return &list + })) + r.GET("/:id", actions.PermissionAction(), actions.ViewAction(new(dto.{{.ClassName}}ById), nil)) + r.POST("", actions.CreateAction(new(dto.{{.ClassName}}Control))) + r.PUT("/:id", actions.PermissionAction(), actions.UpdateAction(new(dto.{{.ClassName}}Control))) + r.DELETE("", actions.PermissionAction(), actions.DeleteAction(new(dto.{{.ClassName}}ById))) + } +} diff --git a/template/v4/actions/router_no_check_role.go.template b/template/v4/actions/router_no_check_role.go.template new file mode 100644 index 0000000..0154557 --- /dev/null +++ b/template/v4/actions/router_no_check_role.go.template @@ -0,0 +1,30 @@ +package router + +import ( + "github.com/gin-gonic/gin" + + "go-admin/app/{{.PackageName}}/middleware" + "go-admin/app/{{.PackageName}}/models" + "go-admin/app/{{.PackageName}}/service/dto" + "go-admin/common/actions" +) + +func init() { + routerNoCheckRole = append(routerNoCheckRole, register{{.ClassName}}Router) +} + +// 无需认证的路由代码 +func register{{.ClassName}}Router(v1 *gin.RouterGroup) { + r := v1.Group("/{{.ModuleName}}") + { + model := &models.{{.ClassName}}{} + r.GET("", actions.IndexAction(model, new(dto.{{.ClassName}}Search), func() interface{} { + list := make([]models.{{.ClassName}}, 0) + return &list + })) + r.GET("/:id", actions.ViewAction(new(dto.{{.ClassName}}ById), nil)) + r.POST("", actions.CreateAction(new(dto.{{.ClassName}}Control))) + r.PUT("/:id", actions.UpdateAction(new(dto.{{.ClassName}}Control))) + r.DELETE("", actions.DeleteAction(new(dto.{{.ClassName}}ById))) + } +} diff --git a/template/v4/dto.go.template b/template/v4/dto.go.template new file mode 100644 index 0000000..428b89a --- /dev/null +++ b/template/v4/dto.go.template @@ -0,0 +1,151 @@ +package dto + +import ( + {{- $bb := false -}} + {{- range .Columns -}} + {{- $z := .IsQuery -}} + {{- if ($z) -}} + {{- if eq .GoType "time.Time" -}}{{- $bb = true -}}{{- end -}} + {{- end -}} + {{- end -}} + {{- range .Columns -}} + {{- if eq .GoField "CreatedAt" -}} + {{- else if eq .GoField "UpdatedAt" -}} + {{- else if eq .GoField "DeletedAt" -}} + {{- else -}} + {{- if eq .GoType "time.Time" -}}{{- $bb = true -}}{{- end -}} + {{- end -}} + {{- end -}} + {{- if eq $bb true }} + "time" + {{- end }} + + "go-admin/app/{{.PackageName}}/models" + "go-admin/common/dto" + common "go-admin/common/models" +) + +type {{.ClassName}}GetPageReq struct { + dto.Pagination `search:"-"` + {{- $tablename := .TBName -}} + {{- range .Columns -}} + {{$z := .IsQuery}} + {{- if ($z) }} + {{.GoField}} {{.GoType}} `form:"{{.JsonField}}" search:"type:{{if eq .QueryType "EQ"}}exact{{ else if eq .QueryType "NE"}}iexact{{ else if eq .QueryType "LIKE"}}contains{{ else if eq .QueryType "GT"}}gt{{ else if eq .QueryType "GTE"}}gte{{ else if eq .QueryType "LT"}}lt{{ else if eq .QueryType "LTE"}}lte{{- end }};column:{{.ColumnName}};table:{{$tablename}}" comment:"{{.ColumnComment}}"` + {{- end }} + {{- end }} + {{.ClassName}}Order +} + +type {{.ClassName}}Order struct { + {{ $tablename := .TBName }} + {{- range .Columns -}} + {{.GoField}} string `form:"{{.JsonField}}Order" search:"type:order;column:{{.ColumnName}};table:{{$tablename}}"` + {{ end }} +} + +func (m *{{.ClassName}}GetPageReq) GetNeedSearch() interface{} { + return *m +} + +type {{.ClassName}}InsertReq struct { + {{- range .Columns -}} + {{$x := .Pk}} + {{- if ($x) }} + {{.GoField}} {{.GoType}} `json:"-" comment:"{{.ColumnComment}}"` // {{.ColumnComment}} + {{- else if eq .GoField "CreatedAt" -}} + {{- else if eq .GoField "UpdatedAt" -}} + {{- else if eq .GoField "DeletedAt" -}} + {{- else if eq .GoField "CreateBy" -}} + {{- else if eq .GoField "UpdateBy" -}} + {{- else }} + {{.GoField}} {{.GoType}} `json:"{{.JsonField}}" comment:"{{.ColumnComment}}"` + {{- end -}} + {{- end }} + common.ControlBy +} + +func (s *{{.ClassName}}InsertReq) Generate(model *models.{{.ClassName}}) { + {{- range .Columns -}} + {{$x := .Pk}} + {{- if ($x) }} + if s.{{.GoField}} == 0 { + model.Model = common.Model{ {{.GoField}}: s.{{.GoField}} } + } + {{- else if eq .GoField "CreatedAt" -}} + {{- else if eq .GoField "UpdatedAt" -}} + {{- else if eq .GoField "DeletedAt" -}} + {{- else if eq .GoField "CreateBy"}} + model.{{.GoField}} = s.{{.GoField}} // 添加这而,需要记录是被谁创建的 + {{- else if eq .GoField "UpdateBy" -}} + {{- else }} + model.{{.GoField}} = s.{{.GoField}} + {{- end -}} + {{- end }} +} + +func (s *{{.ClassName}}InsertReq) GetId() interface{} { + return s.{{.PkGoField}} +} + +type {{.ClassName}}UpdateReq struct { + {{- range .Columns -}} + {{$x := .Pk}} + {{- if ($x) }} + {{.GoField}} {{.GoType}} `uri:"{{.JsonField}}" comment:"{{.ColumnComment}}"` // {{.ColumnComment}} + {{- else if eq .GoField "CreatedAt" -}} + {{- else if eq .GoField "UpdatedAt" -}} + {{- else if eq .GoField "DeletedAt" -}} + {{- else if eq .GoField "CreateBy" -}} + {{- else if eq .GoField "UpdateBy" -}} + {{- else }} + {{.GoField}} {{.GoType}} `json:"{{.JsonField}}" comment:"{{.ColumnComment}}"` + {{- end -}} + {{- end }} + common.ControlBy +} + +func (s *{{.ClassName}}UpdateReq) Generate(model *models.{{.ClassName}}) { + {{- range .Columns -}} + {{$x := .Pk}} + {{- if ($x) }} + if s.{{.GoField}} == 0 { + model.Model = common.Model{ {{.GoField}}: s.{{.GoField}} } + } + {{- else if eq .GoField "CreatedAt" -}} + {{- else if eq .GoField "UpdatedAt" -}} + {{- else if eq .GoField "DeletedAt" -}} + {{- else if eq .GoField "CreateBy" -}} + {{- else if eq .GoField "UpdateBy"}} + model.{{.GoField}} = s.{{.GoField}} // 添加这而,需要记录是被谁更新的 + {{- else }} + model.{{.GoField}} = s.{{.GoField}} + {{- end -}} + {{- end }} +} + +func (s *{{.ClassName}}UpdateReq) GetId() interface{} { + return s.{{.PkGoField}} +} + +// {{.ClassName}}GetReq 功能获取请求参数 +type {{.ClassName}}GetReq struct { + {{- range .Columns -}} + {{$x := .Pk}} + {{- if ($x) }} + {{.GoField}} {{.GoType}} `uri:"{{.JsonField}}"` + {{- end }} + {{- end }} +} +func (s *{{.ClassName}}GetReq) GetId() interface{} { + return s.{{.PkGoField}} +} + +// {{.ClassName}}DeleteReq 功能删除请求参数 +type {{.ClassName}}DeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *{{.ClassName}}DeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/template/v4/js.go.template b/template/v4/js.go.template new file mode 100644 index 0000000..db3d4a2 --- /dev/null +++ b/template/v4/js.go.template @@ -0,0 +1,47 @@ +import request from '@/utils/request' + +// 查询{{.ClassName}}列表 +export function list{{.ClassName}}(query) { + return request({ + url: '/api/v1/{{.ModuleName}}', + method: 'get', + params: query + }) +} + +// 查询{{.ClassName}}详细 +export function get{{.ClassName}} ({{.PkJsonField}}) { + return request({ + url: '/api/v1/{{.ModuleName}}/' + {{.PkJsonField}}, + method: 'get' + }) +} + + +// 新增{{.ClassName}} +export function add{{.ClassName}}(data) { + return request({ + url: '/api/v1/{{.ModuleName}}', + method: 'post', + data: data + }) +} + +// 修改{{.ClassName}} +export function update{{.ClassName}}(data) { + return request({ + url: '/api/v1/{{.ModuleName}}/'+data.{{.PkJsonField}}, + method: 'put', + data: data + }) +} + +// 删除{{.ClassName}} +export function del{{.ClassName}}(data) { + return request({ + url: '/api/v1/{{.ModuleName}}', + method: 'delete', + data: data + }) +} + diff --git a/template/v4/model.go.template b/template/v4/model.go.template new file mode 100644 index 0000000..2f56123 --- /dev/null +++ b/template/v4/model.go.template @@ -0,0 +1,55 @@ +package models + +import ( + {{- $bb := false -}} + {{- range .Columns -}} + {{- $z := .IsQuery -}} + {{- if ($z) -}} + {{- if eq .GoType "time.Time" -}}{{- $bb = true -}}{{- end -}} + {{- end -}} + {{- end -}} + {{- range .Columns -}} + {{- if eq .GoField "CreatedAt" -}} + {{- else if eq .GoField "UpdatedAt" -}} + {{- else if eq .GoField "DeletedAt" -}} + {{- else -}} + {{- if eq .GoType "time.Time" -}}{{- $bb = true -}}{{- end -}} + {{- end -}} + {{- end -}} + {{- if eq $bb true }} + "time" + {{- end }} + + "go-admin/common/models" + +) + +type {{.ClassName}} struct { + models.Model + {{ range .Columns -}} + {{$x := .Pk}} + {{- if ($x) }} + {{- else if eq .GoField "CreatedAt" -}} + {{- else if eq .GoField "UpdatedAt" -}} + {{- else if eq .GoField "DeletedAt" -}} + {{- else if eq .GoField "CreateBy" -}} + {{- else if eq .GoField "UpdateBy" -}} + {{- else }} + {{.GoField}} {{.GoType}} `json:"{{.JsonField}}" gorm:"type:{{.ColumnType}};comment:{{- if eq .ColumnComment "" -}}{{.GoField}}{{- else -}}{{.ColumnComment}}{{end -}}"` {{end -}} + {{- end }} + models.ModelTime + models.ControlBy +} + +func ({{.ClassName}}) TableName() string { + return "{{.TBName}}" +} + +func (e *{{.ClassName}}) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *{{.ClassName}}) GetId() interface{} { + return e.{{.PkGoField}} +} \ No newline at end of file diff --git a/template/v4/no_actions/apis.go.template b/template/v4/no_actions/apis.go.template new file mode 100644 index 0000000..7878776 --- /dev/null +++ b/template/v4/no_actions/apis.go.template @@ -0,0 +1,198 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/{{.PackageName}}/models" + "go-admin/app/{{.PackageName}}/service" + "go-admin/app/{{.PackageName}}/service/dto" + "go-admin/common/actions" +) + +type {{.ClassName}} struct { + api.Api +} + +// GetPage 获取{{.TableComment}}列表 +// @Summary 获取{{.TableComment}}列表 +// @Description 获取{{.TableComment}}列表 +// @Tags {{.TableComment}} +{{- $tablename := .TBName -}} +{{- range .Columns -}} +{{$z := .IsQuery}} +{{- if ($z) }} +// @Param {{.JsonField}} query {{.GoType}} false "{{.ColumnComment}}" +{{- end -}} +{{- end }} +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.{{.ClassName}}}} "{"code": 200, "data": [...]}" +// @Router /api/v1/{{.ModuleName}} [get] +// @Security Bearer +func (e {{.ClassName}}) GetPage(c *gin.Context) { + req := dto.{{.ClassName}}GetPageReq{} + s := service.{{.ClassName}}{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.{{.ClassName}}, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取{{.TableComment}}失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取{{.TableComment}} +// @Summary 获取{{.TableComment}} +// @Description 获取{{.TableComment}} +// @Tags {{.TableComment}} +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.{{.ClassName}}} "{"code": 200, "data": [...]}" +// @Router /api/v1/{{.ModuleName}}/{id} [get] +// @Security Bearer +func (e {{.ClassName}}) Get(c *gin.Context) { + req := dto.{{.ClassName}}GetReq{} + s := service.{{.ClassName}}{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.{{.ClassName}} + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取{{.TableComment}}失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK( object, "查询成功") +} + +// Insert 创建{{.TableComment}} +// @Summary 创建{{.TableComment}} +// @Description 创建{{.TableComment}} +// @Tags {{.TableComment}} +// @Accept application/json +// @Product application/json +// @Param data body dto.{{.ClassName}}InsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/{{.ModuleName}} [post] +// @Security Bearer +func (e {{.ClassName}}) Insert(c *gin.Context) { + req := dto.{{.ClassName}}InsertReq{} + s := service.{{.ClassName}}{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建{{.TableComment}}失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改{{.TableComment}} +// @Summary 修改{{.TableComment}} +// @Description 修改{{.TableComment}} +// @Tags {{.TableComment}} +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.{{.ClassName}}UpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/{{.ModuleName}}/{id} [put] +// @Security Bearer +func (e {{.ClassName}}) Update(c *gin.Context) { + req := dto.{{.ClassName}}UpdateReq{} + s := service.{{.ClassName}}{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改{{.TableComment}}失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "修改成功") +} + +// Delete 删除{{.TableComment}} +// @Summary 删除{{.TableComment}} +// @Description 删除{{.TableComment}} +// @Tags {{.TableComment}} +// @Param data body dto.{{.ClassName}}DeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/{{.ModuleName}} [delete] +// @Security Bearer +func (e {{.ClassName}}) Delete(c *gin.Context) { + s := service.{{.ClassName}}{} + req := dto.{{.ClassName}}DeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除{{.TableComment}}失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK( req.GetId(), "删除成功") +} diff --git a/template/v4/no_actions/router_check_role.go.template b/template/v4/no_actions/router_check_role.go.template new file mode 100644 index 0000000..f8a3c3a --- /dev/null +++ b/template/v4/no_actions/router_check_role.go.template @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/{{.PackageName}}/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, register{{.ClassName}}Router) +} + +// register{{.ClassName}}Router +func register{{.ClassName}}Router(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.{{.ClassName}}{} + r := v1.Group("/{{.ModuleName}}").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/template/v4/no_actions/router_no_check_role.go.template b/template/v4/no_actions/router_no_check_role.go.template new file mode 100644 index 0000000..fc26fe6 --- /dev/null +++ b/template/v4/no_actions/router_no_check_role.go.template @@ -0,0 +1,25 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/{{.PackageName}}/apis" +) + +func init() { + routerCheckRole = append(routerCheckRole, register{{.ClassName}}Router) +} + +// register{{.ClassName}}Router +func register{{.ClassName}}Router(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.{{.ClassName}}{} + r := v1.Group("/{{.ModuleName}}").Use(authMiddleware.MiddlewareFunc()) + { + r.GET("", api.GetPage) + r.GET("/:id", api.Get) + r.POST("", api.Insert) + r.PUT("/:id", api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/template/v4/no_actions/service.go.template b/template/v4/no_actions/service.go.template new file mode 100644 index 0000000..84e8886 --- /dev/null +++ b/template/v4/no_actions/service.go.template @@ -0,0 +1,109 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/{{.PackageName}}/models" + "go-admin/app/{{.PackageName}}/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" +) + +type {{.ClassName}} struct { + service.Service +} + +// GetPage 获取{{.ClassName}}列表 +func (e *{{.ClassName}}) GetPage(c *dto.{{.ClassName}}GetPageReq, p *actions.DataPermission, list *[]models.{{.ClassName}}, count *int64) error { + var err error + var data models.{{.ClassName}} + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("{{.ClassName}}Service GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取{{.ClassName}}对象 +func (e *{{.ClassName}}) Get(d *dto.{{.ClassName}}GetReq, p *actions.DataPermission, model *models.{{.ClassName}}) error { + var data models.{{.ClassName}} + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service Get{{.ClassName}} error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建{{.ClassName}}对象 +func (e *{{.ClassName}}) Insert(c *dto.{{.ClassName}}InsertReq) error { + var err error + var data models.{{.ClassName}} + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("{{.ClassName}}Service Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改{{.ClassName}}对象 +func (e *{{.ClassName}}) Update(c *dto.{{.ClassName}}UpdateReq, p *actions.DataPermission) error { + var err error + var data = models.{{.ClassName}}{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("{{.ClassName}}Service Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除{{.ClassName}} +func (e *{{.ClassName}}) Remove(d *dto.{{.ClassName}}DeleteReq, p *actions.DataPermission) error { + var data models.{{.ClassName}} + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service Remove{{.ClassName}} error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} diff --git a/template/v4/vue.go.template b/template/v4/vue.go.template new file mode 100644 index 0000000..ff70016 --- /dev/null +++ b/template/v4/vue.go.template @@ -0,0 +1,485 @@ +{{$tableComment:=.TableComment}} + + + diff --git a/test/api.go.template b/test/api.go.template new file mode 100644 index 0000000..1ae26ca --- /dev/null +++ b/test/api.go.template @@ -0,0 +1,126 @@ +package apis + +import ( +"github.com/gin-gonic/gin" +"github.com/gin-gonic/gin/binding" +"go-admin/models" +"github.com/go-admin-team/go-admin-core/sdk/pkg" +"go-admin/utils" +"net/http" +) + +// @Summary 配置列表数据 +// @Description 获取JSON +// @Tags 配置 +// @Param configKey query string false "configKey" +// @Param configName query string false "configName" +// @Param configType query string false "configType" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} app.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/configList [get] +// @Security Bearer +func Get{{.ClassName}}List(c *gin.Context) { +var data models.{{.ClassName}} +var err error +var pageSize = 10 +var pageIndex = 1 + +if size := c.Request.FormValue("pageSize"); size != "" { +pageSize = pkg.StrToInt(err, size) +} + +if index := c.Request.FormValue("pageIndex"); index != "" { +pageIndex = pkg.StrToInt(err, index) +} + +{{ range .Columns -}} + {{$z := .IsQuery}} + {{- if ($z) -}} + data.{{.GoField}} = c.Request.FormValue("{{.JsonField}}") + {{ end }} +{{- end -}} + +data.DataScope = utils.GetUserIdStr(c) +result, count, err := data.GetPage(pageSize, pageIndex) +pkg.HasError(err, "", -1) + +var mp = make(map[string]interface{}, 3) +mp["list"] = result +mp["count"] = count +mp["pageIndex"] = pageIndex +mp["pageIndex"] = pageSize + +var res app.Response +res.Data = mp + +c.JSON(http.StatusOK, res.ReturnOK()) +} + +// @Summary 获取配置 +// @Description 获取JSON +// @Tags 配置 +// @Param configId path int true "配置编码" +// @Success 200 {object} app.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/config/{configId} [get] +// @Security Bearer +func Get{{.ClassName}}(c *gin.Context) { +var data models.{{.ClassName}} +data.{{.PkGoField}}, _ = utils.StringToInt(c.Param("{{.PkJsonField}}")) +result, err := data.Get() +pkg.HasError(err, "抱歉未找到相关信息", -1) + +var res app.Response +res.Data = result + +c.JSON(http.StatusOK, res.ReturnOK()) +} + +// @Summary 添加配置 +// @Description 获取JSON +// @Tags 配置 +// @Accept application/json +// @Product application/json +// @Param data body models.{{.ClassName}} true "data" +// @Success 200 {string} string "{"code": 200, "message": "添加成功"}" +// @Success 200 {string} string "{"code": -1, "message": "添加失败"}" +// @Router /api/v1/dict/data [post] +// @Security Bearer +func Insert{{.ClassName}}(c *gin.Context) { +var data models.{{.ClassName}} +err := c.BindWith(&data, binding.JSON) +data.CreateBy = utils.GetUserIdStr(c) +pkg.HasError(err, "", 500) +result, err := data.Create() +pkg.HasError(err, "", -1) + +var res app.Response +res.Data = result +c.JSON(http.StatusOK, res.ReturnOK()) + +} + +func Update{{.ClassName}}(c *gin.Context) { +var data models.{{.ClassName}} +err := c.BindWith(&data, binding.JSON) +pkg.HasError(err, "数据解析失败", -1) +data.UpdateBy = utils.GetUserIdStr(c) +result, err := data.Update(data.{{.PkGoField}}) +pkg.HasError(err, "", -1) + +var res app.Response +res.Data = result +c.JSON(http.StatusOK, res.ReturnOK()) +} + +func Delete{{.ClassName}}(c *gin.Context) { +var data models.{{.ClassName}} +id, err := utils.StringToInt(c.Param("{{.PkJsonField}}")) +data.UpdateBy = utils.GetUserIdStr(c) +_, err = data.Delete(id) +pkg.HasError(err, "修改失败", 500) + +var res app.Response +res.Msg = "删除成功" +c.JSON(http.StatusOK, res.ReturnOK()) +} diff --git a/test/gen_test.go b/test/gen_test.go new file mode 100644 index 0000000..99a5c74 --- /dev/null +++ b/test/gen_test.go @@ -0,0 +1,47 @@ +package test + +import ( + //"go-admin/models/tools" + //"os" + "testing" + //"text/template" +) + +func TestGoModelTemplate(t *testing.T) { + //t1, err := template.ParseFiles("model.go.template") + //if err != nil { + // t.Error(err) + //} + //table := tools.SysTables{} + //table.TBName = "sys_tables" + //tab, err := table.Get() + //if err != nil { + // t.Error(err) + //} + //file, err := os.Create("models/" + table.PackageName + ".go") + //if err != nil { + // t.Error(err) + //} + //defer file.Close() + // + //_ = t1.Execute(file, tab) + t.Log("") +} + +func TestGoApiTemplate(t *testing.T) { + //t1, err := template.ParseFiles("api.go.template") + //if err != nil { + // t.Error(err) + //} + //table := tools.SysTables{} + //table.TBName = "sys_tables" + //tab, _ := table.Get() + //file, err := os.Create("apis/" + table.PackageName + ".go") + //if err != nil { + // t.Error(err) + //} + //defer file.Close() + // + //_ = t1.Execute(file, tab) + t.Log("") +} diff --git a/test/model.go.template b/test/model.go.template new file mode 100644 index 0000000..6d9eaaa --- /dev/null +++ b/test/model.go.template @@ -0,0 +1,103 @@ +package models + +import ( +orm "go-admin/global" +"go-admin/utils" +"time" +) + +type {{.ClassName}} struct { + +{{ range .Columns -}} + {{$x := .Pk}} + // {{.ColumnComment}} + {{if ($x)}}{{.GoField}} {{.GoType}} `json:"{{.JsonField}}" gorm:"column:{{.ColumnName}};primary_key"`{{else}}{{.GoField}} {{.GoType}} `json:"{{.JsonField}}" gorm:"column:{{.ColumnName}};"`{{end}} +{{ end -}} +} + +// 创建{{.ClassName}} +func (e *{{.ClassName}}) Create() ({{.ClassName}}, error) { +var doc {{.ClassName}} +doc.IsDel = "0" +e.CreateTime = time.Now().String() +result := orm.Eloquent.Table("{{.TableName}}").Create(&e) +if result.Error != nil { +err := result.Error +return doc, err +} +doc = *e +return doc, nil +} + +// 获取{{.ClassName}} +func (e *{{.ClassName}}) Get() ({{.ClassName}}, error) { +var doc {{.ClassName}} + +table := orm.Eloquent.Table("{{.TableName}}") +{{ range .Columns -}} + {{$z := .IsQuery}} + {{- if ($z) -}}if e.{{.GoField}} != "" { + table = table.Where("{{.ColumnName}} = ?", e.{{.GoField}}) + } + {{ end }} +{{- end -}} + +if err := table.First(&doc).Error; err != nil { +return doc, err +} +return doc, nil +} + +// 获取{{.ClassName}}带分页 +func (e *{{.ClassName}}) GetPage(pageSize int, pageIndex int) ([]{{.ClassName}}, int32, error) { +var doc []{{.ClassName}} + +table := orm.Eloquent.Table("{{.TableName}}") +{{ range .Columns -}} + {{$z := .IsQuery}} + {{- if ($z) -}}if e.{{.GoField}} != "" { + table = table.Where("{{.ColumnName}} = ?", e.{{.GoField}}) + } + {{ end }} +{{- end -}} + +// 数据权限控制 +dataPermission := new(DataPermission) +dataPermission.UserId, _ = utils.StringToInt(e.DataScope) +table,err := dataPermission.GetDataScope("{{.TableName}}", table) +if err != nil { +return nil, 0, err +} +var count int32 +table = table.Offset((pageIndex - 1) * pageSize).Limit(pageSize) +if err := table.Find(&doc).Offset(-1).Limit(-1).Count(&count).Error; err != nil { +return nil, 0, err +} +return doc, count, nil +} + +// 更新{{.ClassName}} +func (e *{{.ClassName}}) Update(id int) (update {{.ClassName}}, err error) { +if err = orm.Eloquent.Table("{{.TableName}}").Where("{{.PkColumn}} = ?", id).First(&update).Error; err != nil { +return +} + +//参数1:是要修改的数据 +//参数2:是修改的数据 +if err = orm.Eloquent.Table("{{.TableName}}").Model(&update).Updates(&e).Error; err != nil { +return +} +return +} + +// 删除{{.ClassName}} +func (e *{{.ClassName}}) Delete(id int) (success bool, err error) { + +if err = orm.Eloquent.Table("{{.TableName}}").Where("{{.PkColumn}} = ?", id).Delete(&{{.ClassName}}{}).Error; err != nil { +success = false +return +} +success = true +return +} +